Merge pull request #1971 from umap-project/slideshow-module

chore: move slideshow to a module
This commit is contained in:
Yohan Boniface 2024-07-05 17:47:13 +02:00 committed by GitHub
commit 665656080c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 225 additions and 263 deletions

View file

@ -0,0 +1,69 @@
.umap-slideshow-toolbox {
position: absolute;
right: 0;
top: 0;
display: none;
}
.umap-slideshow-enabled .umap-slideshow-toolbox {
display: inline-block;
}
.umap-slideshow-toolbox li {
display: inline-block;
cursor: pointer;
font-size: 1.5em;
background-color: var(--background-color);
color: var(--text-color);
width: calc(var(--footer-height) * 2);
height: var(--footer-height);
line-height: var(--footer-height);
vertical-align: middle;
text-align: center;
}
.umap-slideshow-toolbox li + li {
border-left: 1px solid var(--color-light);
}
.umap-slideshow-toolbox li:hover {
background-color: var(--color-mediumGray);
}
.umap-slideshow-active .umap-slideshow-toolbox .play,
.umap-slideshow-toolbox .play {
width: calc(var(--footer-height) * 3);
text-align: left;
padding-left: 20px;
}
.umap-slideshow-toolbox .play:after {
content: '⏯︎';
}
.umap-slideshow-active .umap-slideshow-toolbox .play:after {
content: '⏸︎';
}
.umap-slideshow-toolbox .stop:before {
content: '⏹';
}
.umap-slideshow-toolbox .next:before {
content: '⏵︎';
}
.umap-slideshow-toolbox .prev:before {
content: '⏴︎';
}
.umap-slideshow-toolbox .play div {
height: 20px;
width: 20px;
margin: 0px auto;
position: relative;
top: 5px;
border-left: 3px solid rgba(255,255,239,.15);
border-right: 3px solid rgba(255,255,255,.15);
border-bottom: 3px solid rgba(255,255,255,.15);
border-top: 3px solid rgba(255,255,255,.8);
border-radius:100%;
display: inline-block;
visibility: hidden;
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(359deg);}
}
.umap-slideshow-active .umap-slideshow-toolbox .play .spinner {
visibility: visible;
}

View file

@ -13,6 +13,7 @@ import Orderable from './orderable.js'
import { HTTPError, NOKError, Request, RequestError, ServerRequest } from './request.js'
import Rules from './rules.js'
import { SCHEMA } from './schema.js'
import Slideshow from './slideshow.js'
import { SyncEngine } from './sync/engine.js'
import Dialog from './ui/dialog.js'
import { EditPanel, FullPanel, Panel } from './ui/panel.js'
@ -47,6 +48,7 @@ window.U = {
Rules,
SCHEMA,
ServerRequest,
Slideshow,
SyncEngine,
Tooltip,
URLs,

View file

@ -0,0 +1,137 @@
import { WithTemplate } from './utils.js'
import { translate } from './i18n.js'
const TOOLBOX_TEMPLATE = `
<ul class="umap-slideshow-toolbox dark">
<li class="play" title="${translate('Start slideshow')}" data-ref="play">
<div class="spinner" style="animation: none;"></div>
</li>
<li class="stop" title="${translate('Stop slideshow')}" data-ref="stop"></li>
<li class="prev" title="${translate('Zoom to the previous')}" data-ref="previous"></li>
<li class="next" title="${translate('Zoom to the next')}" data-ref="next"></li>
</ul>
`
export default class Slideshow extends WithTemplate {
constructor(map, options) {
super()
this.map = map
this._id = null
this.CLASSNAME = 'umap-slideshow-active'
this.options = Object.assign(
{
delay: 5000,
autoplay: false,
},
options
)
this._current = null
if (this.options.autoplay) {
this.map.onceDataLoaded(function () {
this.play()
}, this)
}
this.map.on(
'edit:enabled',
function () {
this.stop()
},
this
)
}
set current(feature) {
this._current = feature
}
get current() {
if (!this._current) {
const datalayer = this.defaultDatalayer()
if (datalayer) this._current = datalayer.getFeatureByIndex(0)
}
return this._current
}
get next() {
if (this._current === null) {
return this.current
}
return this.current.getNext()
}
defaultDatalayer() {
return this.map.findDataLayer((d) => d.canBrowse())
}
startSpinner() {
const time = Number.parseInt(this.options.delay, 10)
if (!time) return
const css = `rotation ${time / 1000}s infinite linear`
const spinner = document.querySelector('.umap-slideshow-toolbox .play .spinner')
spinner.style.animation = css
}
stopSpinner() {
const spinner = document.querySelector('.umap-slideshow-toolbox .play .spinner')
spinner.style.animation = 'none'
}
play() {
if (this._id) return
if (this.map.editEnabled || !this.map.options.slideshow.active) return
L.DomUtil.addClass(document.body, this.CLASSNAME)
this._id = window.setInterval(L.bind(this.loop, this), this.options.delay)
this.startSpinner()
this.loop()
}
loop() {
this.current = this.next
this.step()
}
pause() {
if (this._id) {
this.stopSpinner()
L.DomUtil.removeClass(document.body, this.CLASSNAME)
window.clearInterval(this._id)
this._id = null
}
}
stop() {
this.pause()
this.current = null
}
forward() {
this.pause()
this.current = this.next
this.step()
}
backward() {
this.pause()
if (this.current) this.current = this.current.getPrevious()
this.step()
}
step() {
if (!this.current) return this.stop()
this.current.zoomTo({ easing: this.options.easing })
this.current.view()
}
toggle() {
this._id ? this.pause() : this.play()
}
renderToolbox(container) {
container.appendChild(this.loadTemplate(TOOLBOX_TEMPLATE))
this.elements.play.addEventListener('click', this.toggle.bind(this))
this.elements.stop.addEventListener('click', this.stop.bind(this))
this.elements.previous.addEventListener('click', this.backward.bind(this))
this.elements.next.addEventListener('click', this.forward.bind(this))
}
}

View file

@ -1,4 +1,5 @@
import { translate } from '../i18n.js'
import { WithTemplate } from '../utils.js'
const TEMPLATE = `
<dialog data-ref="dialog">
@ -18,19 +19,6 @@ const TEMPLATE = `
</dialog>
`
class WithTemplate {
loadTemplate(html) {
const template = document.createElement('template')
template.innerHTML = html
this.element = template.content.firstElementChild
this.elements = {}
for (const element of this.element.querySelectorAll('[data-ref]')) {
this.elements[element.dataset.ref] = element
}
return this.element
}
}
// From https://css-tricks.com/replace-javascript-dialogs-html-dialog-element/
export default class Dialog extends WithTemplate {
constructor(settings = {}) {

View file

@ -377,3 +377,16 @@ export function toggleBadge(element, value) {
if (value) element.dataset.badge = value === true ? ' ' : value
else delete element.dataset.badge
}
export class WithTemplate {
loadTemplate(html) {
const template = document.createElement('template')
template.innerHTML = html.split('\n').map((line) => line.trim()).join('')
this.element = template.content.firstElementChild
this.elements = {}
for (const element of this.element.querySelectorAll('[data-ref]')) {
this.elements[element.dataset.ref] = element
}
return this.element
}
}

View file

@ -1,163 +0,0 @@
U.Slideshow = L.Class.extend({
statics: {
CLASSNAME: 'umap-slideshow-active',
},
options: {
delay: 5000,
autoplay: false,
},
initialize: function (map, options) {
this.setOptions(options)
this.map = map
this._id = null
// current feature
let current = null
try {
Object.defineProperty(this, 'current', {
get: function () {
if (!current) {
const datalayer = this.defaultDatalayer()
if (datalayer) current = datalayer.getFeatureByIndex(0)
}
return current
},
set: (feature) => {
current = feature
},
})
} catch (e) {
// Certainly IE8, which has a limited version of defineProperty
}
try {
Object.defineProperty(this, 'next', {
get: () => {
if (!current) {
return this.current
}
return current.getNext()
},
})
} catch (e) {
// Certainly IE8, which has a limited version of defineProperty
}
if (this.options.autoplay) {
this.map.onceDataLoaded(function () {
this.play()
}, this)
}
this.map.on(
'edit:enabled',
function () {
this.stop()
},
this
)
},
setOptions: function (options) {
L.setOptions(this, options)
this.timeSpinner()
},
defaultDatalayer: function () {
return this.map.findDataLayer((d) => d.canBrowse())
},
timeSpinner: function () {
const time = Number.parseInt(this.options.delay, 10)
if (!time) return
const css = `rotation ${time / 1000}s infinite linear`
const spinners = document.querySelectorAll('.umap-slideshow-toolbox .play .spinner')
for (let i = 0; i < spinners.length; i++) {
spinners[i].style.animation = css
spinners[i].style['-webkit-animation'] = css
spinners[i].style['-moz-animation'] = css
spinners[i].style['-o-animation'] = css
}
},
resetSpinners: () => {
// Make that animnation is coordinated with user actions
const spinners = document.querySelectorAll('.umap-slideshow-toolbox .play .spinner')
let el
let newOne
for (let i = 0; i < spinners.length; i++) {
el = spinners[i]
newOne = el.cloneNode(true)
el.parentNode.replaceChild(newOne, el)
}
},
play: function () {
if (this._id) return
if (this.map.editEnabled || !this.map.options.slideshow.active) return
L.DomUtil.addClass(document.body, U.Slideshow.CLASSNAME)
this._id = window.setInterval(L.bind(this.loop, this), this.options.delay)
this.resetSpinners()
this.loop()
},
loop: function () {
this.current = this.next
this.step()
},
pause: function () {
if (this._id) {
L.DomUtil.removeClass(document.body, U.Slideshow.CLASSNAME)
window.clearInterval(this._id)
this._id = null
}
},
stop: function () {
this.pause()
this.current = null
},
forward: function () {
this.pause()
this.current = this.next
this.step()
},
backward: function () {
this.pause()
if (this.current) this.current = this.current.getPrevious()
this.step()
},
step: function () {
if (!this.current) return this.stop()
this.current.zoomTo({ easing: this.options.easing })
this.current.view()
},
renderToolbox: function (container) {
const box = L.DomUtil.create('ul', 'umap-slideshow-toolbox')
const play = L.DomUtil.create('li', 'play', box)
const stop = L.DomUtil.create('li', 'stop', box)
const prev = L.DomUtil.create('li', 'prev', box)
const next = L.DomUtil.create('li', 'next', box)
L.DomUtil.create('div', 'spinner', play)
play.title = L._('Start slideshow')
stop.title = L._('Stop slideshow')
next.title = L._('Zoom to the next')
prev.title = L._('Zoom to the previous')
const toggle = function () {
if (this._id) this.pause()
else this.play()
}
L.DomEvent.on(play, 'click', L.DomEvent.stop).on(play, 'click', toggle, this)
L.DomEvent.on(stop, 'click', L.DomEvent.stop).on(stop, 'click', this.stop, this)
L.DomEvent.on(prev, 'click', L.DomEvent.stop).on(prev, 'click', this.backward, this)
L.DomEvent.on(next, 'click', L.DomEvent.stop).on(next, 'click', this.forward, this)
container.appendChild(box)
this.timeSpinner()
return box
},
})

View file

@ -677,91 +677,7 @@ ul.photon-autocomplete {
.umap-help {
font-style: italic;
}
.umap-slideshow-toolbox {
position: absolute;
right: 0;
top: 0;
display: none;
}
.umap-slideshow-enabled .umap-slideshow-toolbox {
display: inline-block;
}
.umap-slideshow-toolbox li {
display: inline-block;
cursor: pointer;
font-size: 1.5em;
background-color: #464646;
color: #fff;
width: calc(var(--footer-height) * 2);
height: var(--footer-height);
line-height: var(--footer-height);
vertical-align: middle;
text-align: center;
}
.umap-slideshow-toolbox li + li {
border-left: 1px solid #aaa;
}
.umap-slideshow-toolbox li:hover {
background-color: #666;
}
.umap-slideshow-active .umap-slideshow-toolbox .play,
.umap-slideshow-toolbox .play {
width: calc(var(--footer-height) * 3);
text-align: left;
padding-left: 20px;
}
.umap-slideshow-toolbox .play:after {
content: '⏯︎';
}
.umap-slideshow-active .umap-slideshow-toolbox .play:after {
content: '⏸︎';
}
.umap-slideshow-toolbox .stop:before {
content: '⏹';
}
.umap-slideshow-toolbox .next:before {
content: '⏵︎';
}
.umap-slideshow-toolbox .prev:before {
content: '⏴︎';
}
.umap-slideshow-toolbox .play div {
height: 20px;
width: 20px;
margin: 0px auto;
position: relative;
top: 5px;
-webkit-animation: rotation 5s infinite linear;
-moz-animation: rotation 5s infinite linear;
-o-animation: rotation 5s infinite linear;
animation: rotation 5s infinite linear;
border-left: 3px solid rgba(255,255,239,.15);
border-right: 3px solid rgba(255,255,255,.15);
border-bottom: 3px solid rgba(255,255,255,.15);
border-top: 3px solid rgba(255,255,255,.8);
border-radius:100%;
display: inline-block;
visibility: hidden;
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(359deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(359deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(359deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(359deg);}
}
.umap-slideshow-active .umap-slideshow-toolbox .play .spinner {
visibility: visible;
}
.umap-datalayer-version {
padding: 5px 0;
border-bottom: 1px solid #202425;
@ -776,7 +692,6 @@ ul.photon-autocomplete {
margin-right: 10px;
}
.leaflet-toolbar-tip {
background-color: var(--color-darkGray);
}

View file

@ -3,6 +3,7 @@
--color-waterMint: #B9F5D2;
--color-darkBlue: #263B58;
--color-lightGray: #ddd;
--color-mediumGray: #3e4444;
--color-darkGray: #323737;
--color-light: white;
--color-limeGreen: #b9f5d2;

View file

@ -28,6 +28,7 @@
<link rel="stylesheet" href="{% static 'umap/content.css' %}" />
<link rel="stylesheet" href="{% static 'umap/nav.css' %}" />
<link rel="stylesheet" href="{% static 'umap/map.css' %}" />
<link rel="stylesheet" href="{% static 'umap/css/slideshow.css' %}" />
<link rel="stylesheet" href="{% static 'umap/css/panel.css' %}" />
<link rel="stylesheet" href="{% static 'umap/css/window.css' %}" />
<link rel="stylesheet" href="{% static 'umap/css/tooltip.css' %}" />

View file

@ -54,7 +54,6 @@
<script src="{% static 'umap/js/umap.datalayer.permissions.js' %}" defer></script>
<script src="{% static 'umap/js/umap.layer.js' %}" defer></script>
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
<script src="{% static 'umap/js/umap.slideshow.js' %}" defer></script>
<script src="{% static 'umap/js/umap.tableeditor.js' %}" defer></script>
<script src="{% static 'umap/js/umap.share.js' %}" defer></script>
<script src="{% static 'umap/js/umap.js' %}" defer></script>