django-image-gallery 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. django_image_gallery-0.1.0/LICENSE +21 -0
  2. django_image_gallery-0.1.0/PKG-INFO +137 -0
  3. django_image_gallery-0.1.0/README.md +111 -0
  4. django_image_gallery-0.1.0/THIRD_PARTY_NOTICES.md +28 -0
  5. django_image_gallery-0.1.0/django_image_gallery/__init__.py +0 -0
  6. django_image_gallery-0.1.0/django_image_gallery/admin.py +13 -0
  7. django_image_gallery-0.1.0/django_image_gallery/apps.py +6 -0
  8. django_image_gallery-0.1.0/django_image_gallery/migrations/0001_initial.py +29 -0
  9. django_image_gallery-0.1.0/django_image_gallery/migrations/__init__.py +0 -0
  10. django_image_gallery-0.1.0/django_image_gallery/models.py +29 -0
  11. django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/css/gallery.css +173 -0
  12. django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/css/masonry.css +14 -0
  13. django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/js/gallery.js +184 -0
  14. django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/js/imagesloaded.pkgd.min.js +12 -0
  15. django_image_gallery-0.1.0/django_image_gallery/static/django_image_gallery/js/masonry.pkgd.min.js +9 -0
  16. django_image_gallery-0.1.0/django_image_gallery/templates/django_image_gallery/gallery.html +64 -0
  17. django_image_gallery-0.1.0/django_image_gallery/templatetags/__init__.py +0 -0
  18. django_image_gallery-0.1.0/django_image_gallery/templatetags/django_image_gallery.py +11 -0
  19. django_image_gallery-0.1.0/django_image_gallery/tests.py +106 -0
  20. django_image_gallery-0.1.0/django_image_gallery/views.py +3 -0
  21. django_image_gallery-0.1.0/django_image_gallery.egg-info/PKG-INFO +137 -0
  22. django_image_gallery-0.1.0/django_image_gallery.egg-info/SOURCES.txt +25 -0
  23. django_image_gallery-0.1.0/django_image_gallery.egg-info/dependency_links.txt +1 -0
  24. django_image_gallery-0.1.0/django_image_gallery.egg-info/requires.txt +2 -0
  25. django_image_gallery-0.1.0/django_image_gallery.egg-info/top_level.txt +1 -0
  26. django_image_gallery-0.1.0/pyproject.toml +50 -0
  27. django_image_gallery-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Towe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-image-gallery
3
+ Version: 0.1.0
4
+ Summary: A Django image gallery application.
5
+ Author: Alexander Towe
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/alextowe/django-image-gallery
8
+ Project-URL: Repository, https://github.com/alextowe/django-image-gallery
9
+ Project-URL: Issues, https://github.com/alextowe/django-image-gallery/issues
10
+ Classifier: Framework :: Django
11
+ Classifier: Framework :: Django :: 5.2
12
+ Classifier: Framework :: Django :: 6.0
13
+ Classifier: Framework :: Django :: 6.1
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ License-File: THIRD_PARTY_NOTICES.md
23
+ Requires-Dist: Django<7.0,>=5.2
24
+ Requires-Dist: Pillow>=10.0
25
+ Dynamic: license-file
26
+
27
+ # Django Image Gallery
28
+
29
+ An image gallery application for Django projects. Provides an easy way to manage, organize, and display images through the Django admin interface and a reusable template tag.
30
+
31
+ ## Features
32
+
33
+ - Django model for managing gallery images
34
+ - Automatic resizing of uploaded images exceeding 2000 × 2000 pixels
35
+ - Published and unpublished image management
36
+ - Configurable image ordering
37
+ - Masonry image layout
38
+ - Load-more functionality
39
+ - Full-screen image viewer
40
+ - Django admin integration
41
+ - Reusable template tag
42
+
43
+ ## Requirements
44
+
45
+ - Python 3.10+
46
+ - Django 5.2, 6.0, or 6.1
47
+ - Pillow 10.0 or later
48
+
49
+ ## Installation
50
+
51
+ Install the package using pip:
52
+
53
+ ```bash
54
+ pip install django-image-gallery
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ Add `django_image_gallery` to your project's `INSTALLED_APPS`:
60
+
61
+ ```python
62
+ INSTALLED_APPS = [
63
+ # ...
64
+ "django_image_gallery",
65
+ ]
66
+ ```
67
+
68
+ Run the database migrations:
69
+
70
+ ```bash
71
+ python manage.py migrate
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ Load the template tag library and include the gallery wherever you want it displayed:
77
+
78
+ ```django
79
+ {% load django_image_gallery %}
80
+
81
+ {% gallery %}
82
+ ```
83
+
84
+ The template tag retrieves all published gallery images and renders them using the package's included gallery template.
85
+
86
+ ## Media Configuration
87
+
88
+ Configure Django to handle uploaded media files:
89
+
90
+ ```python
91
+ MEDIA_URL = "/media/"
92
+ MEDIA_ROOT = BASE_DIR / "media"
93
+ ```
94
+
95
+ During local development, you can serve media files through Django by adding the following to your project's root `urls.py`:
96
+
97
+ ```python
98
+ from django.conf import settings
99
+ from django.conf.urls.static import static
100
+
101
+ urlpatterns = [
102
+ # Your URL patterns
103
+ ]
104
+
105
+ if settings.DEBUG:
106
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
107
+ ```
108
+
109
+ For production deployments, configure your web server or storage backend to serve uploaded media files. Uploaded images must be accessible through the configured media storage.
110
+
111
+ ## Managing Images
112
+
113
+ Images can be added and managed through the Django admin interface.
114
+
115
+ After creating a superuser:
116
+
117
+ ```bash
118
+ python manage.py createsuperuser
119
+ ```
120
+
121
+ Log in to the admin interface and locate **Gallery Images**.
122
+
123
+ Each image supports the following fields:
124
+
125
+ | Field | Description |
126
+ |---|---|
127
+ | Title | Optional image title |
128
+ | Image | Uploaded image file |
129
+ | Caption | Optional image caption |
130
+ | Sort order | Controls the display order of images |
131
+ | Published | Determines whether the image appears in the gallery |
132
+
133
+ Only published images appear in the gallery. Images are ordered by `sort_order`, with newer entries taking precedence when sort orders match.
134
+
135
+ ## License
136
+
137
+ This project is licensed under the MIT License. See the `LICENSE` file for details.
@@ -0,0 +1,111 @@
1
+ # Django Image Gallery
2
+
3
+ An image gallery application for Django projects. Provides an easy way to manage, organize, and display images through the Django admin interface and a reusable template tag.
4
+
5
+ ## Features
6
+
7
+ - Django model for managing gallery images
8
+ - Automatic resizing of uploaded images exceeding 2000 × 2000 pixels
9
+ - Published and unpublished image management
10
+ - Configurable image ordering
11
+ - Masonry image layout
12
+ - Load-more functionality
13
+ - Full-screen image viewer
14
+ - Django admin integration
15
+ - Reusable template tag
16
+
17
+ ## Requirements
18
+
19
+ - Python 3.10+
20
+ - Django 5.2, 6.0, or 6.1
21
+ - Pillow 10.0 or later
22
+
23
+ ## Installation
24
+
25
+ Install the package using pip:
26
+
27
+ ```bash
28
+ pip install django-image-gallery
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ Add `django_image_gallery` to your project's `INSTALLED_APPS`:
34
+
35
+ ```python
36
+ INSTALLED_APPS = [
37
+ # ...
38
+ "django_image_gallery",
39
+ ]
40
+ ```
41
+
42
+ Run the database migrations:
43
+
44
+ ```bash
45
+ python manage.py migrate
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ Load the template tag library and include the gallery wherever you want it displayed:
51
+
52
+ ```django
53
+ {% load django_image_gallery %}
54
+
55
+ {% gallery %}
56
+ ```
57
+
58
+ The template tag retrieves all published gallery images and renders them using the package's included gallery template.
59
+
60
+ ## Media Configuration
61
+
62
+ Configure Django to handle uploaded media files:
63
+
64
+ ```python
65
+ MEDIA_URL = "/media/"
66
+ MEDIA_ROOT = BASE_DIR / "media"
67
+ ```
68
+
69
+ During local development, you can serve media files through Django by adding the following to your project's root `urls.py`:
70
+
71
+ ```python
72
+ from django.conf import settings
73
+ from django.conf.urls.static import static
74
+
75
+ urlpatterns = [
76
+ # Your URL patterns
77
+ ]
78
+
79
+ if settings.DEBUG:
80
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
81
+ ```
82
+
83
+ For production deployments, configure your web server or storage backend to serve uploaded media files. Uploaded images must be accessible through the configured media storage.
84
+
85
+ ## Managing Images
86
+
87
+ Images can be added and managed through the Django admin interface.
88
+
89
+ After creating a superuser:
90
+
91
+ ```bash
92
+ python manage.py createsuperuser
93
+ ```
94
+
95
+ Log in to the admin interface and locate **Gallery Images**.
96
+
97
+ Each image supports the following fields:
98
+
99
+ | Field | Description |
100
+ |---|---|
101
+ | Title | Optional image title |
102
+ | Image | Uploaded image file |
103
+ | Caption | Optional image caption |
104
+ | Sort order | Controls the display order of images |
105
+ | Published | Determines whether the image appears in the gallery |
106
+
107
+ Only published images appear in the gallery. Images are ordered by `sort_order`, with newer entries taking precedence when sort orders match.
108
+
109
+ ## License
110
+
111
+ This project is licensed under the MIT License. See the `LICENSE` file for details.
@@ -0,0 +1,28 @@
1
+ # Third-Party Notices
2
+
3
+ This package includes the following third-party JavaScript libraries. Their original license notices are retained in the distributed JavaScript files.
4
+
5
+ ## ImagesLoaded
6
+
7
+ - **Version:** 5.0.0
8
+ - **License:** MIT License
9
+ - **Project:** https://imagesloaded.desandro.com/
10
+ - **Source:** https://github.com/desandro/imagesloaded
11
+
12
+ The packaged distribution includes EvEmitter code. Its original MIT license notice is retained in the distribution.
13
+
14
+ ## Masonry
15
+
16
+ - **Version:** 4.2.2
17
+ - **Author:** David DeSandro
18
+ - **License:** MIT License
19
+ - **Project:** https://masonry.desandro.com/
20
+ - **Source:** https://github.com/desandro/masonry
21
+
22
+ The packaged distribution includes supporting libraries, including EvEmitter, get-size, desandro-matches-selector, fizzy-ui-utils, Outlayer, and jquery-bridget. Their original code is distributed as part of the Masonry packaged build.
23
+
24
+ ## License information
25
+
26
+ The third-party libraries listed above are distributed under the MIT License. Their respective authors and copyright holders retain ownership of their work.
27
+
28
+ The original license notices included in the bundled JavaScript files are preserved.
@@ -0,0 +1,13 @@
1
+ from django.contrib import admin
2
+ from .models import GalleryImage
3
+
4
+
5
+ @admin.register(GalleryImage)
6
+ class GalleryImageAdmin(admin.ModelAdmin):
7
+ list_display = (
8
+ "title",
9
+ "image",
10
+ "caption",
11
+ "sort_order",
12
+ "is_published",
13
+ )
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjangoImageGalleryConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "django_image_gallery"
@@ -0,0 +1,29 @@
1
+ # Generated by Django 6.0.5 on 2026-10-03 19:33
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ initial = True
9
+
10
+ dependencies = [
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name='GalleryImage',
16
+ fields=[
17
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+ ('title', models.CharField(blank=True, max_length=200)),
19
+ ('image', models.ImageField(upload_to='gallery/')),
20
+ ('caption', models.TextField(blank=True)),
21
+ ('sort_order', models.PositiveIntegerField(default=0)),
22
+ ('is_published', models.BooleanField(default=False)),
23
+ ],
24
+ options={
25
+ 'verbose_name': 'Gallery Image',
26
+ 'ordering': ['sort_order', '-id'],
27
+ },
28
+ ),
29
+ ]
@@ -0,0 +1,29 @@
1
+ from PIL import Image
2
+ from django.db import models
3
+
4
+
5
+ class GalleryImage(models.Model):
6
+ title = models.CharField(max_length=200, blank=True)
7
+ image = models.ImageField(upload_to="gallery/")
8
+ caption = models.TextField(blank=True)
9
+ sort_order = models.PositiveIntegerField(default=0)
10
+ is_published = models.BooleanField(default=False)
11
+
12
+ def save(self, *args, **kwargs):
13
+ super().save(*args, **kwargs)
14
+
15
+ img = Image.open(self.image.path)
16
+
17
+ max_size = (2000, 2000)
18
+
19
+ if img.width > max_size[0] or img.height > max_size[1]:
20
+ img.thumbnail(max_size)
21
+
22
+ img.save(self.image.path, quality=85, optimize=True)
23
+
24
+ class Meta:
25
+ ordering = ["sort_order", "-id"]
26
+ verbose_name = "Gallery Image"
27
+
28
+ def __str__(self):
29
+ return self.title or f"Image {self.id}"
@@ -0,0 +1,173 @@
1
+ #image-gallery-modal {
2
+ position: fixed;
3
+ inset: 0;
4
+ z-index: 1000;
5
+ display: flex;
6
+ align-items: center;
7
+ justify-content: center;
8
+ background: rgba(0, 0, 0, 0.85);
9
+ opacity: 0;
10
+ visibility: hidden;
11
+ pointer-events: none;
12
+ transition: opacity 0.5s ease, visibility 0.5s ease;
13
+ }
14
+
15
+ #image-gallery-modal.is-open {
16
+ opacity: 1;
17
+ visibility: visible;
18
+ pointer-events: auto;
19
+ }
20
+
21
+ .image-gallery-modal-content {
22
+ position: relative;
23
+ width: 100%;
24
+ height: 100%;
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ pointer-events: none;
29
+ }
30
+
31
+ .gallery-modal-image,
32
+ .gallery-close,
33
+ .gallery-prev,
34
+ .gallery-next {
35
+ pointer-events: auto;
36
+ }
37
+
38
+ .gallery-modal-image {
39
+ position: absolute;
40
+ max-width: 90vw;
41
+ max-height: 90vh;
42
+ object-fit: contain;
43
+ }
44
+
45
+ .gallery-modal-image[hidden] {
46
+ display: none;
47
+ }
48
+
49
+ .gallery-slide-in-right {
50
+ animation: gallery-slide-in-right 0.5s ease;
51
+ }
52
+
53
+ .gallery-slide-in-left {
54
+ animation: gallery-slide-in-left 0.5s ease;
55
+ }
56
+
57
+ @keyframes gallery-slide-in-right {
58
+ from {
59
+ transform: translateX(100%);
60
+ }
61
+
62
+ to {
63
+ transform: translateX(0);
64
+ }
65
+ }
66
+
67
+ @keyframes gallery-slide-in-left {
68
+ from {
69
+ transform: translateX(-100%);
70
+ }
71
+
72
+ to {
73
+ transform: translateX(0);
74
+ }
75
+ }
76
+
77
+ .gallery-close,
78
+ .gallery-prev,
79
+ .gallery-next {
80
+ position: absolute;
81
+ border: 0;
82
+ background: transparent;
83
+ color: white;
84
+ cursor: pointer;
85
+ z-index: 10;
86
+ }
87
+
88
+ .gallery-close {
89
+ top: 20px;
90
+ right: 25px;
91
+ font-size: 2rem;
92
+ line-height: 1;
93
+ }
94
+
95
+ .gallery-prev,
96
+ .gallery-next {
97
+ top: 50%;
98
+ transform: translateY(-50%);
99
+ font-size: 2.5rem;
100
+ padding: 1rem;
101
+ }
102
+
103
+ .gallery-prev {
104
+ left: 20px;
105
+ }
106
+
107
+ .gallery-next {
108
+ right: 20px;
109
+ }
110
+
111
+ .image-gallery-controls {
112
+ display: flex;
113
+ justify-content: center;
114
+ gap: 1rem;
115
+ margin-top: 1.5rem;
116
+ }
117
+
118
+ .image-gallery-controls button {
119
+ display: inline-block;
120
+ border: 1px solid #fff;
121
+ border-radius: 0;
122
+ background: #fff;
123
+ color: #0f172a;
124
+ font-family: inherit;
125
+ font-size: 1rem;
126
+ font-weight: 500;
127
+ letter-spacing: 0.1em;
128
+ line-height: 1.5;
129
+ padding: 0.375rem 3rem;
130
+ text-align: center;
131
+ cursor: pointer;
132
+ transition: background-color 0.15s ease-in-out,
133
+ border-color 0.15s ease-in-out,
134
+ box-shadow 0.15s ease-in-out;
135
+ }
136
+
137
+ .image-gallery-controls button:hover {
138
+ background: #cbd5e1;
139
+ border-color: #cbd5e1;
140
+ }
141
+
142
+ .image-gallery-controls button:focus-visible {
143
+ outline: 0;
144
+ box-shadow: 0 0 0 0.25rem rgb(207 207 207 / 50%);
145
+ }
146
+
147
+ .image-gallery-controls button:active {
148
+ background: #cbd5e1;
149
+ border-color: #cbd5e1;
150
+ }
151
+
152
+ .image-gallery-controls button:disabled {
153
+ opacity: 0.65;
154
+ pointer-events: none;
155
+ }
156
+
157
+ body.image-gallery-open {
158
+ overflow: hidden;
159
+ }
160
+
161
+ .gallery-slide {
162
+ position: relative;
163
+ width: 100%;
164
+ height: 100%;
165
+ overflow: hidden;
166
+ display: flex;
167
+ align-items: center;
168
+ justify-content: center;
169
+ }
170
+
171
+ #image-gallery-modal {
172
+ touch-action: none;
173
+ }
@@ -0,0 +1,14 @@
1
+ #image-gallery .gallery-grid {
2
+ width: 100%;
3
+ }
4
+
5
+ #image-gallery .gallery-item {
6
+ width: 50%;
7
+ padding: 0.5rem;
8
+ }
9
+
10
+ #image-gallery .gallery-image {
11
+ width: 100%;
12
+ display: block;
13
+ border-radius: 0.25rem;
14
+ }
@@ -0,0 +1,184 @@
1
+ const gallery = document.getElementById("image-gallery");
2
+ const galleryGrid = gallery?.querySelector(".gallery-grid");
3
+
4
+ if (galleryGrid) {
5
+ imagesLoaded(galleryGrid, function () {
6
+ galleryGrid.masonry = new Masonry(galleryGrid, {
7
+ itemSelector: ".gallery-item",
8
+ columnWidth: ".gallery-item",
9
+ percentPosition: true,
10
+ });
11
+ });
12
+ }
13
+
14
+ const galleryModal = document.getElementById("image-gallery-modal");
15
+ const modalImages = galleryModal?.querySelectorAll(".gallery-modal-image");
16
+ const closeButton = galleryModal?.querySelector(".gallery-close");
17
+ const previousButton = galleryModal?.querySelector(".gallery-prev");
18
+ const nextButton = galleryModal?.querySelector(".gallery-next");
19
+
20
+ let currentIndex = 0;
21
+ let modalHideTimeout = null;
22
+
23
+ function showGalleryImage(index, direction = 1) {
24
+ if (!modalImages?.length) {
25
+ return;
26
+ }
27
+
28
+ if (index < 0) {
29
+ index = modalImages.length - 1;
30
+ }
31
+
32
+ if (index >= modalImages.length) {
33
+ index = 0;
34
+ }
35
+
36
+ modalImages.forEach(function (image) {
37
+ image.hidden = true;
38
+ image.classList.remove("gallery-slide-in-left", "gallery-slide-in-right");
39
+ });
40
+
41
+ const image = modalImages[index];
42
+
43
+ image.hidden = false;
44
+
45
+ image.classList.add(
46
+ direction === 1
47
+ ? "gallery-slide-in-right"
48
+ : "gallery-slide-in-left"
49
+ );
50
+
51
+ currentIndex = index;
52
+ }
53
+
54
+ function openGallery(index) {
55
+ if (!galleryModal) {
56
+ return;
57
+ }
58
+
59
+ if (modalHideTimeout) {
60
+ clearTimeout(modalHideTimeout);
61
+ modalHideTimeout = null;
62
+ }
63
+
64
+ showGalleryImage(index);
65
+
66
+ galleryModal.hidden = false;
67
+
68
+ requestAnimationFrame(function () {
69
+ galleryModal.classList.add("is-open");
70
+ });
71
+
72
+ galleryModal.setAttribute("aria-hidden", "false");
73
+ document.body.classList.add("image-gallery-open");
74
+
75
+ galleryModal.focus();
76
+ }
77
+
78
+ function closeGallery() {
79
+ if (!galleryModal) {
80
+ return;
81
+ }
82
+
83
+ galleryModal.classList.remove("is-open");
84
+ galleryModal.setAttribute("aria-hidden", "true");
85
+ document.body.classList.remove("image-gallery-open");
86
+
87
+ modalHideTimeout = setTimeout(function () {
88
+ galleryModal.hidden = true;
89
+ modalHideTimeout = null;
90
+ }, 500);
91
+ }
92
+
93
+ gallery?.addEventListener("click", function (event) {
94
+ const image = event.target.closest(".gallery-image");
95
+
96
+ if (!image) {
97
+ return;
98
+ }
99
+
100
+ openGallery(parseInt(image.dataset.index, 10));
101
+ });
102
+
103
+ closeButton?.addEventListener("click", closeGallery);
104
+
105
+ previousButton?.addEventListener("click", function (event) {
106
+ event.preventDefault();
107
+ showGalleryImage(currentIndex - 1, -1);
108
+ });
109
+
110
+ nextButton?.addEventListener("click", function (event) {
111
+ event.preventDefault();
112
+ showGalleryImage(currentIndex + 1, 1);
113
+ });
114
+
115
+ galleryModal?.addEventListener("click", function (event) {
116
+ if (!event.target.closest(".image-gallery-modal-content")) {
117
+ closeGallery();
118
+ }
119
+ });
120
+
121
+ window.addEventListener("keydown", function (event) {
122
+ if (!galleryModal || galleryModal.hidden) {
123
+ return;
124
+ }
125
+
126
+ if (event.key === "Escape") {
127
+ event.preventDefault();
128
+ closeGallery();
129
+ return;
130
+ }
131
+
132
+ if (event.key === "ArrowLeft") {
133
+ event.preventDefault();
134
+ event.stopPropagation();
135
+ showGalleryImage(currentIndex - 1, -1);
136
+ return;
137
+ }
138
+
139
+ if (event.key === "ArrowRight") {
140
+ event.preventDefault();
141
+ event.stopPropagation();
142
+ showGalleryImage(currentIndex + 1, 1);
143
+ return;
144
+ }
145
+ }, true);
146
+
147
+ const loadMoreButton = document.getElementById("image-gallery-load-more");
148
+ const hideButton = document.getElementById("image-gallery-hide");
149
+ const template = document.getElementById("image-gallery-template");
150
+
151
+ let loadedItems = [];
152
+
153
+ if (loadMoreButton) {
154
+ loadMoreButton.addEventListener("click", function () {
155
+ const fragment = template.content.cloneNode(true);
156
+ loadedItems = Array.from(fragment.children);
157
+
158
+ galleryGrid.appendChild(fragment);
159
+
160
+ imagesLoaded(loadedItems, function () {
161
+ galleryGrid.masonry.appended(loadedItems);
162
+ });
163
+
164
+ loadMoreButton.hidden = true;
165
+ hideButton.hidden = false;
166
+ });
167
+ }
168
+
169
+ if (hideButton) {
170
+ hideButton.addEventListener("click", function () {
171
+ galleryGrid.masonry.remove(loadedItems);
172
+ galleryGrid.masonry.layout();
173
+
174
+ loadedItems = [];
175
+
176
+ loadMoreButton.hidden = false;
177
+ hideButton.hidden = true;
178
+
179
+ gallery.scrollIntoView({
180
+ behavior: "smooth",
181
+ block: "start",
182
+ });
183
+ });
184
+ }
@@ -0,0 +1,12 @@
1
+ /*!
2
+ * imagesLoaded PACKAGED v5.0.0
3
+ * JavaScript is all like "You images are done yet or what?"
4
+ * MIT License
5
+ */
6
+ !function(t,e){"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,(function(){function t(){}let e=t.prototype;return e.on=function(t,e){if(!t||!e)return this;let i=this._events=this._events||{},s=i[t]=i[t]||[];return s.includes(e)||s.push(e),this},e.once=function(t,e){if(!t||!e)return this;this.on(t,e);let i=this._onceEvents=this._onceEvents||{};return(i[t]=i[t]||{})[e]=!0,this},e.off=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;let s=i.indexOf(e);return-1!=s&&i.splice(s,1),this},e.emitEvent=function(t,e){let i=this._events&&this._events[t];if(!i||!i.length)return this;i=i.slice(0),e=e||[];let s=this._onceEvents&&this._onceEvents[t];for(let n of i){s&&s[n]&&(this.off(t,n),delete s[n]),n.apply(this,e)}return this},e.allOff=function(){return delete this._events,delete this._onceEvents,this},t})),
7
+ /*!
8
+ * imagesLoaded v5.0.0
9
+ * JavaScript is all like "You images are done yet or what?"
10
+ * MIT License
11
+ */
12
+ function(t,e){"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter")):t.imagesLoaded=e(t,t.EvEmitter)}("undefined"!=typeof window?window:this,(function(t,e){let i=t.jQuery,s=t.console;function n(t,e,o){if(!(this instanceof n))return new n(t,e,o);let r=t;var h;("string"==typeof t&&(r=document.querySelectorAll(t)),r)?(this.elements=(h=r,Array.isArray(h)?h:"object"==typeof h&&"number"==typeof h.length?[...h]:[h]),this.options={},"function"==typeof e?o=e:Object.assign(this.options,e),o&&this.on("always",o),this.getImages(),i&&(this.jqDeferred=new i.Deferred),setTimeout(this.check.bind(this))):s.error(`Bad element for imagesLoaded ${r||t}`)}n.prototype=Object.create(e.prototype),n.prototype.getImages=function(){this.images=[],this.elements.forEach(this.addElementImages,this)};const o=[1,9,11];n.prototype.addElementImages=function(t){"IMG"===t.nodeName&&this.addImage(t),!0===this.options.background&&this.addElementBackgroundImages(t);let{nodeType:e}=t;if(!e||!o.includes(e))return;let i=t.querySelectorAll("img");for(let t of i)this.addImage(t);if("string"==typeof this.options.background){let e=t.querySelectorAll(this.options.background);for(let t of e)this.addElementBackgroundImages(t)}};const r=/url\((['"])?(.*?)\1\)/gi;function h(t){this.img=t}function d(t,e){this.url=t,this.element=e,this.img=new Image}return n.prototype.addElementBackgroundImages=function(t){let e=getComputedStyle(t);if(!e)return;let i=r.exec(e.backgroundImage);for(;null!==i;){let s=i&&i[2];s&&this.addBackground(s,t),i=r.exec(e.backgroundImage)}},n.prototype.addImage=function(t){let e=new h(t);this.images.push(e)},n.prototype.addBackground=function(t,e){let i=new d(t,e);this.images.push(i)},n.prototype.check=function(){if(this.progressedCount=0,this.hasAnyBroken=!1,!this.images.length)return void this.complete();let t=(t,e,i)=>{setTimeout((()=>{this.progress(t,e,i)}))};this.images.forEach((function(e){e.once("progress",t),e.check()}))},n.prototype.progress=function(t,e,i){this.progressedCount++,this.hasAnyBroken=this.hasAnyBroken||!t.isLoaded,this.emitEvent("progress",[this,t,e]),this.jqDeferred&&this.jqDeferred.notify&&this.jqDeferred.notify(this,t),this.progressedCount===this.images.length&&this.complete(),this.options.debug&&s&&s.log(`progress: ${i}`,t,e)},n.prototype.complete=function(){let t=this.hasAnyBroken?"fail":"done";if(this.isComplete=!0,this.emitEvent(t,[this]),this.emitEvent("always",[this]),this.jqDeferred){let t=this.hasAnyBroken?"reject":"resolve";this.jqDeferred[t](this)}},h.prototype=Object.create(e.prototype),h.prototype.check=function(){this.getIsImageComplete()?this.confirm(0!==this.img.naturalWidth,"naturalWidth"):(this.proxyImage=new Image,this.img.crossOrigin&&(this.proxyImage.crossOrigin=this.img.crossOrigin),this.proxyImage.addEventListener("load",this),this.proxyImage.addEventListener("error",this),this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.proxyImage.src=this.img.currentSrc||this.img.src)},h.prototype.getIsImageComplete=function(){return this.img.complete&&this.img.naturalWidth},h.prototype.confirm=function(t,e){this.isLoaded=t;let{parentNode:i}=this.img,s="PICTURE"===i.nodeName?i:this.img;this.emitEvent("progress",[this,s,e])},h.prototype.handleEvent=function(t){let e="on"+t.type;this[e]&&this[e](t)},h.prototype.onload=function(){this.confirm(!0,"onload"),this.unbindEvents()},h.prototype.onerror=function(){this.confirm(!1,"onerror"),this.unbindEvents()},h.prototype.unbindEvents=function(){this.proxyImage.removeEventListener("load",this),this.proxyImage.removeEventListener("error",this),this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype=Object.create(h.prototype),d.prototype.check=function(){this.img.addEventListener("load",this),this.img.addEventListener("error",this),this.img.src=this.url,this.getIsImageComplete()&&(this.confirm(0!==this.img.naturalWidth,"naturalWidth"),this.unbindEvents())},d.prototype.unbindEvents=function(){this.img.removeEventListener("load",this),this.img.removeEventListener("error",this)},d.prototype.confirm=function(t,e){this.isLoaded=t,this.emitEvent("progress",[this,this.element,e])},n.makeJQueryPlugin=function(e){(e=e||t.jQuery)&&(i=e,i.fn.imagesLoaded=function(t,e){return new n(this,t,e).jqDeferred.promise(i(this))})},n.makeJQueryPlugin(),n}));
@@ -0,0 +1,9 @@
1
+ /*!
2
+ * Masonry PACKAGED v4.2.2
3
+ * Cascading grid layout library
4
+ * https://masonry.desandro.com
5
+ * MIT License
6
+ * by David DeSandro
7
+ */
8
+
9
+ !function(t,e){"function"==typeof define&&define.amd?define("jquery-bridget/jquery-bridget",["jquery"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("jquery")):t.jQueryBridget=e(t,t.jQuery)}(window,function(t,e){"use strict";function i(i,r,a){function h(t,e,n){var o,r="$()."+i+'("'+e+'")';return t.each(function(t,h){var u=a.data(h,i);if(!u)return void s(i+" not initialized. Cannot call methods, i.e. "+r);var d=u[e];if(!d||"_"==e.charAt(0))return void s(r+" is not a valid method");var l=d.apply(u,n);o=void 0===o?l:o}),void 0!==o?o:t}function u(t,e){t.each(function(t,n){var o=a.data(n,i);o?(o.option(e),o._init()):(o=new r(n,e),a.data(n,i,o))})}a=a||e||t.jQuery,a&&(r.prototype.option||(r.prototype.option=function(t){a.isPlainObject(t)&&(this.options=a.extend(!0,this.options,t))}),a.fn[i]=function(t){if("string"==typeof t){var e=o.call(arguments,1);return h(this,t,e)}return u(this,t),this},n(a))}function n(t){!t||t&&t.bridget||(t.bridget=i)}var o=Array.prototype.slice,r=t.console,s="undefined"==typeof r?function(){}:function(t){r.error(t)};return n(e||t.jQuery),i}),function(t,e){"function"==typeof define&&define.amd?define("ev-emitter/ev-emitter",e):"object"==typeof module&&module.exports?module.exports=e():t.EvEmitter=e()}("undefined"!=typeof window?window:this,function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var i=this._events=this._events||{},n=i[t]=i[t]||[];return-1==n.indexOf(e)&&n.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var i=this._onceEvents=this._onceEvents||{},n=i[t]=i[t]||{};return n[e]=!0,this}},e.off=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){var n=i.indexOf(e);return-1!=n&&i.splice(n,1),this}},e.emitEvent=function(t,e){var i=this._events&&this._events[t];if(i&&i.length){i=i.slice(0),e=e||[];for(var n=this._onceEvents&&this._onceEvents[t],o=0;o<i.length;o++){var r=i[o],s=n&&n[r];s&&(this.off(t,r),delete n[r]),r.apply(this,e)}return this}},e.allOff=function(){delete this._events,delete this._onceEvents},t}),function(t,e){"function"==typeof define&&define.amd?define("get-size/get-size",e):"object"==typeof module&&module.exports?module.exports=e():t.getSize=e()}(window,function(){"use strict";function t(t){var e=parseFloat(t),i=-1==t.indexOf("%")&&!isNaN(e);return i&&e}function e(){}function i(){for(var t={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},e=0;u>e;e++){var i=h[e];t[i]=0}return t}function n(t){var e=getComputedStyle(t);return e||a("Style returned "+e+". Are you running this code in a hidden iframe on Firefox? See https://bit.ly/getsizebug1"),e}function o(){if(!d){d=!0;var e=document.createElement("div");e.style.width="200px",e.style.padding="1px 2px 3px 4px",e.style.borderStyle="solid",e.style.borderWidth="1px 2px 3px 4px",e.style.boxSizing="border-box";var i=document.body||document.documentElement;i.appendChild(e);var o=n(e);s=200==Math.round(t(o.width)),r.isBoxSizeOuter=s,i.removeChild(e)}}function r(e){if(o(),"string"==typeof e&&(e=document.querySelector(e)),e&&"object"==typeof e&&e.nodeType){var r=n(e);if("none"==r.display)return i();var a={};a.width=e.offsetWidth,a.height=e.offsetHeight;for(var d=a.isBorderBox="border-box"==r.boxSizing,l=0;u>l;l++){var c=h[l],f=r[c],m=parseFloat(f);a[c]=isNaN(m)?0:m}var p=a.paddingLeft+a.paddingRight,g=a.paddingTop+a.paddingBottom,y=a.marginLeft+a.marginRight,v=a.marginTop+a.marginBottom,_=a.borderLeftWidth+a.borderRightWidth,z=a.borderTopWidth+a.borderBottomWidth,E=d&&s,b=t(r.width);b!==!1&&(a.width=b+(E?0:p+_));var x=t(r.height);return x!==!1&&(a.height=x+(E?0:g+z)),a.innerWidth=a.width-(p+_),a.innerHeight=a.height-(g+z),a.outerWidth=a.width+y,a.outerHeight=a.height+v,a}}var s,a="undefined"==typeof console?e:function(t){console.error(t)},h=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],u=h.length,d=!1;return r}),function(t,e){"use strict";"function"==typeof define&&define.amd?define("desandro-matches-selector/matches-selector",e):"object"==typeof module&&module.exports?module.exports=e():t.matchesSelector=e()}(window,function(){"use strict";var t=function(){var t=window.Element.prototype;if(t.matches)return"matches";if(t.matchesSelector)return"matchesSelector";for(var e=["webkit","moz","ms","o"],i=0;i<e.length;i++){var n=e[i],o=n+"MatchesSelector";if(t[o])return o}}();return function(e,i){return e[t](i)}}),function(t,e){"function"==typeof define&&define.amd?define("fizzy-ui-utils/utils",["desandro-matches-selector/matches-selector"],function(i){return e(t,i)}):"object"==typeof module&&module.exports?module.exports=e(t,require("desandro-matches-selector")):t.fizzyUIUtils=e(t,t.matchesSelector)}(window,function(t,e){var i={};i.extend=function(t,e){for(var i in e)t[i]=e[i];return t},i.modulo=function(t,e){return(t%e+e)%e};var n=Array.prototype.slice;i.makeArray=function(t){if(Array.isArray(t))return t;if(null===t||void 0===t)return[];var e="object"==typeof t&&"number"==typeof t.length;return e?n.call(t):[t]},i.removeFrom=function(t,e){var i=t.indexOf(e);-1!=i&&t.splice(i,1)},i.getParent=function(t,i){for(;t.parentNode&&t!=document.body;)if(t=t.parentNode,e(t,i))return t},i.getQueryElement=function(t){return"string"==typeof t?document.querySelector(t):t},i.handleEvent=function(t){var e="on"+t.type;this[e]&&this[e](t)},i.filterFindElements=function(t,n){t=i.makeArray(t);var o=[];return t.forEach(function(t){if(t instanceof HTMLElement){if(!n)return void o.push(t);e(t,n)&&o.push(t);for(var i=t.querySelectorAll(n),r=0;r<i.length;r++)o.push(i[r])}}),o},i.debounceMethod=function(t,e,i){i=i||100;var n=t.prototype[e],o=e+"Timeout";t.prototype[e]=function(){var t=this[o];clearTimeout(t);var e=arguments,r=this;this[o]=setTimeout(function(){n.apply(r,e),delete r[o]},i)}},i.docReady=function(t){var e=document.readyState;"complete"==e||"interactive"==e?setTimeout(t):document.addEventListener("DOMContentLoaded",t)},i.toDashed=function(t){return t.replace(/(.)([A-Z])/g,function(t,e,i){return e+"-"+i}).toLowerCase()};var o=t.console;return i.htmlInit=function(e,n){i.docReady(function(){var r=i.toDashed(n),s="data-"+r,a=document.querySelectorAll("["+s+"]"),h=document.querySelectorAll(".js-"+r),u=i.makeArray(a).concat(i.makeArray(h)),d=s+"-options",l=t.jQuery;u.forEach(function(t){var i,r=t.getAttribute(s)||t.getAttribute(d);try{i=r&&JSON.parse(r)}catch(a){return void(o&&o.error("Error parsing "+s+" on "+t.className+": "+a))}var h=new e(t,i);l&&l.data(t,n,h)})})},i}),function(t,e){"function"==typeof define&&define.amd?define("outlayer/item",["ev-emitter/ev-emitter","get-size/get-size"],e):"object"==typeof module&&module.exports?module.exports=e(require("ev-emitter"),require("get-size")):(t.Outlayer={},t.Outlayer.Item=e(t.EvEmitter,t.getSize))}(window,function(t,e){"use strict";function i(t){for(var e in t)return!1;return e=null,!0}function n(t,e){t&&(this.element=t,this.layout=e,this.position={x:0,y:0},this._create())}function o(t){return t.replace(/([A-Z])/g,function(t){return"-"+t.toLowerCase()})}var r=document.documentElement.style,s="string"==typeof r.transition?"transition":"WebkitTransition",a="string"==typeof r.transform?"transform":"WebkitTransform",h={WebkitTransition:"webkitTransitionEnd",transition:"transitionend"}[s],u={transform:a,transition:s,transitionDuration:s+"Duration",transitionProperty:s+"Property",transitionDelay:s+"Delay"},d=n.prototype=Object.create(t.prototype);d.constructor=n,d._create=function(){this._transn={ingProperties:{},clean:{},onEnd:{}},this.css({position:"absolute"})},d.handleEvent=function(t){var e="on"+t.type;this[e]&&this[e](t)},d.getSize=function(){this.size=e(this.element)},d.css=function(t){var e=this.element.style;for(var i in t){var n=u[i]||i;e[n]=t[i]}},d.getPosition=function(){var t=getComputedStyle(this.element),e=this.layout._getOption("originLeft"),i=this.layout._getOption("originTop"),n=t[e?"left":"right"],o=t[i?"top":"bottom"],r=parseFloat(n),s=parseFloat(o),a=this.layout.size;-1!=n.indexOf("%")&&(r=r/100*a.width),-1!=o.indexOf("%")&&(s=s/100*a.height),r=isNaN(r)?0:r,s=isNaN(s)?0:s,r-=e?a.paddingLeft:a.paddingRight,s-=i?a.paddingTop:a.paddingBottom,this.position.x=r,this.position.y=s},d.layoutPosition=function(){var t=this.layout.size,e={},i=this.layout._getOption("originLeft"),n=this.layout._getOption("originTop"),o=i?"paddingLeft":"paddingRight",r=i?"left":"right",s=i?"right":"left",a=this.position.x+t[o];e[r]=this.getXValue(a),e[s]="";var h=n?"paddingTop":"paddingBottom",u=n?"top":"bottom",d=n?"bottom":"top",l=this.position.y+t[h];e[u]=this.getYValue(l),e[d]="",this.css(e),this.emitEvent("layout",[this])},d.getXValue=function(t){var e=this.layout._getOption("horizontal");return this.layout.options.percentPosition&&!e?t/this.layout.size.width*100+"%":t+"px"},d.getYValue=function(t){var e=this.layout._getOption("horizontal");return this.layout.options.percentPosition&&e?t/this.layout.size.height*100+"%":t+"px"},d._transitionTo=function(t,e){this.getPosition();var i=this.position.x,n=this.position.y,o=t==this.position.x&&e==this.position.y;if(this.setPosition(t,e),o&&!this.isTransitioning)return void this.layoutPosition();var r=t-i,s=e-n,a={};a.transform=this.getTranslate(r,s),this.transition({to:a,onTransitionEnd:{transform:this.layoutPosition},isCleaning:!0})},d.getTranslate=function(t,e){var i=this.layout._getOption("originLeft"),n=this.layout._getOption("originTop");return t=i?t:-t,e=n?e:-e,"translate3d("+t+"px, "+e+"px, 0)"},d.goTo=function(t,e){this.setPosition(t,e),this.layoutPosition()},d.moveTo=d._transitionTo,d.setPosition=function(t,e){this.position.x=parseFloat(t),this.position.y=parseFloat(e)},d._nonTransition=function(t){this.css(t.to),t.isCleaning&&this._removeStyles(t.to);for(var e in t.onTransitionEnd)t.onTransitionEnd[e].call(this)},d.transition=function(t){if(!parseFloat(this.layout.options.transitionDuration))return void this._nonTransition(t);var e=this._transn;for(var i in t.onTransitionEnd)e.onEnd[i]=t.onTransitionEnd[i];for(i in t.to)e.ingProperties[i]=!0,t.isCleaning&&(e.clean[i]=!0);if(t.from){this.css(t.from);var n=this.element.offsetHeight;n=null}this.enableTransition(t.to),this.css(t.to),this.isTransitioning=!0};var l="opacity,"+o(a);d.enableTransition=function(){if(!this.isTransitioning){var t=this.layout.options.transitionDuration;t="number"==typeof t?t+"ms":t,this.css({transitionProperty:l,transitionDuration:t,transitionDelay:this.staggerDelay||0}),this.element.addEventListener(h,this,!1)}},d.onwebkitTransitionEnd=function(t){this.ontransitionend(t)},d.onotransitionend=function(t){this.ontransitionend(t)};var c={"-webkit-transform":"transform"};d.ontransitionend=function(t){if(t.target===this.element){var e=this._transn,n=c[t.propertyName]||t.propertyName;if(delete e.ingProperties[n],i(e.ingProperties)&&this.disableTransition(),n in e.clean&&(this.element.style[t.propertyName]="",delete e.clean[n]),n in e.onEnd){var o=e.onEnd[n];o.call(this),delete e.onEnd[n]}this.emitEvent("transitionEnd",[this])}},d.disableTransition=function(){this.removeTransitionStyles(),this.element.removeEventListener(h,this,!1),this.isTransitioning=!1},d._removeStyles=function(t){var e={};for(var i in t)e[i]="";this.css(e)};var f={transitionProperty:"",transitionDuration:"",transitionDelay:""};return d.removeTransitionStyles=function(){this.css(f)},d.stagger=function(t){t=isNaN(t)?0:t,this.staggerDelay=t+"ms"},d.removeElem=function(){this.element.parentNode.removeChild(this.element),this.css({display:""}),this.emitEvent("remove",[this])},d.remove=function(){return s&&parseFloat(this.layout.options.transitionDuration)?(this.once("transitionEnd",function(){this.removeElem()}),void this.hide()):void this.removeElem()},d.reveal=function(){delete this.isHidden,this.css({display:""});var t=this.layout.options,e={},i=this.getHideRevealTransitionEndProperty("visibleStyle");e[i]=this.onRevealTransitionEnd,this.transition({from:t.hiddenStyle,to:t.visibleStyle,isCleaning:!0,onTransitionEnd:e})},d.onRevealTransitionEnd=function(){this.isHidden||this.emitEvent("reveal")},d.getHideRevealTransitionEndProperty=function(t){var e=this.layout.options[t];if(e.opacity)return"opacity";for(var i in e)return i},d.hide=function(){this.isHidden=!0,this.css({display:""});var t=this.layout.options,e={},i=this.getHideRevealTransitionEndProperty("hiddenStyle");e[i]=this.onHideTransitionEnd,this.transition({from:t.visibleStyle,to:t.hiddenStyle,isCleaning:!0,onTransitionEnd:e})},d.onHideTransitionEnd=function(){this.isHidden&&(this.css({display:"none"}),this.emitEvent("hide"))},d.destroy=function(){this.css({position:"",left:"",right:"",top:"",bottom:"",transition:"",transform:""})},n}),function(t,e){"use strict";"function"==typeof define&&define.amd?define("outlayer/outlayer",["ev-emitter/ev-emitter","get-size/get-size","fizzy-ui-utils/utils","./item"],function(i,n,o,r){return e(t,i,n,o,r)}):"object"==typeof module&&module.exports?module.exports=e(t,require("ev-emitter"),require("get-size"),require("fizzy-ui-utils"),require("./item")):t.Outlayer=e(t,t.EvEmitter,t.getSize,t.fizzyUIUtils,t.Outlayer.Item)}(window,function(t,e,i,n,o){"use strict";function r(t,e){var i=n.getQueryElement(t);if(!i)return void(h&&h.error("Bad element for "+this.constructor.namespace+": "+(i||t)));this.element=i,u&&(this.$element=u(this.element)),this.options=n.extend({},this.constructor.defaults),this.option(e);var o=++l;this.element.outlayerGUID=o,c[o]=this,this._create();var r=this._getOption("initLayout");r&&this.layout()}function s(t){function e(){t.apply(this,arguments)}return e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e}function a(t){if("number"==typeof t)return t;var e=t.match(/(^\d*\.?\d*)(\w*)/),i=e&&e[1],n=e&&e[2];if(!i.length)return 0;i=parseFloat(i);var o=m[n]||1;return i*o}var h=t.console,u=t.jQuery,d=function(){},l=0,c={};r.namespace="outlayer",r.Item=o,r.defaults={containerStyle:{position:"relative"},initLayout:!0,originLeft:!0,originTop:!0,resize:!0,resizeContainer:!0,transitionDuration:"0.4s",hiddenStyle:{opacity:0,transform:"scale(0.001)"},visibleStyle:{opacity:1,transform:"scale(1)"}};var f=r.prototype;n.extend(f,e.prototype),f.option=function(t){n.extend(this.options,t)},f._getOption=function(t){var e=this.constructor.compatOptions[t];return e&&void 0!==this.options[e]?this.options[e]:this.options[t]},r.compatOptions={initLayout:"isInitLayout",horizontal:"isHorizontal",layoutInstant:"isLayoutInstant",originLeft:"isOriginLeft",originTop:"isOriginTop",resize:"isResizeBound",resizeContainer:"isResizingContainer"},f._create=function(){this.reloadItems(),this.stamps=[],this.stamp(this.options.stamp),n.extend(this.element.style,this.options.containerStyle);var t=this._getOption("resize");t&&this.bindResize()},f.reloadItems=function(){this.items=this._itemize(this.element.children)},f._itemize=function(t){for(var e=this._filterFindItemElements(t),i=this.constructor.Item,n=[],o=0;o<e.length;o++){var r=e[o],s=new i(r,this);n.push(s)}return n},f._filterFindItemElements=function(t){return n.filterFindElements(t,this.options.itemSelector)},f.getItemElements=function(){return this.items.map(function(t){return t.element})},f.layout=function(){this._resetLayout(),this._manageStamps();var t=this._getOption("layoutInstant"),e=void 0!==t?t:!this._isLayoutInited;this.layoutItems(this.items,e),this._isLayoutInited=!0},f._init=f.layout,f._resetLayout=function(){this.getSize()},f.getSize=function(){this.size=i(this.element)},f._getMeasurement=function(t,e){var n,o=this.options[t];o?("string"==typeof o?n=this.element.querySelector(o):o instanceof HTMLElement&&(n=o),this[t]=n?i(n)[e]:o):this[t]=0},f.layoutItems=function(t,e){t=this._getItemsForLayout(t),this._layoutItems(t,e),this._postLayout()},f._getItemsForLayout=function(t){return t.filter(function(t){return!t.isIgnored})},f._layoutItems=function(t,e){if(this._emitCompleteOnItems("layout",t),t&&t.length){var i=[];t.forEach(function(t){var n=this._getItemLayoutPosition(t);n.item=t,n.isInstant=e||t.isLayoutInstant,i.push(n)},this),this._processLayoutQueue(i)}},f._getItemLayoutPosition=function(){return{x:0,y:0}},f._processLayoutQueue=function(t){this.updateStagger(),t.forEach(function(t,e){this._positionItem(t.item,t.x,t.y,t.isInstant,e)},this)},f.updateStagger=function(){var t=this.options.stagger;return null===t||void 0===t?void(this.stagger=0):(this.stagger=a(t),this.stagger)},f._positionItem=function(t,e,i,n,o){n?t.goTo(e,i):(t.stagger(o*this.stagger),t.moveTo(e,i))},f._postLayout=function(){this.resizeContainer()},f.resizeContainer=function(){var t=this._getOption("resizeContainer");if(t){var e=this._getContainerSize();e&&(this._setContainerMeasure(e.width,!0),this._setContainerMeasure(e.height,!1))}},f._getContainerSize=d,f._setContainerMeasure=function(t,e){if(void 0!==t){var i=this.size;i.isBorderBox&&(t+=e?i.paddingLeft+i.paddingRight+i.borderLeftWidth+i.borderRightWidth:i.paddingBottom+i.paddingTop+i.borderTopWidth+i.borderBottomWidth),t=Math.max(t,0),this.element.style[e?"width":"height"]=t+"px"}},f._emitCompleteOnItems=function(t,e){function i(){o.dispatchEvent(t+"Complete",null,[e])}function n(){s++,s==r&&i()}var o=this,r=e.length;if(!e||!r)return void i();var s=0;e.forEach(function(e){e.once(t,n)})},f.dispatchEvent=function(t,e,i){var n=e?[e].concat(i):i;if(this.emitEvent(t,n),u)if(this.$element=this.$element||u(this.element),e){var o=u.Event(e);o.type=t,this.$element.trigger(o,i)}else this.$element.trigger(t,i)},f.ignore=function(t){var e=this.getItem(t);e&&(e.isIgnored=!0)},f.unignore=function(t){var e=this.getItem(t);e&&delete e.isIgnored},f.stamp=function(t){t=this._find(t),t&&(this.stamps=this.stamps.concat(t),t.forEach(this.ignore,this))},f.unstamp=function(t){t=this._find(t),t&&t.forEach(function(t){n.removeFrom(this.stamps,t),this.unignore(t)},this)},f._find=function(t){return t?("string"==typeof t&&(t=this.element.querySelectorAll(t)),t=n.makeArray(t)):void 0},f._manageStamps=function(){this.stamps&&this.stamps.length&&(this._getBoundingRect(),this.stamps.forEach(this._manageStamp,this))},f._getBoundingRect=function(){var t=this.element.getBoundingClientRect(),e=this.size;this._boundingRect={left:t.left+e.paddingLeft+e.borderLeftWidth,top:t.top+e.paddingTop+e.borderTopWidth,right:t.right-(e.paddingRight+e.borderRightWidth),bottom:t.bottom-(e.paddingBottom+e.borderBottomWidth)}},f._manageStamp=d,f._getElementOffset=function(t){var e=t.getBoundingClientRect(),n=this._boundingRect,o=i(t),r={left:e.left-n.left-o.marginLeft,top:e.top-n.top-o.marginTop,right:n.right-e.right-o.marginRight,bottom:n.bottom-e.bottom-o.marginBottom};return r},f.handleEvent=n.handleEvent,f.bindResize=function(){t.addEventListener("resize",this),this.isResizeBound=!0},f.unbindResize=function(){t.removeEventListener("resize",this),this.isResizeBound=!1},f.onresize=function(){this.resize()},n.debounceMethod(r,"onresize",100),f.resize=function(){this.isResizeBound&&this.needsResizeLayout()&&this.layout()},f.needsResizeLayout=function(){var t=i(this.element),e=this.size&&t;return e&&t.innerWidth!==this.size.innerWidth},f.addItems=function(t){var e=this._itemize(t);return e.length&&(this.items=this.items.concat(e)),e},f.appended=function(t){var e=this.addItems(t);e.length&&(this.layoutItems(e,!0),this.reveal(e))},f.prepended=function(t){var e=this._itemize(t);if(e.length){var i=this.items.slice(0);this.items=e.concat(i),this._resetLayout(),this._manageStamps(),this.layoutItems(e,!0),this.reveal(e),this.layoutItems(i)}},f.reveal=function(t){if(this._emitCompleteOnItems("reveal",t),t&&t.length){var e=this.updateStagger();t.forEach(function(t,i){t.stagger(i*e),t.reveal()})}},f.hide=function(t){if(this._emitCompleteOnItems("hide",t),t&&t.length){var e=this.updateStagger();t.forEach(function(t,i){t.stagger(i*e),t.hide()})}},f.revealItemElements=function(t){var e=this.getItems(t);this.reveal(e)},f.hideItemElements=function(t){var e=this.getItems(t);this.hide(e)},f.getItem=function(t){for(var e=0;e<this.items.length;e++){var i=this.items[e];if(i.element==t)return i}},f.getItems=function(t){t=n.makeArray(t);var e=[];return t.forEach(function(t){var i=this.getItem(t);i&&e.push(i)},this),e},f.remove=function(t){var e=this.getItems(t);this._emitCompleteOnItems("remove",e),e&&e.length&&e.forEach(function(t){t.remove(),n.removeFrom(this.items,t)},this)},f.destroy=function(){var t=this.element.style;t.height="",t.position="",t.width="",this.items.forEach(function(t){t.destroy()}),this.unbindResize();var e=this.element.outlayerGUID;delete c[e],delete this.element.outlayerGUID,u&&u.removeData(this.element,this.constructor.namespace)},r.data=function(t){t=n.getQueryElement(t);var e=t&&t.outlayerGUID;return e&&c[e]},r.create=function(t,e){var i=s(r);return i.defaults=n.extend({},r.defaults),n.extend(i.defaults,e),i.compatOptions=n.extend({},r.compatOptions),i.namespace=t,i.data=r.data,i.Item=s(o),n.htmlInit(i,t),u&&u.bridget&&u.bridget(t,i),i};var m={ms:1,s:1e3};return r.Item=o,r}),function(t,e){"function"==typeof define&&define.amd?define(["outlayer/outlayer","get-size/get-size"],e):"object"==typeof module&&module.exports?module.exports=e(require("outlayer"),require("get-size")):t.Masonry=e(t.Outlayer,t.getSize)}(window,function(t,e){var i=t.create("masonry");i.compatOptions.fitWidth="isFitWidth";var n=i.prototype;return n._resetLayout=function(){this.getSize(),this._getMeasurement("columnWidth","outerWidth"),this._getMeasurement("gutter","outerWidth"),this.measureColumns(),this.colYs=[];for(var t=0;t<this.cols;t++)this.colYs.push(0);this.maxY=0,this.horizontalColIndex=0},n.measureColumns=function(){if(this.getContainerWidth(),!this.columnWidth){var t=this.items[0],i=t&&t.element;this.columnWidth=i&&e(i).outerWidth||this.containerWidth}var n=this.columnWidth+=this.gutter,o=this.containerWidth+this.gutter,r=o/n,s=n-o%n,a=s&&1>s?"round":"floor";r=Math[a](r),this.cols=Math.max(r,1)},n.getContainerWidth=function(){var t=this._getOption("fitWidth"),i=t?this.element.parentNode:this.element,n=e(i);this.containerWidth=n&&n.innerWidth},n._getItemLayoutPosition=function(t){t.getSize();var e=t.size.outerWidth%this.columnWidth,i=e&&1>e?"round":"ceil",n=Math[i](t.size.outerWidth/this.columnWidth);n=Math.min(n,this.cols);for(var o=this.options.horizontalOrder?"_getHorizontalColPosition":"_getTopColPosition",r=this[o](n,t),s={x:this.columnWidth*r.col,y:r.y},a=r.y+t.size.outerHeight,h=n+r.col,u=r.col;h>u;u++)this.colYs[u]=a;return s},n._getTopColPosition=function(t){var e=this._getTopColGroup(t),i=Math.min.apply(Math,e);return{col:e.indexOf(i),y:i}},n._getTopColGroup=function(t){if(2>t)return this.colYs;for(var e=[],i=this.cols+1-t,n=0;i>n;n++)e[n]=this._getColGroupY(n,t);return e},n._getColGroupY=function(t,e){if(2>e)return this.colYs[t];var i=this.colYs.slice(t,t+e);return Math.max.apply(Math,i)},n._getHorizontalColPosition=function(t,e){var i=this.horizontalColIndex%this.cols,n=t>1&&i+t>this.cols;i=n?0:i;var o=e.size.outerWidth&&e.size.outerHeight;return this.horizontalColIndex=o?i+t:this.horizontalColIndex,{col:i,y:this._getColGroupY(i,t)}},n._manageStamp=function(t){var i=e(t),n=this._getElementOffset(t),o=this._getOption("originLeft"),r=o?n.left:n.right,s=r+i.outerWidth,a=Math.floor(r/this.columnWidth);a=Math.max(0,a);var h=Math.floor(s/this.columnWidth);h-=s%this.columnWidth?0:1,h=Math.min(this.cols-1,h);for(var u=this._getOption("originTop"),d=(u?n.top:n.bottom)+i.outerHeight,l=a;h>=l;l++)this.colYs[l]=Math.max(d,this.colYs[l])},n._getContainerSize=function(){this.maxY=Math.max.apply(Math,this.colYs);var t={height:this.maxY};return this._getOption("fitWidth")&&(t.width=this._getContainerFitWidth()),t},n._getContainerFitWidth=function(){for(var t=0,e=this.cols;--e&&0===this.colYs[e];)t++;return(this.cols-t)*this.columnWidth-this.gutter},n.needsResizeLayout=function(){var t=this.containerWidth;return this.getContainerWidth(),t!=this.containerWidth},i});
@@ -0,0 +1,64 @@
1
+ {% load static %}
2
+
3
+ <link rel="stylesheet" href="{% static 'django_image_gallery/css/masonry.css' %}">
4
+ <link rel="stylesheet" href="{% static 'django_image_gallery/css/gallery.css' %}">
5
+
6
+ <div id="image-gallery">
7
+ <div class="image-gallery-container">
8
+
9
+ <div class="gallery-grid">
10
+ {% for image in gallery_images|slice:":8" %}
11
+ <div class="gallery-item">
12
+ <img src="{{ image.image.url }}" class="gallery-image" data-index="{{ forloop.counter0 }}" loading="lazy" alt="{{ image.title }}">
13
+ </div>
14
+ {% endfor %}
15
+ </div>
16
+
17
+ <template id="image-gallery-template">
18
+ {% for image in gallery_images|slice:"8:" %}
19
+ <div class="gallery-item">
20
+ <img src="{{ image.image.url }}" class="gallery-image" data-index="{{ forloop.counter0|add:8 }}" loading="lazy" alt="{{ image.title }}">
21
+ </div>
22
+ {% endfor %}
23
+ </template>
24
+
25
+ {% if gallery_images|length > 8 %}
26
+ <div class="image-gallery-controls">
27
+ <button id="image-gallery-load-more" type="button">
28
+ Load More
29
+ </button>
30
+
31
+ <button id="image-gallery-hide" type="button" hidden>
32
+ Hide Images
33
+ </button>
34
+ </div>
35
+ {% endif %}
36
+ </div>
37
+
38
+ <div id="image-gallery-modal" class="image-gallery-modal" hidden aria-hidden="true" tabindex="-1">
39
+ <div class="image-gallery-modal-content">
40
+
41
+ <button type="button" class="gallery-close" aria-label="Close gallery">
42
+ &times;
43
+ </button>
44
+
45
+ <button type="button" class="gallery-prev" aria-label="Previous image">
46
+ &#10094;
47
+ </button>
48
+
49
+ <div class="gallery-slide">
50
+ {% for image in gallery_images %}
51
+ <img src="{{ image.image.url }}" class="gallery-modal-image" data-index="{{ forloop.counter0 }}" alt="{{ image.title }}">
52
+ {% endfor %}
53
+ </div>
54
+
55
+ <button type="button" class="gallery-next" aria-label="Next image">
56
+ &#10095;
57
+ </button>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <script src="{% static 'django_image_gallery/js/imagesloaded.pkgd.min.js' %}"></script>
63
+ <script src="{% static 'django_image_gallery/js/masonry.pkgd.min.js' %}"></script>
64
+ <script src="{% static 'django_image_gallery/js/gallery.js' %}"></script>
@@ -0,0 +1,11 @@
1
+ from django import template
2
+
3
+ from django_image_gallery.models import GalleryImage
4
+
5
+ register = template.Library()
6
+
7
+
8
+ @register.inclusion_tag("django_image_gallery/gallery.html")
9
+ def gallery():
10
+ gallery_images = GalleryImage.objects.filter(is_published=True)
11
+ return {"gallery_images": gallery_images}
@@ -0,0 +1,106 @@
1
+
2
+ import tempfile
3
+ from io import BytesIO
4
+
5
+ from PIL import Image
6
+ from django.core.files.uploadedfile import SimpleUploadedFile
7
+ from django.test import TestCase, override_settings
8
+ from django.template import Context, Template
9
+
10
+ from django_image_gallery.models import GalleryImage
11
+
12
+
13
+ class GalleryImageTests(TestCase):
14
+ def setUp(self):
15
+ self.temp_dir = tempfile.TemporaryDirectory()
16
+ self.addCleanup(self.temp_dir.cleanup)
17
+
18
+ media_settings = override_settings(MEDIA_ROOT=self.temp_dir.name)
19
+ media_settings.enable()
20
+ self.addCleanup(media_settings.disable)
21
+
22
+ def create_test_image(self, filename="test-image.jpg", size=(800, 600)):
23
+ buffer = BytesIO()
24
+
25
+ Image.new("RGB", size, color=(100, 150, 200)).save(
26
+ buffer,
27
+ format="JPEG",
28
+ )
29
+
30
+ return SimpleUploadedFile(
31
+ filename,
32
+ buffer.getvalue(),
33
+ content_type="image/jpeg",
34
+ )
35
+
36
+ def test_create_gallery_image(self):
37
+ image = GalleryImage.objects.create(
38
+ title="Test Image",
39
+ image=self.create_test_image(),
40
+ caption="A test caption.",
41
+ )
42
+
43
+ self.assertEqual(image.title, "Test Image")
44
+ self.assertEqual(image.caption, "A test caption.")
45
+ self.assertEqual(image.sort_order, 0)
46
+ self.assertFalse(image.is_published)
47
+ self.assertEqual(str(image), "Test Image")
48
+
49
+ def test_image_resizing(self):
50
+ image = GalleryImage.objects.create(
51
+ title="Large Image",
52
+ image=self.create_test_image(size=(2400, 1200)),
53
+ )
54
+
55
+ with Image.open(image.image.path) as stored_image:
56
+ self.assertEqual(stored_image.size, (2000, 1000))
57
+
58
+ def test_small_images_are_not_resized(self):
59
+ image = GalleryImage.objects.create(
60
+ title="Small Image",
61
+ image=self.create_test_image(size=(800, 600)),
62
+ )
63
+
64
+ with Image.open(image.image.path) as stored_image:
65
+ self.assertEqual(stored_image.size, (800, 600))
66
+
67
+ def test_gallery_image_ordering(self):
68
+ first = GalleryImage.objects.create(
69
+ title="First",
70
+ image=self.create_test_image("first.jpg"),
71
+ sort_order=1,
72
+ )
73
+
74
+ second = GalleryImage.objects.create(
75
+ title="Second",
76
+ image=self.create_test_image("second.jpg"),
77
+ sort_order=2,
78
+ )
79
+
80
+ images = list(GalleryImage.objects.all())
81
+
82
+ self.assertEqual(images, [first, second])
83
+
84
+ def test_gallery_displays_only_published_images(self):
85
+ GalleryImage.objects.create(
86
+ title="Published Image",
87
+ image=self.create_test_image("published.jpg"),
88
+ is_published=True,
89
+ )
90
+
91
+ GalleryImage.objects.create(
92
+ title="Unpublished Image",
93
+ image=self.create_test_image("unpublished.jpg"),
94
+ is_published=False,
95
+ )
96
+
97
+ template = Template(
98
+ "{% load django_image_gallery %}{% gallery %}"
99
+ )
100
+
101
+ html = template.render(Context({}))
102
+
103
+ self.assertIn("Published Image", html)
104
+ self.assertIn("published.jpg", html)
105
+ self.assertNotIn("Unpublished Image", html)
106
+ self.assertNotIn("unpublished.jpg", html)
@@ -0,0 +1,3 @@
1
+ from django.shortcuts import render
2
+
3
+ # Create your views here.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-image-gallery
3
+ Version: 0.1.0
4
+ Summary: A Django image gallery application.
5
+ Author: Alexander Towe
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/alextowe/django-image-gallery
8
+ Project-URL: Repository, https://github.com/alextowe/django-image-gallery
9
+ Project-URL: Issues, https://github.com/alextowe/django-image-gallery/issues
10
+ Classifier: Framework :: Django
11
+ Classifier: Framework :: Django :: 5.2
12
+ Classifier: Framework :: Django :: 6.0
13
+ Classifier: Framework :: Django :: 6.1
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ License-File: THIRD_PARTY_NOTICES.md
23
+ Requires-Dist: Django<7.0,>=5.2
24
+ Requires-Dist: Pillow>=10.0
25
+ Dynamic: license-file
26
+
27
+ # Django Image Gallery
28
+
29
+ An image gallery application for Django projects. Provides an easy way to manage, organize, and display images through the Django admin interface and a reusable template tag.
30
+
31
+ ## Features
32
+
33
+ - Django model for managing gallery images
34
+ - Automatic resizing of uploaded images exceeding 2000 × 2000 pixels
35
+ - Published and unpublished image management
36
+ - Configurable image ordering
37
+ - Masonry image layout
38
+ - Load-more functionality
39
+ - Full-screen image viewer
40
+ - Django admin integration
41
+ - Reusable template tag
42
+
43
+ ## Requirements
44
+
45
+ - Python 3.10+
46
+ - Django 5.2, 6.0, or 6.1
47
+ - Pillow 10.0 or later
48
+
49
+ ## Installation
50
+
51
+ Install the package using pip:
52
+
53
+ ```bash
54
+ pip install django-image-gallery
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ Add `django_image_gallery` to your project's `INSTALLED_APPS`:
60
+
61
+ ```python
62
+ INSTALLED_APPS = [
63
+ # ...
64
+ "django_image_gallery",
65
+ ]
66
+ ```
67
+
68
+ Run the database migrations:
69
+
70
+ ```bash
71
+ python manage.py migrate
72
+ ```
73
+
74
+ ## Usage
75
+
76
+ Load the template tag library and include the gallery wherever you want it displayed:
77
+
78
+ ```django
79
+ {% load django_image_gallery %}
80
+
81
+ {% gallery %}
82
+ ```
83
+
84
+ The template tag retrieves all published gallery images and renders them using the package's included gallery template.
85
+
86
+ ## Media Configuration
87
+
88
+ Configure Django to handle uploaded media files:
89
+
90
+ ```python
91
+ MEDIA_URL = "/media/"
92
+ MEDIA_ROOT = BASE_DIR / "media"
93
+ ```
94
+
95
+ During local development, you can serve media files through Django by adding the following to your project's root `urls.py`:
96
+
97
+ ```python
98
+ from django.conf import settings
99
+ from django.conf.urls.static import static
100
+
101
+ urlpatterns = [
102
+ # Your URL patterns
103
+ ]
104
+
105
+ if settings.DEBUG:
106
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
107
+ ```
108
+
109
+ For production deployments, configure your web server or storage backend to serve uploaded media files. Uploaded images must be accessible through the configured media storage.
110
+
111
+ ## Managing Images
112
+
113
+ Images can be added and managed through the Django admin interface.
114
+
115
+ After creating a superuser:
116
+
117
+ ```bash
118
+ python manage.py createsuperuser
119
+ ```
120
+
121
+ Log in to the admin interface and locate **Gallery Images**.
122
+
123
+ Each image supports the following fields:
124
+
125
+ | Field | Description |
126
+ |---|---|
127
+ | Title | Optional image title |
128
+ | Image | Uploaded image file |
129
+ | Caption | Optional image caption |
130
+ | Sort order | Controls the display order of images |
131
+ | Published | Determines whether the image appears in the gallery |
132
+
133
+ Only published images appear in the gallery. Images are ordered by `sort_order`, with newer entries taking precedence when sort orders match.
134
+
135
+ ## License
136
+
137
+ This project is licensed under the MIT License. See the `LICENSE` file for details.
@@ -0,0 +1,25 @@
1
+ LICENSE
2
+ README.md
3
+ THIRD_PARTY_NOTICES.md
4
+ pyproject.toml
5
+ django_image_gallery/__init__.py
6
+ django_image_gallery/admin.py
7
+ django_image_gallery/apps.py
8
+ django_image_gallery/models.py
9
+ django_image_gallery/tests.py
10
+ django_image_gallery/views.py
11
+ django_image_gallery.egg-info/PKG-INFO
12
+ django_image_gallery.egg-info/SOURCES.txt
13
+ django_image_gallery.egg-info/dependency_links.txt
14
+ django_image_gallery.egg-info/requires.txt
15
+ django_image_gallery.egg-info/top_level.txt
16
+ django_image_gallery/migrations/0001_initial.py
17
+ django_image_gallery/migrations/__init__.py
18
+ django_image_gallery/static/django_image_gallery/css/gallery.css
19
+ django_image_gallery/static/django_image_gallery/css/masonry.css
20
+ django_image_gallery/static/django_image_gallery/js/gallery.js
21
+ django_image_gallery/static/django_image_gallery/js/imagesloaded.pkgd.min.js
22
+ django_image_gallery/static/django_image_gallery/js/masonry.pkgd.min.js
23
+ django_image_gallery/templates/django_image_gallery/gallery.html
24
+ django_image_gallery/templatetags/__init__.py
25
+ django_image_gallery/templatetags/django_image_gallery.py
@@ -0,0 +1,2 @@
1
+ Django<7.0,>=5.2
2
+ Pillow>=10.0
@@ -0,0 +1 @@
1
+ django_image_gallery
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "django-image-gallery"
7
+ version = "0.1.0"
8
+ description = "A Django image gallery application."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+
13
+ authors = [
14
+ { name = "Alexander Towe" },
15
+ ]
16
+
17
+ dependencies = [
18
+ "Django>=5.2,<7.0",
19
+ "Pillow>=10.0",
20
+ ]
21
+
22
+ classifiers = [
23
+ "Framework :: Django",
24
+ "Framework :: Django :: 5.2",
25
+ "Framework :: Django :: 6.0",
26
+ "Framework :: Django :: 6.1",
27
+ "Intended Audience :: Developers",
28
+ "Operating System :: OS Independent",
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3 :: Only",
31
+ "Programming Language :: Python :: 3.14",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/alextowe/django-image-gallery"
36
+ Repository = "https://github.com/alextowe/django-image-gallery"
37
+ Issues = "https://github.com/alextowe/django-image-gallery/issues"
38
+
39
+ [tool.setuptools]
40
+ license-files = ["LICENSE", "THIRD_PARTY_NOTICES.md"]
41
+
42
+ [tool.setuptools.packages.find]
43
+ include = ["django_image_gallery*"]
44
+
45
+ [tool.setuptools.package-data]
46
+ django_image_gallery = [
47
+ "templates/**/*.html",
48
+ "static/**/*",
49
+ "migrations/*.py",
50
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+