Browse Source

Implement film panel logic (#562)

Film Strips are similar to carousels, but with 'chained' content. Instead of a single object being shown at a time, multiple film panels are on screen and slide past with the press of a button.

Closes #462

---------

Co-authored-by: Vince Salvino <salvino@coderedcorp.com>
Jeremy Childers 2 years ago
parent
commit
849f1f4a96

+ 2 - 0
coderedcms/blocks/__init__.py

@@ -41,6 +41,7 @@ from .content_blocks import (  # noqa
     CardBlock,
     CarouselBlock,
     ContentWallBlock,
+    FilmStripBlock,
     ImageGalleryBlock,
     ModalBlock,
     NavDocumentLinkWithSubLinkBlock,
@@ -89,6 +90,7 @@ CONTENT_STREAMBLOCKS = HTML_STREAMBLOCKS + [
     ("accordion", AccordionBlock()),
     ("card", CardBlock()),
     ("carousel", CarouselBlock()),
+    ("film_strip", FilmStripBlock()),
     ("image_gallery", ImageGalleryBlock()),
     ("modal", ModalBlock(HTML_STREAMBLOCKS)),
     ("pricelist", PriceListBlock()),

+ 14 - 1
coderedcms/blocks/content_blocks.py

@@ -80,6 +80,19 @@ class CarouselBlock(BaseBlock):
         template = "coderedcms/blocks/carousel_block.html"
 
 
+class FilmStripBlock(BaseBlock):
+    """
+    Enables choosing a Film Strip Snippet.
+    """
+
+    film_strip = SnippetChooserBlock("coderedcms.FilmStrip")
+
+    class Meta:
+        icon = "image"
+        label = _("Film Strip")
+        template = "coderedcms/blocks/film_strip_block.html"
+
+
 class ImageGalleryBlock(BaseBlock):
     """
     Show a collection of images with interactive previews that expand to
@@ -204,7 +217,7 @@ class NavSubLinkBlock(BaseBlock):
 
 class NavExternalLinkWithSubLinkBlock(NavSubLinkBlock, NavExternalLinkBlock):
     """
-    Extermal link with option for sub-links.
+    External link with option for sub-links.
     """
 
     class Meta:

+ 113 - 0
coderedcms/migrations/0036_filmstrip_filmpanel.py

@@ -0,0 +1,113 @@
+# Generated by Django 4.1.7 on 2023-03-30 19:14
+
+import coderedcms.fields
+from django.db import migrations, models
+import django.db.models.deletion
+import modelcluster.fields
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("wagtailimages", "0025_alter_image_file_alter_rendition_file"),
+        ("coderedcms", "0035_remove_googleapisettings_site_and_more"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="FilmStrip",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=255, verbose_name="Name")),
+            ],
+            options={
+                "verbose_name": "Film Strip",
+            },
+        ),
+        migrations.CreateModel(
+            name="FilmPanel",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "sort_order",
+                    models.IntegerField(blank=True, editable=False, null=True),
+                ),
+                (
+                    "background_color",
+                    models.CharField(
+                        blank=True,
+                        help_text="Hexadecimal, rgba, or CSS color notation (e.g. #ff0011)",
+                        max_length=255,
+                        verbose_name="Background color",
+                    ),
+                ),
+                (
+                    "foreground_color",
+                    models.CharField(
+                        blank=True,
+                        help_text="Hexadecimal, rgba, or CSS color notation (e.g. #ff0011)",
+                        max_length=255,
+                        verbose_name="Text color",
+                    ),
+                ),
+                (
+                    "custom_css_class",
+                    models.CharField(
+                        blank=True,
+                        max_length=255,
+                        verbose_name="Custom CSS class",
+                    ),
+                ),
+                (
+                    "custom_id",
+                    models.CharField(
+                        blank=True, max_length=255, verbose_name="Custom ID"
+                    ),
+                ),
+                (
+                    "content",
+                    coderedcms.fields.CoderedStreamField(
+                        blank=True, use_json_field=True
+                    ),
+                ),
+                (
+                    "background_image",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.SET_NULL,
+                        related_name="+",
+                        to="wagtailimages.image",
+                        verbose_name="Background image",
+                    ),
+                ),
+                (
+                    "film_strip",
+                    modelcluster.fields.ParentalKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="film_panels",
+                        to="coderedcms.filmstrip",
+                        verbose_name="Film Panel",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "Film Panel",
+            },
+        ),
+    ]

+ 74 - 0
coderedcms/models/snippet_models.py

@@ -216,6 +216,80 @@ class ClassifierTerm(Orderable, models.Model):
         return "{0} > {1}".format(self.classifier.name, self.name)
 
 
+@register_snippet
+class FilmStrip(ClusterableModel):
+    class Meta:
+        verbose_name = _("Film Strip")
+
+    name = models.CharField(
+        max_length=255,
+        verbose_name=_("Name"),
+    )
+
+    panels = [
+        FieldPanel("name"),
+        InlinePanel("film_panels", label=_("Panels")),
+    ]
+
+    def __str__(self):
+        return self.name
+
+
+class FilmPanel(Orderable, models.Model):
+    class Meta:
+        verbose_name = _("Film Panel")
+
+    film_strip = ParentalKey(
+        FilmStrip,
+        related_name="film_panels",
+        verbose_name=_("Film Panel"),
+    )
+    background_image = models.ForeignKey(
+        get_image_model_string(),
+        null=True,
+        blank=True,
+        on_delete=models.SET_NULL,
+        related_name="+",
+        verbose_name=_("Background image"),
+    )
+    background_color = models.CharField(
+        max_length=255,
+        blank=True,
+        verbose_name=_("Background color"),
+        help_text=_("Hexadecimal, rgba, or CSS color notation (e.g. #ff0011)"),
+    )
+    foreground_color = models.CharField(
+        max_length=255,
+        blank=True,
+        verbose_name=_("Text color"),
+        help_text=_("Hexadecimal, rgba, or CSS color notation (e.g. #ff0011)"),
+    )
+    custom_css_class = models.CharField(
+        max_length=255,
+        blank=True,
+        verbose_name=_("Custom CSS class"),
+    )
+    custom_id = models.CharField(
+        max_length=255,
+        blank=True,
+        verbose_name=_("Custom ID"),
+    )
+    content = CoderedStreamField(
+        HTML_STREAMBLOCKS,
+        blank=True,
+        use_json_field=True,
+    )
+
+    panels = [
+        FieldPanel("background_image"),
+        FieldPanel("background_color"),
+        FieldPanel("foreground_color"),
+        FieldPanel("custom_css_class"),
+        FieldPanel("custom_id"),
+        FieldPanel("content"),
+    ]
+
+
 @register_snippet
 class Navbar(models.Model):
     """

File diff suppressed because it is too large
+ 4 - 6
coderedcms/project_template/basic/website/migrations/0001_initial.py


File diff suppressed because it is too large
+ 4 - 6
coderedcms/project_template/sass/website/migrations/0001_initial.py


+ 17 - 0
coderedcms/static/coderedcms/css/crx-front.css

@@ -36,6 +36,23 @@ License: https://github.com/coderedcorp/coderedcms/blob/dev/LICENSE
   height: 500px;
 }
 
+.crx-filmstrip-container {
+  overflow: hidden;
+}
+
+@media (hover: none) {
+  .crx-filmstrip-container {
+    overflow-x: scroll;
+  }
+}
+
+.crx-filmstrip-panel {
+  min-width: 300px;
+  min-height: 300px;
+  padding: 40px;
+  text-align: center;
+}
+
 .modal-lightbox {
   max-width: 100vw;
   text-align: center;

File diff suppressed because it is too large
+ 1 - 0
coderedcms/static/coderedcms/css/crx-front.css.map


File diff suppressed because it is too large
+ 0 - 0
coderedcms/static/coderedcms/css/crx-front.min.css


+ 32 - 1
coderedcms/static/coderedcms/js/crx-front.js

@@ -132,6 +132,37 @@ document.addEventListener("DOMContentLoaded", function () {
   if (document.querySelectorAll("[data-masonry]").length > 0) {
     load_script(libs.masonry);
   }
-});
 
+  /** Film Strip Controls **/
+  let strips = document.querySelectorAll("[data-block='film-strip']");
+  strips.forEach((el) => {
+    const leftButton = el.querySelector("[data-button='left']");
+    const rightButton = el.querySelector("[data-button='right']");
+    const container = el.querySelector("[data-block='film-container']");
+
+    leftButton.addEventListener("click", function () {
+      const panels = el.querySelectorAll("[data-block='film-panel']");
+      let currentBlock = parseInt(el.dataset.currentBlock) - 1;
+      if (currentBlock < 0) currentBlock = panels.length - 1;
+      el.dataset.currentBlock = currentBlock;
+
+      const elem = panels[currentBlock];
+      const left = elem.offsetLeft;
+
+      container.scroll({ top: 0, left: left, behavior: "smooth" });
+    });
+
+    rightButton.addEventListener("click", function () {
+      const panels = el.querySelectorAll("[data-block='film-panel']");
+      let currentBlock = parseInt(el.dataset.currentBlock) + 1;
+      if (currentBlock >= panels.length) currentBlock = 0;
+      el.dataset.currentBlock = currentBlock;
+
+      const elem = panels[currentBlock];
+      const left = elem.offsetLeft;
+
+      container.scroll({ top: 0, left: left, behavior: "smooth" });
+    });
+  });
+});
 /* @license-end */

+ 15 - 0
coderedcms/static/coderedcms/scss/_crx-filmstrip.scss

@@ -0,0 +1,15 @@
+.crx-filmstrip-container {
+  overflow: hidden;
+  // If device does not support hover, it is most likely a touchscreen device.
+  // In that case, allow manual scrolling.
+  @media (hover: none) {
+    overflow-x: scroll;
+  }
+}
+
+.crx-filmstrip-panel {
+  min-width: 300px;
+  min-height: 300px;
+  padding: 40px;
+  text-align: center;
+}

+ 1 - 0
coderedcms/static/coderedcms/scss/crx-front.scss

@@ -9,6 +9,7 @@ License: https://github.com/coderedcorp/coderedcms/blob/dev/LICENSE
 
 @import "crx-article";
 @import "crx-bs-overrides";
+@import "crx-filmstrip";
 @import "crx-gallery";
 @import "crx-hero";
 @import "crx-location";

+ 32 - 0
coderedcms/templates/coderedcms/blocks/film_strip_block.html

@@ -0,0 +1,32 @@
+{% extends "coderedcms/blocks/base_block.html" %}
+{% load i18n wagtailcore_tags wagtailimages_tags %}
+{% block block_render %}
+{% with value=self.film_strip %}
+<div class="position-relative" data-block="film-strip" data-current-block='0'>
+  <div class="crx-filmstrip-container" data-block="film-container">
+    <div class="row g-0 flex-nowrap" data-block="film-row">
+      {% for panel in value.film_panels.all %}
+        {% image panel.background_image original as image %}
+        <div data-block="film-panel" class="col-auto col crx-filmstrip-panel {{panel.custom_css_class}}" {% if panel.custom_id %}id="{{panel.custom_id}}"{% endif %} style="
+          {% if image %}background-image: url({{image.url}}); background-size: cover; background-position: center;{% endif %}
+          {% if panel.background_color %}background-color: {{panel.background_color}}{% endif %}
+          {% if panel.foreground_color %}color: {{panel.foreground_color}}{% endif %}
+        ">
+          {% include_block panel.content %}
+        </div>
+        {% endfor %}
+    </div>
+  </div>
+  {% block film_strip_buttons %}
+  <div class="row justify-content-between">
+    <button type="button" class="btn col-auto" data-button="left">
+      ← {% trans 'Previous' %}
+    </button>
+    <button type="button" class="btn col-auto" data-button="right">
+      {% trans 'Next' %} →
+    </button>
+  </div>
+  {% endblock film_strip_buttons %}
+</div>
+{% endwith %}
+{% endblock %}

+ 14 - 0
docs/features/blocks/contentblocks/filmstrip.rst

@@ -0,0 +1,14 @@
+Film Strip Block
+================
+
+Allows the user to choose a long horizontal row of scrollable panels.
+
+
+Field Reference
+---------------
+
+Fields and purposes:
+
+* **Film Strip** - Choose a :doc:`/features/snippets/filmstrip` snippet.
+
+If you don't have any film strips already made, you can build a carousel by clicking **Choose a Film Strip** and clicking on "Why not **create one now**?" in the popup box. This will take you to **Snippets > Film Strips** where you can create a carousel to add to the page.

+ 2 - 2
docs/features/blocks/contentblocks/index.rst

@@ -14,6 +14,7 @@ Content blocks are the blocks for adding various types of content to your site.
     carousel
     download
     embedmedia
+    filmstrip
     googlemap
     html
     image
@@ -23,8 +24,7 @@ Content blocks are the blocks for adding various types of content to your site.
     modal
     pagepreview
     pricelist
-    reusablecontent
     quote
+    reusablecontent
     table
     text
-

+ 34 - 0
docs/features/snippets/filmstrip.rst

@@ -0,0 +1,34 @@
+Film Strip
+==========
+
+A long horizontal row of scrollable panels, similar to a reel of film.
+
+.. figure:: img/filmstrip_block.png
+   :alt:  The film strip block, with four panels (three fully visible).
+
+   The film strip block, with four panels (three fully visible).
+
+
+Usage
+-----
+
+You define your film strips and their panels in the **Snippets > Film Strips** section of the admin.  Once defined, any page with a body streamfield can show that film strip by selecting it with a :doc:`/features/blocks/contentblocks/filmstrip`.
+
+Fields
+------
+
+Slider
+~~~~~~
+**Name**: A unique name for your film strip.  It can be anything, it is just used as a personal reference to easily tell them apart within Wagtail.
+
+Panels
+~~~~~~
+
+To add a slide to your carousel, click the "Add Slides" button.
+
+**Background image**: An optional image that will fill the entire panel. May be cropped or scaled to fit.
+**Background color**: An optional color that will fill the entire panel.
+**Foreground color**: An optional color used for text in the panel.
+**Custom CSS class**: If you need to add a specific CSS class for this panel, add it here.
+**Custom ID**: If you need to add a specific ID for this panel, add it here.
+**Content**: A streamfield that contains the content blocks for the panel.

BIN
docs/features/snippets/img/filmstrip_block.png


+ 1 - 0
docs/features/snippets/index.rst

@@ -11,6 +11,7 @@ functionality.
     carousels
     classifiers
     content_walls
+    filmstrip
     footers
     navigation_bars
     reusable_content

Some files were not shown because too many files changed in this diff