├── deck
├── __init__.py
├── settings
│ ├── __init__.py
│ ├── production.py
│ ├── dev.py
│ └── base.py
├── react
│ ├── app
│ │ ├── Slide
│ │ │ └── components
│ │ │ │ ├── HeadingField.jsx
│ │ │ │ ├── EmbedField.jsx
│ │ │ │ ├── ParagraphField.jsx
│ │ │ │ ├── Title.jsx
│ │ │ │ ├── ImageField.jsx
│ │ │ │ ├── FlexGroupField.jsx
│ │ │ │ ├── CodeField.jsx
│ │ │ │ └── Slide.jsx
│ │ ├── Weaver
│ │ │ └── components
│ │ │ │ └── Weaver.jsx
│ │ ├── Loader
│ │ │ ├── components
│ │ │ │ └── Loader.jsx
│ │ │ └── styles
│ │ │ │ └── styles.scss
│ │ ├── index.jsx
│ │ ├── styles.scss
│ │ └── App
│ │ │ └── components
│ │ │ └── App.jsx
│ ├── package.json
│ ├── .eslintrc.js
│ └── webpack.config.js
├── templates
│ ├── 404.html
│ ├── app.html
│ ├── 500.html
│ └── base.html
├── wsgi.py
├── views.py
├── urls.py
└── static
│ └── img
│ └── logo.svg
├── home
├── __init__.py
├── migrations
│ ├── __init__.py
│ ├── 0001_initial.py
│ └── 0002_create_homepage.py
├── models.py
└── templates
│ └── home
│ └── home_page.html
├── slides
├── __init__.py
├── migrations
│ ├── __init__.py
│ ├── 0004_slide_ordering.py
│ ├── 0002_auto_20160513_1659.py
│ ├── 0003_auto_20160513_1702.py
│ ├── 0005_auto_20160514_0407.py
│ ├── 0006_auto_20160514_0437.py
│ ├── 0001_initial.py
│ └── 0007_auto_20160529_0352.py
├── apps.py
├── image.py
└── models.py
├── .gitignore
├── requirements.txt
├── manage.py
└── README.md
/deck/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/home/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/slides/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/home/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/slides/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/deck/settings/__init__.py:
--------------------------------------------------------------------------------
1 | from .dev import *
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite3
2 | static
3 | media
4 | *.pyc
5 | node_modules
6 | npm-debug.log
7 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django>=1.9,<1.10
2 | wagtail==1.5.2
3 | django-webpack-loader==0.3.0
4 |
--------------------------------------------------------------------------------
/slides/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class SlidesConfig(AppConfig):
5 | name = 'slides'
6 |
--------------------------------------------------------------------------------
/deck/settings/production.py:
--------------------------------------------------------------------------------
1 | from .base import *
2 |
3 |
4 | DEBUG = False
5 |
6 | try:
7 | from .local import *
8 | except ImportError:
9 | pass
10 |
--------------------------------------------------------------------------------
/deck/react/app/Slide/components/HeadingField.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | export default function Heading({ value }) {
5 | return
{value}
6 | }
7 |
--------------------------------------------------------------------------------
/slides/image.py:
--------------------------------------------------------------------------------
1 | # Expose Image fields to the API
2 |
3 | from wagtail.wagtailimages.models import get_image_model
4 |
5 | model = get_image_model()
6 | model.api_fields = model.admin_form_fields
7 |
--------------------------------------------------------------------------------
/home/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from django.db import models
4 |
5 | from wagtail.wagtailcore.models import Page
6 |
7 |
8 | class HomePage(Page):
9 | pass
10 |
--------------------------------------------------------------------------------
/deck/react/app/Slide/components/EmbedField.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | export default function EmbedField({ value }) {
5 | return
6 | }
7 |
--------------------------------------------------------------------------------
/deck/react/app/Slide/components/ParagraphField.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 |
4 | export default function ParagraphField({ value }) {
5 | return
6 | }
7 |
--------------------------------------------------------------------------------
/deck/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block body_class %}template-404{% endblock %}
4 |
5 | {% block content %}
6 | Page not found
7 |
8 | Sorry, this page could not be found.
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "deck.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/deck/react/app/Slide/components/Title.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 |
4 | export default function Title({ display_title, title }) {
5 | if (!display_title) {
6 | return null
7 | }
8 |
9 | return (
10 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/deck/react/app/Weaver/components/Weaver.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Weaver({ display_weaver }) {
4 | if (!display_weaver) {
5 | return null
6 | }
7 |
8 | return (
9 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/deck/react/app/Slide/components/ImageField.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Loader from '~/Loader/components/Loader'
4 |
5 |
6 | export default function ImageField({ value, images }) {
7 | const image = images.find(image => image.id === value)
8 | if (!image) {
9 | return
10 | }
11 |
12 | return (
13 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/deck/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for deck project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "deck.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/deck/react/app/Slide/components/FlexGroupField.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 |
4 | export default function FlexGroupField({ value, images, getField }) {
5 | return (
6 |
7 | {value.map((field, index) =>
8 |
9 | {getField(field.type)({value: field.value, images})}
10 |
11 | )}
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/deck/react/app/Slide/components/CodeField.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { highlightAuto } from 'highlight.js'
4 |
5 | import 'highlight.js/styles/github-gist.css'
6 |
7 |
8 | const languages = ['python', 'jsx', 'bash', 'json', 'html']
9 |
10 |
11 | export default function CodeField({ value }) {
12 | const html = highlightAuto(value, languages)
13 | return (
14 |
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/deck/react/app/Loader/components/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import '~/Loader/styles/styles.scss'
4 |
5 |
6 | export default function Loader({ loading }) {
7 | const className = loading ? "loader" : "loader fade-out"
8 |
9 | return (
10 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/deck/templates/app.html:
--------------------------------------------------------------------------------
1 | {% load render_bundle from webpack_loader %}
2 |
3 |
4 |
5 |
6 |
7 |
8 | Wagtail CMS + React
9 |
10 |
11 |
12 |
13 |
14 | {% render_bundle 'main' %}
15 |
16 |
17 |
--------------------------------------------------------------------------------
/deck/settings/dev.py:
--------------------------------------------------------------------------------
1 | from .base import *
2 |
3 |
4 | # SECURITY WARNING: don't run with debug turned on in production!
5 | DEBUG = True
6 |
7 | for template_engine in TEMPLATES:
8 | template_engine['OPTIONS']['debug'] = True
9 |
10 | # SECURITY WARNING: keep the secret key used in production secret!
11 | SECRET_KEY = 'nkc65egdike&i3cghykteb&8gk$%ub^&!kvn-v4#4*wk((nzqq'
12 |
13 |
14 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
15 |
16 |
17 | try:
18 | from .local import *
19 | except ImportError:
20 | pass
21 |
--------------------------------------------------------------------------------
/home/templates/home/home_page.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block body_class %}template-homepage{% endblock %}
4 |
5 | {% block content %}
6 | Welcome to your new Wagtail site!
7 |
8 | You can access the admin interface here (make sure you have run "./manage.py createsuperuser" in the console first).
9 |
10 |
If you haven't already given the documentation a read, head over to http://docs.wagtail.io to start building on Wagtail
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/slides/migrations/0004_slide_ordering.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.9.6 on 2016-05-13 17:10
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('slides', '0003_auto_20160513_1702'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='slide',
17 | name='ordering',
18 | field=models.IntegerField(default=1),
19 | preserve_default=False,
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/slides/migrations/0002_auto_20160513_1659.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.9.6 on 2016-05-13 16:59
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations
6 | import wagtail.wagtailcore.fields
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | ('slides', '0001_initial'),
13 | ]
14 |
15 | operations = [
16 | migrations.AlterField(
17 | model_name='slide',
18 | name='speaker_notes',
19 | field=wagtail.wagtailcore.fields.RichTextField(blank=True, null=True),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/home/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('wagtailcore', '__latest__'),
11 | ]
12 |
13 | operations = [
14 | migrations.CreateModel(
15 | name='HomePage',
16 | fields=[
17 | ('page_ptr', models.OneToOneField(on_delete=models.CASCADE, parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
18 | ],
19 | options={
20 | 'abstract': False,
21 | },
22 | bases=('wagtailcore.page',),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/deck/templates/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Internal server error
10 |
11 |
12 |
13 | Internal server error
14 |
15 | Sorry, there seems to be an error. Please try again soon.
16 |
17 |
18 |
--------------------------------------------------------------------------------
/slides/migrations/0003_auto_20160513_1702.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.9.6 on 2016-05-13 17:02
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('slides', '0002_auto_20160513_1659'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='slide',
17 | name='centered_slide',
18 | field=models.BooleanField(default=False),
19 | ),
20 | migrations.AddField(
21 | model_name='slide',
22 | name='display_title',
23 | field=models.BooleanField(default=False),
24 | ),
25 | migrations.AddField(
26 | model_name='slide',
27 | name='display_weaver',
28 | field=models.BooleanField(default=False),
29 | ),
30 | ]
31 |
--------------------------------------------------------------------------------
/deck/react/app/index.jsx:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill'
2 |
3 | window.onunhandledrejection = (e) => {
4 | if ('reason' in e) {
5 | throw e.reason
6 | }
7 | }
8 |
9 | import React, { Component } from 'react'
10 | import { render } from 'react-dom'
11 | import { Router, IndexRedirect, Route, browserHistory } from 'react-router'
12 |
13 | import App from './App/components/App'
14 | import Slide from '~/Slide/components/Slide'
15 |
16 | import './styles.scss'
17 |
18 | class NotFound extends Component {
19 | render() {
20 | return 404: Error Not Found
21 | }
22 | }
23 |
24 | render(
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | , document.getElementById('app'))
35 |
--------------------------------------------------------------------------------
/slides/migrations/0005_auto_20160514_0407.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.9.6 on 2016-05-14 04:07
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations
6 | import wagtail.wagtailcore.blocks
7 | import wagtail.wagtailcore.fields
8 | import wagtail.wagtailimages.blocks
9 |
10 |
11 | class Migration(migrations.Migration):
12 |
13 | dependencies = [
14 | ('slides', '0004_slide_ordering'),
15 | ]
16 |
17 | operations = [
18 | migrations.AlterField(
19 | model_name='slide',
20 | name='contents',
21 | field=wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image')), ('embed', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code')))),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/slides/migrations/0006_auto_20160514_0437.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.9.6 on 2016-05-14 04:37
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations
6 | import wagtail.wagtailcore.blocks
7 | import wagtail.wagtailcore.fields
8 | import wagtail.wagtailimages.blocks
9 |
10 |
11 | class Migration(migrations.Migration):
12 |
13 | dependencies = [
14 | ('slides', '0005_auto_20160514_0407'),
15 | ]
16 |
17 | operations = [
18 | migrations.AlterField(
19 | model_name='slide',
20 | name='contents',
21 | field=wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image')), ('embed', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code')), ('code', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code')))),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/deck/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponse, JsonResponse
2 |
3 |
4 | # Server holds the presentation.
5 | template = '''
6 | Clicks: {cur_count}.
7 |
8 | Increment to {next_count}
9 |
10 | '''
11 |
12 |
13 | def counter(request, cur_count="1"):
14 | # Server deals with application logic.
15 | next_count = int(cur_count) + 1
16 |
17 | # Server responds with everything the browser needs.
18 | response = template.format(
19 | cur_count=cur_count, next_count=next_count)
20 |
21 | return HttpResponse(response)
22 |
23 |
24 | # Front-end deals with presentation separately.
25 | def decoupled_counter(request, cur_count="1"):
26 | # Server deals with application logic.
27 | next_count = int(cur_count) + 1
28 |
29 | response = {
30 | 'cur_count': int(cur_count),
31 | 'next_count': next_count,
32 | 'next_link': '/counter/{}/'.format(next_count),
33 | }
34 |
35 | # Server just responds with data.
36 | return JsonResponse(response)
37 |
--------------------------------------------------------------------------------
/slides/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.9.6 on 2016-05-13 14:12
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 | import django.db.models.deletion
7 | import wagtail.wagtailcore.blocks
8 | import wagtail.wagtailcore.fields
9 | import wagtail.wagtailimages.blocks
10 |
11 |
12 | class Migration(migrations.Migration):
13 |
14 | initial = True
15 |
16 | dependencies = [
17 | ('wagtailcore', '0028_merge'),
18 | ]
19 |
20 | operations = [
21 | migrations.CreateModel(
22 | name='Slide',
23 | fields=[
24 | ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
25 | ('contents', wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image'))))),
26 | ('speaker_notes', wagtail.wagtailcore.fields.RichTextField()),
27 | ],
28 | options={
29 | 'abstract': False,
30 | },
31 | bases=('wagtailcore.page',),
32 | ),
33 | ]
34 |
--------------------------------------------------------------------------------
/home/migrations/0002_create_homepage.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import migrations
5 |
6 |
7 | def create_homepage(apps, schema_editor):
8 | # Get models
9 | ContentType = apps.get_model('contenttypes.ContentType')
10 | Page = apps.get_model('wagtailcore.Page')
11 | Site = apps.get_model('wagtailcore.Site')
12 | HomePage = apps.get_model('home.HomePage')
13 |
14 | # Delete the default homepage
15 | Page.objects.get(id=2).delete()
16 |
17 | # Create content type for homepage model
18 | homepage_content_type, created = ContentType.objects.get_or_create(
19 | model='homepage', app_label='home')
20 |
21 | # Create a new homepage
22 | homepage = HomePage.objects.create(
23 | title="Homepage",
24 | slug='home',
25 | content_type=homepage_content_type,
26 | path='00010001',
27 | depth=2,
28 | numchild=0,
29 | url_path='/home/',
30 | )
31 |
32 | # Create a site with the new homepage set as the root
33 | Site.objects.create(
34 | hostname='localhost', root_page=homepage, is_default_site=True)
35 |
36 |
37 | class Migration(migrations.Migration):
38 |
39 | dependencies = [
40 | ('home', '0001_initial'),
41 | ]
42 |
43 | operations = [
44 | migrations.RunPython(create_homepage),
45 | ]
46 |
--------------------------------------------------------------------------------
/deck/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import include, url
2 | from django.conf import settings
3 | from django.contrib import admin
4 | from django.views.generic import TemplateView
5 |
6 | from wagtail.wagtailadmin import urls as wagtailadmin_urls
7 | from wagtail.wagtaildocs import urls as wagtaildocs_urls
8 | from wagtail.wagtailcore import urls as wagtail_urls
9 | from wagtail.wagtailimages import urls as wagtailimage_urls
10 | from wagtail.contrib.wagtailapi import urls as wagtailapi_urls
11 |
12 | from .views import decoupled_counter, counter
13 |
14 |
15 | urlpatterns = [
16 | url(r'^django-admin/', include(admin.site.urls)),
17 |
18 | url(r'^admin/', include(wagtailadmin_urls)),
19 | url(r'^documents/', include(wagtaildocs_urls)),
20 |
21 | url(r'^api/', include(wagtailapi_urls)),
22 | url(r'^wagtail/', include(wagtail_urls)),
23 | url(r'^images/', include(wagtailimage_urls)),
24 |
25 | url(r'^counter/$', counter),
26 | url(r'^counter/(?P[0-9]+)/$', counter),
27 | ]
28 |
29 |
30 | if settings.DEBUG:
31 | from django.conf.urls.static import static
32 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns
33 | from django.views.generic import TemplateView
34 |
35 | # Serve static and media files from development server
36 | urlpatterns += staticfiles_urlpatterns()
37 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
38 |
39 | urlpatterns += [url('', TemplateView.as_view(template_name="app.html"))]
40 |
--------------------------------------------------------------------------------
/deck/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load static wagtailuserbar %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {% block title %}{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %}{% endblock %}{% block title_suffix %}{% endblock %}
12 |
13 |
14 |
15 | {# Global stylesheets #}
16 |
17 |
18 | {% block extra_css %}
19 | {# Override this in templates to add extra stylesheets #}
20 | {% endblock %}
21 |
22 |
23 |
24 | {% wagtailuserbar %}
25 |
26 | {% block content %}{% endblock %}
27 |
28 | {# Global javascript #}
29 |
30 |
31 | {% block extra_js %}
32 | {# Override this in templates to add extra javascript #}
33 | {% endblock %}
34 |
35 |
36 |
--------------------------------------------------------------------------------
/slides/migrations/0007_auto_20160529_0352.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.9.6 on 2016-05-29 03:52
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations
6 | import wagtail.wagtailcore.blocks
7 | import wagtail.wagtailcore.fields
8 | import wagtail.wagtailimages.blocks
9 |
10 |
11 | class Migration(migrations.Migration):
12 |
13 | dependencies = [
14 | ('slides', '0006_auto_20160514_0437'),
15 | ]
16 |
17 | operations = [
18 | migrations.AlterField(
19 | model_name='slide',
20 | name='contents',
21 | field=wagtail.wagtailcore.fields.StreamField((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image')), ('embed', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='site', label='Raw HTML')), ('code', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code', label='Highlighted Code')), ('flex_group', wagtail.wagtailcore.blocks.StreamBlock((('heading', wagtail.wagtailcore.blocks.CharBlock(classname='heading', icon='title')), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock(icon='pilcrow')), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(icon='image')), ('embed', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='site', label='Raw HTML')), ('code', wagtail.wagtailcore.blocks.RawHTMLBlock(icon='code', label='Highlighted Code'))), icon='doc-empty', label='Flex Group')))),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/deck/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wagtail-deck",
3 | "version": "0.0.0",
4 | "private": true,
5 | "description": "",
6 | "main": "webpack.config.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "build": "webpack -p",
10 | "start": "webpack -w -d --display-error-details",
11 | "lint": "eslint app/**/*.jsx"
12 | },
13 | "author": "Emily Horsman ",
14 | "devDependencies": {
15 | "autoprefixer": "^6.3.6",
16 | "babel-core": "^6.8.0",
17 | "babel-eslint": "^6.0.4",
18 | "babel-loader": "^6.2.4",
19 | "babel-plugin-add-module-exports": "^0.2.1",
20 | "babel-polyfill": "^6.8.0",
21 | "babel-preset-es2015": "^6.6.0",
22 | "babel-preset-react": "^6.5.0",
23 | "babel-preset-stage-0": "^6.5.0",
24 | "babel-preset-stage-1": "^6.5.0",
25 | "babel-preset-stage-2": "^6.5.0",
26 | "css-loader": "^0.23.1",
27 | "eslint": "^2.9.0",
28 | "eslint-plugin-react": "^5.1.1",
29 | "exports-loader": "^0.6.3",
30 | "imports-loader": "^0.6.5",
31 | "json-loader": "^0.5.4",
32 | "node-sass": "^3.7.0",
33 | "postcss-loader": "^0.9.1",
34 | "sass-loader": "^3.2.0",
35 | "style-loader": "^0.13.1",
36 | "webpack": "^2.1.0-beta.7",
37 | "webpack-bundle-tracker": "0.0.93",
38 | "webpack-dev-server": "^1.14.1",
39 | "webpack-merge": "^0.12.0"
40 | },
41 | "dependencies": {
42 | "axios": "^0.11.0",
43 | "highlight.js": "^9.3.0",
44 | "react": "^15.0.2",
45 | "react-dom": "^15.0.2",
46 | "react-icons": "^2.0.1",
47 | "react-router": "^2.4.0"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/slides/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from wagtail.wagtailcore.models import Page
4 | from wagtail.wagtailcore.fields import StreamField, RichTextField
5 | from wagtail.wagtailcore import blocks
6 | from wagtail.wagtailadmin.edit_handlers import FieldPanel, StreamFieldPanel
7 | from wagtail.wagtailimages.blocks import ImageChooserBlock
8 |
9 | from . import image
10 |
11 |
12 | base_stream_blocks = [
13 | ('heading', blocks.CharBlock(icon='title', classname="heading")),
14 | ('paragraph', blocks.RichTextBlock(icon='pilcrow')),
15 | ('image', ImageChooserBlock(icon='image')),
16 | ('embed', blocks.RawHTMLBlock(icon='site', label='Raw HTML')),
17 | ('code', blocks.RawHTMLBlock(icon='code', label='Highlighted Code')),
18 | ]
19 |
20 |
21 | class Slide(Page):
22 | contents = StreamField(base_stream_blocks + [
23 | ('flex_group', blocks.StreamBlock(base_stream_blocks,
24 | icon='doc-empty',
25 | label='Flex Group')),
26 | ])
27 |
28 | speaker_notes = RichTextField(blank=True, null=True)
29 |
30 | ordering = models.IntegerField()
31 | display_weaver = models.BooleanField(default=False)
32 | display_title = models.BooleanField(default=False)
33 | centered_slide = models.BooleanField(default=False)
34 |
35 | content_panels = Page.content_panels + [
36 | FieldPanel('ordering'),
37 | FieldPanel('display_weaver'),
38 | FieldPanel('display_title'),
39 | FieldPanel('centered_slide'),
40 | FieldPanel('speaker_notes'),
41 | StreamFieldPanel('contents'),
42 | ]
43 |
44 | api_fields = ['speaker_notes', 'contents', 'display_weaver', 'display_title', 'centered_slide', 'ordering',]
45 |
--------------------------------------------------------------------------------
/deck/react/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * You should probably check out the Airbnb React/JSX style guide.
3 | * https://github.com/airbnb/javascript/tree/master/react
4 | */
5 |
6 | module.exports = {
7 | "rules": {
8 | "quotes": [
9 | 2,
10 | "single"
11 | ],
12 | "linebreak-style": [
13 | 2,
14 | "unix"
15 | ],
16 | "semi": [
17 | 2,
18 | "never"
19 | ],
20 | "comma-dangle": 0,
21 | "jsx-quotes": 1,
22 | "react/no-danger": 1,
23 | "react/no-unknown-property": 1,
24 | "react/no-direct-mutation-state": 1,
25 | "react/jsx-sort-props": 1,
26 | "react/no-is-mounted": 1,
27 | "react/sort-comp": 1,
28 | "react/jsx-no-bind": 1,
29 | "react/self-closing-comp": 1,
30 | "react/wrap-multilines": 1,
31 | "react/jsx-boolean-value": 1,
32 | "react/jsx-closing-bracket-location": 1,
33 | "react/jsx-pascal-case": 1,
34 | "react/prefer-es6-class": 1,
35 | "react/no-multi-comp": 1,
36 | "react/jsx-uses-react": 1,
37 | "react/react-in-jsx-scope": 1,
38 | "react/jsx-max-props-per-line": [1, {"maximum": 2}]
39 | },
40 | "env": {
41 | "es6": true,
42 | "browser": true
43 | },
44 | "extends": "eslint:recommended",
45 | "ecmaFeatures": {
46 | "jsx": true,
47 | "experimentalObjectRestSpread": true,
48 | "modules": true,
49 | "spread": true
50 | },
51 | "parser": "babel-eslint",
52 | "parserOptions": {
53 | "sourceType": "module"
54 | },
55 | "plugins": [
56 | "react"
57 | ],
58 | "settings": {
59 | "react": {
60 | "pragma": "React"
61 | }
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A slide deck with React and Wagtail
2 |
3 | This is a proof-of-concept library demonstrating how to
4 | build a slide presentation CMS using React and Wagtail.
5 |
6 | ## Getting Started
7 |
8 | ```
9 | $ mkvirtualenv --python=python3 wagtail-react-deck
10 | $ git clone https://github.com/littleweaver/wagtail-react-deck.git
11 | $ cd wagtail-react-deck
12 | $ pip install -r requirements.txt
13 | $ ./manage.py migrate
14 | $ ./manage.py runserver
15 | ```
16 |
17 | ## Running webpack
18 |
19 | `django-webpack-loader` and `webpack-stats` are used for the marriage of
20 | Django and webpack. `webpack-stats` outputs a JSON file each time a webpack
21 | bundle is built. `django-webpack-loader` polls this file to determine which
22 | bundle to serve to visitors.
23 |
24 | Make sure you are running node `5.x` or `6.x`. (We recommend
25 | managing your node versions with nvm.)
26 |
27 | ### Development
28 |
29 | ```
30 | $ cd deck/react
31 | $ npm install
32 | $ npm run start
33 | ```
34 |
35 | ### Production
36 |
37 | ```
38 | $ cd deck/react
39 | $ npm install
40 | $ NODE_ENV=production npm run build
41 | ```
42 |
43 | ## Presentation
44 |
45 | Use the left and right arrow keys to change your slides.
46 | Use the `o` key to open a deck with speaker notes in a new tab. Slide
47 | transitions will sync between the presentation and speaker copy (i.e., hitting
48 | the left arrow in the speaker deck with do the same in the presentation deck).
49 |
50 | ## Slide/Content Management
51 |
52 | You’ll need to create a superuser,
53 |
54 | ```
55 | $ ./manage.py createsuperuser
56 | ```
57 |
58 | Wagtail's admin lives at `/admin/` (that trailing slash is important).
59 |
60 | All requests not matched by Django will fall through to the client-side router.
61 |
62 | ## FAQ
63 |
64 | ### Isn’t this totally unnecessary?
65 |
66 | Yes.
67 |
--------------------------------------------------------------------------------
/deck/react/app/Loader/styles/styles.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2016 Connor Atherton
3 | *
4 | * All animations must live in their own file
5 | * in the animations directory and be included
6 | * here.
7 | *
8 | */
9 | /**
10 | * Styles shared by multiple animations
11 | */
12 | /**
13 | * Dots
14 | */
15 | @-webkit-keyframes scale {
16 | 0% {
17 | -webkit-transform: scale(1);
18 | transform: scale(1);
19 | opacity: 1; }
20 | 45% {
21 | -webkit-transform: scale(0.1);
22 | transform: scale(0.1);
23 | opacity: 0.7; }
24 | 80% {
25 | -webkit-transform: scale(1);
26 | transform: scale(1);
27 | opacity: 1; } }
28 | @keyframes scale {
29 | 0% {
30 | -webkit-transform: scale(1);
31 | transform: scale(1);
32 | opacity: 1; }
33 | 45% {
34 | -webkit-transform: scale(0.1);
35 | transform: scale(0.1);
36 | opacity: 0.7; }
37 | 80% {
38 | -webkit-transform: scale(1);
39 | transform: scale(1);
40 | opacity: 1; } }
41 |
42 | .ball-pulse > div:nth-child(1) {
43 | -webkit-animation: scale 0.75s -0.24s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08);
44 | animation: scale 0.75s -0.24s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); }
45 |
46 | .ball-pulse > div:nth-child(2) {
47 | -webkit-animation: scale 0.75s -0.12s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08);
48 | animation: scale 0.75s -0.12s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); }
49 |
50 | .ball-pulse > div:nth-child(3) {
51 | -webkit-animation: scale 0.75s 0s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08);
52 | animation: scale 0.75s 0s infinite cubic-bezier(0.2, 0.68, 0.18, 1.08); }
53 |
54 | .ball-pulse > div {
55 | background-color: #fff;
56 | width: 15px;
57 | height: 15px;
58 | border-radius: 100%;
59 | margin: 2px;
60 | -webkit-animation-fill-mode: both;
61 | animation-fill-mode: both;
62 | display: inline-block; }
63 |
--------------------------------------------------------------------------------
/deck/react/app/styles.scss:
--------------------------------------------------------------------------------
1 | $font-size: 3vw;
2 |
3 | * {
4 | box-sizing: border-box;
5 | }
6 |
7 | *:before,
8 | *:after {
9 | box-sizing: inherit;
10 | }
11 |
12 | html {
13 | font-family: 'Raleway', serif;
14 | font-size: $font-size;
15 | }
16 |
17 | body {
18 | margin: 0;
19 | }
20 |
21 | header,
22 | header h1,
23 | h2,
24 | h3 {
25 | margin-top: 0;
26 | margin-bottom: 0.15em;
27 |
28 | font-family: 'Lato';
29 | }
30 |
31 | header {
32 | border-bottom: 0.15rem solid lighten(#171512, 20%);
33 | margin: 4vh 5vw;
34 | }
35 |
36 | header h1 {
37 | font-size: 5vw;
38 | }
39 |
40 | pre {
41 | font-size: 0.8rem;
42 | line-height: 1;
43 | }
44 |
45 | .slide--has-weaver header {
46 | margin-right: 20vw;
47 | }
48 |
49 | .slide--centered header {
50 | position: absolute;
51 | top: 0;
52 | left: 0;
53 | right: 0;
54 | }
55 |
56 | a {
57 | color: #3f51b5;
58 |
59 | text-decoration: none;
60 |
61 | transition: all 0.25s ease;
62 |
63 | &:hover {
64 | color: #283593;
65 | }
66 | }
67 |
68 | img {
69 | width: 50vw;
70 | max-width: 75vw;
71 | height: auto;
72 | max-height: 75vh;
73 | }
74 |
75 | li:not(:last-child) {
76 | margin-bottom: 0.5em;
77 | }
78 |
79 | .ball-pulse > div {
80 | background-color: rgba(#283593, 0.8);
81 | }
82 |
83 | .loader {
84 | letter-spacing: 1px;
85 | }
86 |
87 | .loader-inner {
88 | display: inline-block;
89 | }
90 |
91 | .weaver {
92 | position: absolute;
93 | top: 0;
94 | right: 1rem;
95 |
96 | width: 15vw;
97 | height: auto;
98 | }
99 |
100 | .slide-contents {
101 | margin-left: 10vw;
102 | margin-right: 10vw;
103 | }
104 |
105 | .slide-contents--centered {
106 | display: flex;
107 | height: 100vh;
108 |
109 | flex-direction: column;
110 | justify-content: center;
111 | align-items: center;
112 |
113 | h1 {
114 | text-align: center;
115 | }
116 | }
117 |
118 | p {
119 | margin: 0.5rem 0;
120 | }
121 |
122 | .slide-contents--centered svg {
123 | height: 60vh;
124 | width: auto;
125 | max-width: 75vw;
126 | }
127 |
128 | .flex svg {
129 | height: 40vh;
130 | max-width: 50vw;
131 | }
132 |
133 | .flex {
134 | display: flex;
135 |
136 | justify-content: center;
137 | }
138 |
139 | .slide-contents--centered .flex {
140 | align-items: center;
141 | }
142 |
143 | .speaker-notes {
144 | position: fixed;
145 | bottom: 0;
146 | left: 0;
147 | right: 0;
148 | padding: 10px;
149 | border-top: 1px solid black;
150 |
151 | background: white;
152 |
153 | font-size: 0.75rem;
154 |
155 | &:hover {
156 | background: rgba(white, 0.6);
157 | }
158 | }
159 |
160 | .counter {
161 | position: fixed;
162 | bottom: 0;
163 | right: 0;
164 | padding: 0.5rem;
165 |
166 | color: #ccc;
167 |
168 | font-size: 2vw;
169 | }
170 |
--------------------------------------------------------------------------------
/deck/react/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var merge = require('webpack-merge');
3 | var BundleTracker = require('webpack-bundle-tracker');
4 | var autoprefixer = require('autoprefixer');
5 |
6 | var TARGET = process.env.npm_lifecycle_event;
7 | process.env.BABEL_ENV = TARGET;
8 |
9 | var target = __dirname + '/../static/js/bundles';
10 |
11 | var common = {
12 | entry: __dirname + '/app/index.jsx',
13 |
14 | output: {
15 | path: target,
16 | filename: '[name]-[hash].js'
17 | },
18 |
19 | resolve: {
20 | alias: { '~': __dirname + '/app' },
21 | extensions: ['', '.js', '.jsx', '.json', '.scss'],
22 | modulesDirectories: ['node_modules']
23 | },
24 |
25 | module: {
26 | loaders: [
27 | {
28 | test: /\.jsx?$/,
29 | loader: 'babel',
30 | query: {
31 | presets: ['react', 'es2015', 'stage-0', 'stage-1', 'stage-2'],
32 | plugins: ['add-module-exports']
33 | },
34 | include: [
35 | __dirname + '/app',
36 | __dirname + '/node_modules/react-icons',
37 | __dirname + '/node_modules/react-icon-base',
38 | ],
39 | },
40 |
41 | {
42 | test: /\.json$/,
43 | loader: 'json',
44 | },
45 |
46 | {
47 | test: /\.scss$/,
48 | loader: 'style!css!postcss!sass',
49 | include: __dirname + '/app'
50 | },
51 |
52 | {
53 | test: /\.css$/,
54 | loader: 'style!css!postcss'
55 | }
56 | ]
57 | },
58 |
59 | sassLoader: {
60 | includePaths: [__dirname + '/node_modules']
61 | },
62 |
63 | postcss: function() {
64 | return {
65 | defaults: [autoprefixer],
66 | cleaner: [autoprefixer({ browsers: [
67 | 'Chrome >= 35',
68 | 'Firefox >= 31',
69 | 'Edge >= 12',
70 | 'Explorer >= 9',
71 | 'iOS >= 8',
72 | 'Safari >= 8',
73 | 'Android 2.3',
74 | 'Android >= 4',
75 | 'Opera >= 12'
76 | ]})]
77 | }
78 | },
79 |
80 | plugins: [
81 | new BundleTracker({
82 | path: target,
83 | filename: './webpack-stats.json'
84 | })
85 | ]
86 | };
87 |
88 | if (TARGET === 'build') {
89 | module.exports = merge(common, {
90 | plugins: [
91 | new webpack.DefinePlugin({
92 | 'process.env': { 'NODE_ENV': JSON.stringify('production') }
93 | }),
94 |
95 | new webpack.optimize.UglifyJsPlugin()
96 | ]
97 | });
98 | }
99 |
100 | if (TARGET === 'start') {
101 | module.exports = merge(common, {
102 | devtool: 'eval-source-map',
103 | devServer: {
104 | contentBase: target,
105 | progress: true,
106 | }
107 | });
108 | }
109 |
--------------------------------------------------------------------------------
/deck/react/app/Slide/components/Slide.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import Loader from '~/Loader/components/Loader'
4 | import CodeField from '~/Slide/components/CodeField'
5 | import EmbedField from '~/Slide/components/EmbedField'
6 | import FlexGroupField from '~/Slide/components/FlexGroupField'
7 | import HeadingField from '~/Slide/components/HeadingField'
8 | import ImageField from '~/Slide/components/ImageField'
9 | import ParagraphField from '~/Slide/components/ParagraphField'
10 | import Title from '~/Slide/components/Title'
11 | import Weaver from '~/Weaver/components/Weaver'
12 |
13 |
14 | const FIELDS = {
15 | code: CodeField,
16 | embed: EmbedField,
17 | flex_group: FlexGroupField,
18 | heading: HeadingField,
19 | image: ImageField,
20 | paragraph: ParagraphField,
21 | }
22 |
23 |
24 | function getField(fieldType) {
25 | if (!FIELDS.hasOwnProperty(fieldType))
26 | throw 'Unknown fieldType: ' + fieldType
27 | return FIELDS[fieldType]
28 | }
29 |
30 |
31 | function getSlideClassName(base, slide) {
32 | const classNames = [base]
33 | if (slide.centered_slide) {
34 | classNames.push(`${base}--centered`)
35 | }
36 |
37 | if (slide.display_weaver) {
38 | classNames.push(`${base}--has-weaver`)
39 | }
40 |
41 | return classNames.join(' ')
42 | }
43 |
44 |
45 | class Slide extends Component {
46 | constructor(props) {
47 | super(props)
48 | this.state = {}
49 | }
50 |
51 | componentWillReceiveProps(nextProps) {
52 | const slide = nextProps.pages.find(page => page.ordering >= parseInt(nextProps.params.ordering))
53 | if (!slide) {
54 | return
55 | }
56 |
57 | this.setState({
58 | slide,
59 | })
60 |
61 | document.title = `${slide.ordering} | ${slide.title}`
62 | }
63 |
64 | render() {
65 | const slide = this.state.slide
66 | if (!slide) {
67 | return
68 | }
69 |
70 | const speakerNotes = window.opener &&
71 |
75 |
76 | return (
77 |
78 | {speakerNotes}
79 |
80 |
81 |
82 |
83 | {slide.contents.map((field, index) =>
84 |
85 | {getField(field.type)({
86 | value: field.value,
87 | images: this.props.images,
88 | getField,
89 | })}
90 |
91 | )}
92 |
93 |
94 |
95 | {this.props.params.ordering} of {this.props.pages.length}
96 |
97 |
98 | )
99 | }
100 | }
101 |
102 | export default Slide
103 |
--------------------------------------------------------------------------------
/deck/settings/base.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for deck project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.9.6.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.9/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.9/ref/settings/
11 | """
12 |
13 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
14 | import os
15 |
16 | PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 | BASE_DIR = os.path.dirname(PROJECT_DIR)
18 |
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
22 |
23 |
24 | # Application definition
25 |
26 | INSTALLED_APPS = [
27 | 'home',
28 | 'slides',
29 |
30 | 'wagtail.wagtailforms',
31 | 'wagtail.wagtailredirects',
32 | 'wagtail.wagtailembeds',
33 | 'wagtail.wagtailsites',
34 | 'wagtail.wagtailusers',
35 | 'wagtail.wagtailsnippets',
36 | 'wagtail.wagtaildocs',
37 | 'wagtail.wagtailimages',
38 | 'wagtail.wagtailsearch',
39 | 'wagtail.wagtailadmin',
40 | 'wagtail.wagtailcore',
41 |
42 | 'wagtail.contrib.wagtailapi',
43 | 'rest_framework',
44 |
45 | 'webpack_loader',
46 |
47 | 'modelcluster',
48 | 'taggit',
49 |
50 | 'django.contrib.admin',
51 | 'django.contrib.auth',
52 | 'django.contrib.contenttypes',
53 | 'django.contrib.sessions',
54 | 'django.contrib.messages',
55 | 'django.contrib.staticfiles',
56 | ]
57 |
58 | MIDDLEWARE_CLASSES = [
59 | 'django.contrib.sessions.middleware.SessionMiddleware',
60 | 'django.middleware.common.CommonMiddleware',
61 | 'django.middleware.csrf.CsrfViewMiddleware',
62 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
63 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
64 | 'django.contrib.messages.middleware.MessageMiddleware',
65 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
66 | 'django.middleware.security.SecurityMiddleware',
67 |
68 | 'wagtail.wagtailcore.middleware.SiteMiddleware',
69 | 'wagtail.wagtailredirects.middleware.RedirectMiddleware',
70 | ]
71 |
72 | ROOT_URLCONF = 'deck.urls'
73 |
74 | TEMPLATES = [
75 | {
76 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
77 | 'DIRS': [
78 | os.path.join(PROJECT_DIR, 'templates'),
79 | ],
80 | 'APP_DIRS': True,
81 | 'OPTIONS': {
82 | 'context_processors': [
83 | 'django.template.context_processors.debug',
84 | 'django.template.context_processors.request',
85 | 'django.contrib.auth.context_processors.auth',
86 | 'django.contrib.messages.context_processors.messages',
87 | ],
88 | },
89 | },
90 | ]
91 |
92 | WSGI_APPLICATION = 'deck.wsgi.application'
93 |
94 |
95 | # Database
96 | # https://docs.djangoproject.com/en/1.9/ref/settings/#databases
97 |
98 | DATABASES = {
99 | 'default': {
100 | 'ENGINE': 'django.db.backends.sqlite3',
101 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
102 | }
103 | }
104 |
105 |
106 | # Internationalization
107 | # https://docs.djangoproject.com/en/1.9/topics/i18n/
108 |
109 | LANGUAGE_CODE = 'en-us'
110 |
111 | TIME_ZONE = 'UTC'
112 |
113 | USE_I18N = True
114 |
115 | USE_L10N = True
116 |
117 | USE_TZ = True
118 |
119 |
120 | # Static files (CSS, JavaScript, Images)
121 | # https://docs.djangoproject.com/en/1.9/howto/static-files/
122 |
123 | STATICFILES_FINDERS = [
124 | 'django.contrib.staticfiles.finders.FileSystemFinder',
125 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
126 | ]
127 |
128 | STATICFILES_DIRS = [
129 | os.path.join(PROJECT_DIR, 'static'),
130 | ]
131 |
132 | STATIC_ROOT = os.path.join(BASE_DIR, 'static')
133 | STATIC_URL = '/static/'
134 |
135 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
136 | MEDIA_URL = '/media/'
137 |
138 |
139 | # Wagtail settings
140 |
141 | WAGTAIL_SITE_NAME = "deck"
142 | WAGTAILAPI_BASE_URL = os.environ.get('WAGTAILAPI_BASE_URL', 'http://localhost')
143 | WAGTAILAPI_LIMIT_MAX = 1000
144 |
145 |
146 | # Webpack Loader Configuration
147 |
148 | WEBPACK_LOADER = {
149 | 'DEFAULT': {
150 | 'STATS_FILE': os.path.join(PROJECT_DIR, 'static', 'js', 'bundles', 'webpack-stats.json'),
151 | 'BUNDLE_DIR_NAME': 'js/bundles/',
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/deck/react/app/App/components/App.jsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import React, { Component, cloneElement } from 'react'
3 | import { Link, browserHistory } from 'react-router'
4 |
5 |
6 | const pageFields = [
7 | 'title',
8 | 'speaker_notes',
9 | 'contents',
10 | 'display_weaver',
11 | 'display_title',
12 | 'centered_slide',
13 | 'ordering',
14 | ]
15 |
16 | const sortPages = (a, b) => a.ordering - b.ordering
17 |
18 | const leftKeyCode = 37
19 | const rightKeyCode = 39
20 | const oKeyCode = 79
21 |
22 | // Preload an image, hidden from the user. This speeds up transitions
23 | // between slides.
24 | function preloadImage(url) {
25 | const img = new Image()
26 | img.src = url
27 | }
28 |
29 | class App extends Component {
30 | constructor(props) {
31 | super(props)
32 |
33 | this.state = {
34 | loading: 0,
35 | pages: [],
36 | images: [],
37 | }
38 | }
39 |
40 | componentDidMount() {
41 | axios.get('/api/v1/pages/', {
42 | params: {
43 | type: 'slides.Slide',
44 | fields: pageFields.join(','),
45 | order: 'ordering',
46 | limit: 1000,
47 | }
48 | })
49 | .then(this.handlePagesIndex.bind(this))
50 |
51 | document.addEventListener('keydown', this.handleKeydown.bind(this))
52 | window.addEventListener('message', this.handleMessage.bind(this))
53 | }
54 |
55 | handleMessage(event) {
56 | if (event.data.nextSlide) {
57 | browserHistory.push(`/slide/${event.data.nextSlide}/`)
58 | }
59 | }
60 |
61 | handleKeydown(event) {
62 | if (event.keyCode === rightKeyCode) {
63 | this.changeSlide(1)
64 | } else if (event.keyCode === leftKeyCode) {
65 | this.changeSlide(-1)
66 | } else if (event.keyCode === oKeyCode) {
67 | this.openSpeakerNotes()
68 | }
69 | }
70 |
71 | openSpeakerNotes() {
72 | this.speakerWindow = window.open(window.location.href)
73 | }
74 |
75 | changeSlide(offset) {
76 | const currentSlide = parseInt(this.props.params.ordering)
77 | const orderings = this.state.pages.map(page => page.ordering)
78 | const maxOrdering = Math.max.apply(null, orderings)
79 | const minOrdering = Math.min.apply(null, orderings)
80 |
81 | const nextSlide = Math.max(Math.min(currentSlide + offset, maxOrdering), minOrdering)
82 | browserHistory.push(`/slide/${nextSlide}/`)
83 |
84 | const target = window.opener || this.speakerWindow
85 | if (target) {
86 | target.postMessage({ nextSlide, }, '*')
87 | }
88 | }
89 |
90 | handlePagesIndex(response) {
91 | const fetchedPages = response.data.pages
92 | const existingPages = this.state.pages
93 | const pages = fetchedPages.filter(fetchedPage => {
94 | return !existingPages.some(page => page.id === fetchedPage.id)
95 | })
96 |
97 | const imageFields = pages.reduce((acc, page) => {
98 | return acc.concat(page.contents.filter(field => field.type === 'image'))
99 | }, [])
100 |
101 | this.fireImageRequests(imageFields)
102 |
103 | this.setState({
104 | loading: this.state.loading - 1,
105 | pages: this.state.pages.concat(pages).sort(sortPages),
106 | })
107 | }
108 |
109 | handlePagesDetail(response) {
110 | const fetchedPage = response.data
111 | if (this.state.pages.find(page => page.id === fetchedPage.id)) {
112 | return
113 | }
114 |
115 | const imageFields = fetchedPage.contents.filter(field => field.type === 'image')
116 |
117 | this.fireImageRequests(imageFields)
118 |
119 | this.setState({
120 | loading: this.state.loading - 1,
121 | pages: this.state.pages.concat(fetchedPage).sort(sortPages),
122 | })
123 | }
124 |
125 | handleImagesIndex(response) {
126 | this.setState({
127 | loading: this.state.loading - 1,
128 | images: this.state.images.concat(response.data.images),
129 | })
130 | }
131 |
132 | handleImagesDetail(response) {
133 | this.setState({
134 | loading: this.state.loading - 1,
135 | images: this.state.images.concat(response.data),
136 | })
137 |
138 | preloadImage(response.data.original_url)
139 | }
140 |
141 | fireImageRequests(imageFields) {
142 | const images = this.state.images
143 | const unfetchedImages = imageFields.filter(field => !images.some(image => image.id === field.value))
144 |
145 | if (unfetchedImages.length === 0) {
146 | return
147 | }
148 |
149 | this.incrementLoading(unfetchedImages.length)
150 | for (const field of unfetchedImages) {
151 | axios.get(`/api/v1/images/${field.value}/`)
152 | .then(this.handleImagesDetail.bind(this))
153 | }
154 | }
155 |
156 | incrementLoading(count = 1) {
157 | this.setState({
158 | loading: this.state.loading + count,
159 | })
160 | }
161 |
162 | render() {
163 | const { children } = this.props
164 | const childProps = {
165 | loading: this.state.loading,
166 | handlePagesIndex: this.handlePagesIndex.bind(this),
167 | handlePagesDetail: this.handlePagesDetail.bind(this),
168 | handleImagesIndex: this.handleImagesIndex.bind(this),
169 | handleImagesDetail: this.handleImagesDetail.bind(this),
170 | incrementLoading: this.incrementLoading.bind(this),
171 | pages: this.state.pages,
172 | images: this.state.images,
173 | }
174 |
175 | return (
176 |
177 | {React.Children.map(children, child => cloneElement(child, childProps))}
178 |
179 | )
180 | }
181 | }
182 |
183 | export default App
184 |
--------------------------------------------------------------------------------
/deck/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
257 |
--------------------------------------------------------------------------------