diff --git a/.gitignore b/.gitignore index 9cf82db..5b67694 100644 --- a/.gitignore +++ b/.gitignore @@ -275,3 +275,4 @@ dmypy.json # Cython debug symbols cython_debug/ +errorlog diff --git a/CHANGES b/CHANGES.rst similarity index 53% rename from CHANGES rename to CHANGES.rst index 28f7d96..18ab3ae 100644 --- a/CHANGES +++ b/CHANGES.rst @@ -1,27 +1,34 @@ -======= Changes -======= +####### + +1.1.0 (*2021-07-24*) +~~~~~~~~~~~~~~~~~~~~ + +- Better compatibility with mobile phones and small screens +- Swipe support +- More space for images +- Restructure repository 1.0.3 (*2021-07-13*) -==================== +~~~~~~~~~~~~~~~~~~~~ - Fix height on prev/next buttons 1.0.2 (*2021-07-13*) -==================== +~~~~~~~~~~~~~~~~~~~~ - Fix scroll computation - Translation of ignored elements - Improve accessibility 1.0.1 (*2021-06-03*) -==================== +~~~~~~~~~~~~~~~~~~~~ - Fix translation - Add timer option - Better CSS and transition 1.0.0 (*2021-05-28*) -==================== +~~~~~~~~~~~~~~~~~~~~ - Initial release diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..4a39bdb --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include VERSION +include CHANGES diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f9b8e55 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +PYTHON = python3 +SRCDIR = . +DISTDIR = ./dist +BUILDDIR = ./build + +all: sdist wheel + +clean: + rm -rf *.egg-info dist build + +sdist: + $(PYTHON) -m build $(SRCDIR) -o $(DISTDIR) -sn + +wheel: + $(PYTHON) -m build $(SRCDIR) -o $(BUILDDIR) -wn + +install: + pip uninstall -y sphinx-galleria + pip install $(DISTDIR)/sphinx_galleria-`cat $(SRCDIR)/VERSION`.tar.gz + +.PHONY: clean sdist wheel install diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..9084fa2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.1.0 diff --git a/sphinx_galleria/__init__.py b/__init__.py similarity index 94% rename from sphinx_galleria/__init__.py rename to __init__.py index 5eb0aee..6589730 100644 --- a/sphinx_galleria/__init__.py +++ b/__init__.py @@ -14,8 +14,12 @@ from PIL import Image from . import directive from . import collector -__version_info__ = (1, 0, 3) -__version__ = '.'.join([str(val) for val in __version_info__]) + +with open(os.path.join( + os.path.dirname(__file__), + 'VERSION')) as version_file: + __version__ = version_file.read().strip() + __version_info__ = tuple(int(v) for v in __version__.split('.')) def copy_images_files(app: Sphinx, env: BuildEnvironment) -> None: diff --git a/sphinx_galleria/collector.py b/collector.py similarity index 100% rename from sphinx_galleria/collector.py rename to collector.py diff --git a/sphinx_galleria/directive.py b/directive.py similarity index 100% rename from sphinx_galleria/directive.py rename to directive.py diff --git a/sphinx_galleria/locale/fr/LC_MESSAGES/sphinx.mo b/locale/fr/LC_MESSAGES/sphinx.mo similarity index 100% rename from sphinx_galleria/locale/fr/LC_MESSAGES/sphinx.mo rename to locale/fr/LC_MESSAGES/sphinx.mo diff --git a/sphinx_galleria/locale/fr/LC_MESSAGES/sphinx.po b/locale/fr/LC_MESSAGES/sphinx.po similarity index 100% rename from sphinx_galleria/locale/fr/LC_MESSAGES/sphinx.po rename to locale/fr/LC_MESSAGES/sphinx.po diff --git a/sphinx_galleria/locale/nl/LC_MESSAGES/sphinx.mo b/locale/nl/LC_MESSAGES/sphinx.mo similarity index 100% rename from sphinx_galleria/locale/nl/LC_MESSAGES/sphinx.mo rename to locale/nl/LC_MESSAGES/sphinx.mo diff --git a/sphinx_galleria/locale/nl/LC_MESSAGES/sphinx.po b/locale/nl/LC_MESSAGES/sphinx.po similarity index 100% rename from sphinx_galleria/locale/nl/LC_MESSAGES/sphinx.po rename to locale/nl/LC_MESSAGES/sphinx.po diff --git a/sphinx_galleria/locale/sphinx.pot b/locale/sphinx.pot similarity index 100% rename from sphinx_galleria/locale/sphinx.pot rename to locale/sphinx.pot diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9bf5c7e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = [ + "setuptools>=56", + "wheel" +] +build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt index 67e3e9b..5eb448c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ Sphinx Pillow +setuptools>=56 +build diff --git a/setup.cfg b/setup.cfg index 3409f63..adc53e4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,61 @@ [metadata] -description-file = README.rst -license-files = +long_description = file: README.rst, CHANGES.rst +long_description_content_type = text/x-rst +license_files = LICENSE LICENSE-de LICENSE-fr LICENSE-nl +name = sphinx_galleria +version = file: VERSION +url = https://procrastinator.nerv-project.eu/nerv-project/sphinx_galleria +license = EUPL 1.2 +author = Kujiu +author_email = kujiu-pypi@kujiu.org +description = Create image galleries with Sphinx +platform = any +classifiers = + Framework :: Sphinx + Framework :: Sphinx :: Extension + Development Status :: 5 - Production/Stable + Environment :: Console + Environment :: Web Environment + Intended Audience :: Developers + License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2) + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Operating System :: OS Independent + Topic :: Documentation + Topic :: Software Development :: Documentation +keywords = sphinx image gallery +project_urls = + Source Code = https://procrastinator.nerv-project.eu/nerv-project/sphinx_galleria + Bug Tracker = https://procrastinator.nerv-project.eu/nerv-project/sphinx_galleria/issues +[options] +install_requires = + Sphinx>=3.0.0 + Pillow +packages = sphinx_galleria +package_dir = + sphinx_galleria = . +# cmdclass = +# compile_catalog = babel.messages.frontend.compile_catalog, +# extract_messages = babel.messages.frontend.extract_messages, +# init_catalog = babel.messages.frontend.init_catalog, +# update_catalog = babel.messages.frontend.update_catalog + +[options.package_data] +sphinx_galleria = + VERSION + static/sphinxgalleria/*.mjs + static/sphinxgalleria/*.css + locale/*.pot + locale/*/LC_MESSAGES/*.po + locale/*/LC_MESSAGES/*.mo [extract_messages] mapping_file = babel.cfg diff --git a/setup.py b/setup.py deleted file mode 100644 index 36874c5..0000000 --- a/setup.py +++ /dev/null @@ -1,58 +0,0 @@ -from setuptools import setup -from babel.messages import frontend as babel - - -with open("README.rst", "r") as fh: - long_description = fh.read() - -setup( - name="sphinx_galleria", - version="1.0.3", - url="https://procrastinator.nerv-project.eu/nerv-project/sphinx_galleria", - license="EUPL 1.2", - author="Kujiu", - author_email="kujiu-pypi@kujiu.org", - description="Create image galleries with Sphinx", - long_description=long_description, - long_description_content_type="text/x-rst", - packages=["sphinx_galleria"], - cmdclass={ - 'compile_catalog': babel.compile_catalog, - 'extract_messages': babel.extract_messages, - 'init_catalog': babel.init_catalog, - 'update_catalog': babel.update_catalog - }, - package_data={ - "sphinx_galleria": [ - "*.py", - "static/sphinxgalleria/*.mjs", - "static/sphinxgalleria/*.css", - "locale/*.pot", - "locale/*/LC_MESSAGES/*.po", - "locale/*/LC_MESSAGES/*.mo", - ] - }, - install_requires=["sphinx>=3.0.0"], - classifiers=[ - "Framework :: Sphinx", - "Framework :: Sphinx :: Extension", - "Development Status :: 5 - Production/Stable", - "Environment :: Console", - "Environment :: Web Environment", - "Intended Audience :: Developers", - "License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Operating System :: OS Independent", - "Topic :: Documentation", - "Topic :: Software Development :: Documentation", - ], - keywords="sphinx image gallery", - project_urls={ - "Source": "https://procrastinator.nerv-project.eu/nerv-project/sphinx_galleria", - "Issues": "https://procrastinator.nerv-project.eu/nerv-project/sphinx_galleria/issues", - }, -) diff --git a/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.css b/static/sphinxgalleria/sphinxgalleria.css similarity index 92% rename from sphinx_galleria/static/sphinxgalleria/sphinxgalleria.css rename to static/sphinxgalleria/sphinxgalleria.css index c18a2ff..f1c9cb4 100644 --- a/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.css +++ b/static/sphinxgalleria/sphinxgalleria.css @@ -31,8 +31,7 @@ } .sphinxgalleria-core figure .row { - flex-flow: row nowrap; - display: flex; + position: relative; flex-shrink: 1000; flex-grow: 1000; } @@ -53,8 +52,7 @@ box-sizing: border-box; width: 100%; height: 100%; - display: flex; - flex-flow: column nowrap; + position: relative; background-color: transparent; } @@ -173,14 +171,13 @@ .sphinxgalleria-core dialog { position: absolute; - top: 3vh; + top: 2.5vh; left: 0; - height: 90vh; - max-height: 90vh; + max-height: 95vh; width: max-content; - margin: 0 auto 0 auto; - padding-left: 3rem; - padding-right: 3rem; + margin: auto; + padding-left: 2vw; + padding-right: 2vw; background-color: black; color: white; border: white solid 0.2rem; @@ -192,7 +189,9 @@ .sphinxgalleria-core dialog img { height: 100%; - width: auto; + width: 100%; + max-width: 90vw !important; + max-height: 90vh !important; object-fit: contain; flex-grow: 1000; flex-shrink: 1000; @@ -227,16 +226,17 @@ .sphinxgalleria-core dialog header button { position: absolute; top: .5rem; - right: -.5rem; + right: 0; + background: transparent; } .sphinxgalleria-core dialog header button::after { content: '✕'; + text-shadow: .1rem .05rem black; } .sphinxgalleria-core dialog header { position: relative; - min-height: 3rem; } .sphinxgalleria-core dialog menu { @@ -257,6 +257,10 @@ .sphinxgalleria-core figure button.prev, .sphinxgalleria-core figure button.next { + position: absolute; + top: 0; + bottom: 0; + z-index: 100; color: hsl(0, 0%, 34%); background-color: transparent; font-size: 4rem; @@ -276,11 +280,23 @@ transition: color ease .4s; } +.sphinxgalleria-core figure button.prev { + left: 0; +} + +.sphinxgalleria-core figure button.next { + right: 0; +} + .sphinxgalleria-core figure button.prev::before, .sphinxgalleria-core dialog menu button.prev::before { content: '<'; } +.sphinxgalleria-core dialog menu button.next { + float: right; +} + .sphinxgalleria-core figure button.next::after, .sphinxgalleria-core dialog menu button.next::after { margin-left: 0.5rem; diff --git a/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.mjs b/static/sphinxgalleria/sphinxgalleria.mjs similarity index 63% rename from sphinx_galleria/static/sphinxgalleria/sphinxgalleria.mjs rename to static/sphinxgalleria/sphinxgalleria.mjs index c8e9a68..ecf3c30 100644 --- a/sphinx_galleria/static/sphinxgalleria/sphinxgalleria.mjs +++ b/static/sphinxgalleria/sphinxgalleria.mjs @@ -7,6 +7,17 @@ export class SphinxGalleria { self.options = options; self.data = data; self.oneimage = self.data.length === 1; + self.swipe_delay = 1500; + self.touch_data = { + id: -1, + x: null, + y: null, + current_x: null, + current_y: null, + interval: null, + swiped: false, + noclick: false + }; self.node_target = null; self.node_figure = document.createElement('figure'); @@ -35,9 +46,6 @@ export class SphinxGalleria { var figure_row = document.createElement('div'); figure_row.classList.add('row'); - self.dialog_button_close = document.createElement('button'); - self.dialog_button_close.classList.add('close'); - self.dialog_button_close_icon = document.createElement('button'); self.dialog_button_close_icon.setAttribute('aria-label', self.options.label_close); @@ -90,13 +98,10 @@ export class SphinxGalleria { self.button_modal.appendChild(self.node_image); self.button_modal.appendChild(self.node_div_caption); - self.dialog_button_close.appendChild(document.createTextNode(self.options.label_close)); - dialog_header.appendChild(self.dialog_title); dialog_header.appendChild(self.dialog_button_close_icon); self.node_dialog.appendChild(dialog_header); self.node_dialog.appendChild(self.dialog_image); - dialog_menu.appendChild(self.dialog_button_close); self.node_dialog.appendChild(dialog_menu); if(!self.oneimage) { @@ -119,30 +124,38 @@ export class SphinxGalleria { self.node_target.appendChild(self.node_thumbnails); self.node_target.appendChild(self.node_mask); - var onmodal = function(event) { - var key = event.key; - if(event.type==='keypress' && key!==" " && key!=="Enter") { + var onmodal = function(ev) { + if(self.touch_data.noclick) { + self.touch_data.noclick = false; + ev.preventDefault(); return; } - event.preventDefault(); + var key = ev.key; + if(ev.type==='keypress' && key!==" " && key!=="Enter") { + return; + } + ev.preventDefault(); self.showModal(); }; self.button_modal.addEventListener('click', onmodal); self.button_modal.addEventListener('keypress', onmodal); - var onclose = function(event) { - var key = event.key; - if(event.type==='keypress' && key!==" " && key!=="Enter") { + var onclose = function(ev) { + if(self.touch_data.noclick) { + self.touch_data.noclick = false; + ev.preventDefault(); return; } - event.preventDefault(); + var key = ev.key; + if(ev.type==='keypress' && key!==" " && key!=="Enter") { + return; + } + ev.preventDefault(); self.closeModal(); }; self.node_mask.addEventListener('click', onclose); - self.node_dialog.addEventListener('click', function(e) {e.stopPropagation();}); - self.dialog_button_close.addEventListener('click', onclose); - self.dialog_button_close.addEventListener('keypress', onclose); + self.node_dialog.addEventListener('click', function(ev) {ev.stopPropagation();}); self.dialog_button_close_icon.addEventListener('click', onclose); self.dialog_button_close_icon.addEventListener('keypress', onclose); @@ -153,21 +166,67 @@ export class SphinxGalleria { }, self.options.timer*1000); } - var onprev = function(event) { - var key = event.key; - if(event.type==='keypress' && key!==" " && key!=="Enter") { + var startSwipe = function(ev) {self.startSwipe(ev);}; + var moveSwipe = function(ev) {self.moveSwipe(ev);}; + var endSwipe = function(ev) {self.endSwipe(ev);}; + + self.node_mask.addEventListener('touchstart', startSwipe, true); + self.node_mask.addEventListener('touchmove', moveSwipe, true); + self.node_mask.addEventListener('touchend', endSwipe, true); + + self.node_target.addEventListener('touchstart', startSwipe, true); + self.node_target.addEventListener('touchmove', moveSwipe); + self.node_target.addEventListener('touchend', endSwipe, true); + + self.node_image.addEventListener('touchstart', startSwipe, true); + self.node_image.addEventListener('touchmove', moveSwipe); + self.node_image.addEventListener('touchend', endSwipe, true); + + self.dialog_image.addEventListener('touchstart', startSwipe, true); + self.dialog_image.addEventListener('touchmove', moveSwipe); + self.dialog_image.addEventListener('touchend', endSwipe, true); + + self.node_mask.addEventListener('mousedown', startSwipe, true); + self.node_mask.addEventListener('mousemove', moveSwipe, true); + self.node_mask.addEventListener('mouseup', endSwipe, true); + + self.node_target.addEventListener('mousedown', startSwipe, true); + self.node_target.addEventListener('mousemove', moveSwipe, true); + self.node_target.addEventListener('mouseup', endSwipe, true); + + self.node_image.addEventListener('mousedown', startSwipe, true); + self.node_image.addEventListener('mousemove', moveSwipe, true); + self.node_image.addEventListener('mouseup', endSwipe, true); + + self.dialog_image.addEventListener('mousedown', startSwipe, true); + self.dialog_image.addEventListener('mousemove', moveSwipe, true); + self.dialog_image.addEventListener('mouseup', endSwipe, true); + + var removeClick = function(ev) { + if(self.touch_data.noclick) { + self.touch_data.noclick = false; + ev.preventDefault(); + ev.stopImmediatePropagation(); + } + }; + self.node_mask.addEventListener('click', removeClick, true); + self.node_target.addEventListener('click', removeClick, true); + + var onprev = function(ev) { + var key = ev.key; + if(ev.type==='keypress' && key!==" " && key!=="Enter") { return; } - event.preventDefault(); + ev.preventDefault(); self.prev(); }; - var onnext = function(event) { - var key = event.key; - if(event.type==='keypress' && key!==" " && key!=="Enter") { + var onnext = function(ev) { + var key = ev.key; + if(ev.type==='keypress' && key!==" " && key!=="Enter") { return; } - event.preventDefault(); + ev.preventDefault(); self.next(); }; @@ -183,14 +242,19 @@ export class SphinxGalleria { self.dialog_button_next.addEventListener('keypress', onnext); self.dialog_button_next.addEventListener('click', onnext); - self.dialog_image.addEventListener('click', function(e) { - var x = e.layerX - e.target.x; - if(x < e.target.width/2) { + self.dialog_image.addEventListener('click', function(ev) { + if(self.touch_data.noclick) { + self.touch_data.noclick = false; + ev.preventDefault(); + return; + } + var x = ev.layerX - ev.target.x; + if(x < ev.target.width/2) { self.prev(); - e.preventDefault(); - } else if(x > e.target.width/2) { + ev.preventDefault(); + } else if(x > ev.target.width/2) { self.next(); - e.preventDefault(); + ev.preventDefault(); } }); @@ -246,8 +310,8 @@ export class SphinxGalleria { image_button.addEventListener('change', function() {self.changeImage();}); }); - document.addEventListener('keydown', function(event) { - var key = event.key; + document.addEventListener('keydown', function(ev) { + var key = ev.key; if(!self.node_dialog.open && !self.node_target.contains(document.activeElement)) { return; @@ -255,13 +319,13 @@ export class SphinxGalleria { if(!self.oneimage && key==="ArrowLeft") { self.prev(); - event.preventDefault(); + ev.preventDefault(); } else if(!self.oneimage && key==="ArrowRight") { self.next(); - event.preventDefault(); + ev.preventDefault(); } else if(key==="Escape") { self.closeModal(); - event.preventDefault(); + ev.preventDefault(); } }); @@ -408,4 +472,115 @@ export class SphinxGalleria { this.node_mask.hidden = true; this.node_mask.setAttribute('aria-hidden', true); } + + startSwipe(ev) { + var self = this; + if(!self.oneimage && self.touch_data.id === -1) { + ev.preventDefault(); + if(ev.type === 'touchstart') { + self.touch_data.id = ev.changedTouches[0].identifier; + self.touch_data.x = ev.changedTouches[0].clientX; + self.touch_data.y = ev.changedTouches[1].clientY; + } else { + self.touch_data.id = -2; + self.touch_data.x = ev.clientX; + self.touch_data.y = ev.clientY; + } + self.touch_data.current_x = self.touch_data.x; + self.touch_data.current_y = self.touch_data.y; + + if(!self.touch_data.interval) { + self.touch_data.interval = setInterval(function() { + self.swipeItem(); + }, self.swipe_delay); + } + } + } + + moveSwipe(ev) { + var self = this; + var move = false; + + if(!self.oneimage && self.touch_data.x) { + ev.preventDefault(); + if(ev.type === 'touchmove') { + ev.changedTouches.forEach(function(touch) { + if(touch.identifier === self.touch_data.id) { + move = true; + self.touch_data.current_x = touch.clientX; + } + }); + } else if(ev.which > 0) { + move = true; + self.touch_data.current_x = ev.clientX; + } + } + } + + endSwipe(ev) { + var self = this; + var stop = false; + + if(!self.oneimage && self.touch_data.x) { + ev.preventDefault(); + if(ev.type === "touchend") { + ev.changedTouches.forEach(function(touch) { + if(touch.identifier === self.touch_data.id) { + stop = true; + self.touch_data.current_x = touch.clientX; + self.touch_data.current_y = touch.clientY; + } + }); + } else { + stop = true; + self.touch_data.current_x = ev.clientX; + self.touch_data.current_y = ev.clientY; + } + + if(stop) { + self.touch_data.id = -1; + if(self.touch_data.interval) { + clearInterval(self.touch_data.interval); + self.touch_data.interval = null; + } + if(!self.touch_data.swiped) { + self.swipeItem(); + } + + self.touch_data.x = null; + self.touch_data.y = null; + self.touch_data.current_x = null; + self.touch_data.current_y = null; + self.touch_data.swiped = false; + } + } + } + + swipeItem() { + var self = this; + var width = self.touch_data.current_x - self.touch_data.x; + var height = self.touch_data.current_y - self.touch_data.y; + + if(Math.abs(width) > self.getMinWidthSwipe()) { + if(width > 0) { + self.next(true); + self.touch_data.noclick = true; + self.touch_data.swiped = true; + } else { + self.prev(true); + self.touch_data.noclick = true; + self.touch_data.swiped = true; + } + } else if(Math.abs(height) > self.node_mask.clientHeight/2) { + self.closeModal(); + self.touch_data.noclick = true; + } + } + + getMinWidthSwipe() { + if(this.node_mask.hidden) { + return this.node_image.clientWidth/2; + } + return this.dialog_image.clientWidth/2; + } }