@pi0/framework7
Version:
Full featured mobile HTML framework for building iOS & Android apps
594 lines (547 loc) • 18.6 kB
JavaScript
/* eslint indent: ["off"] */
import $ from 'dom7';
import Utils from '../../utils/utils';
import Framework7Class from '../../utils/class';
class PhotoBrowser extends Framework7Class {
constructor(app, params = {}) {
super(params, [app]);
const pb = this;
pb.app = app;
const defaults = Utils.extend({
on: {},
}, app.modules.photoBrowser.params.photoBrowser);
// Extend defaults with modules params
pb.useModulesParams(defaults);
pb.params = Utils.extend(defaults, params);
Utils.extend(pb, {
exposed: false,
opened: false,
activeIndex: pb.params.swiper.initialSlide,
url: pb.params.url,
view: pb.params.view || app.views.main,
swipeToClose: {
allow: true,
isTouched: false,
diff: undefined,
start: undefined,
current: undefined,
started: false,
activeSlide: undefined,
timeStart: undefined,
},
});
// Install Modules
pb.useModules();
// Init
pb.init();
}
onTransitionStart(swiper) {
const pb = this;
pb.activeIndex = swiper.activeIndex;
let current = swiper.activeIndex + 1;
let total = swiper.slides.length;
if (swiper.params.loop) {
total -= 2;
current -= swiper.loopedSlides;
if (current < 1) current = total + current;
if (current > total) current -= total;
}
let $currentEl = pb.$containerEl.find('.photo-browser-current');
let $totalEl = pb.$containerEl.find('.photo-browser-total');
if (pb.params.type === 'page' && pb.params.navbar && $currentEl.length === 0 && pb.app.theme === 'ios') {
const navbarEl = pb.app.navbar.getElByPage(pb.$containerEl);
if (navbarEl) {
$currentEl = $(navbarEl).find('.photo-browser-current');
$totalEl = $(navbarEl).find('.photo-browser-total');
}
}
$currentEl.text(current);
$totalEl.text(total);
// Update captions
if (pb.captions.length > 0) {
const captionIndex = swiper.params.loop ? swiper.slides.eq(swiper.activeIndex).attr('data-swiper-slide-index') : pb.activeIndex;
pb.$captionsContainerEl.find('.photo-browser-caption-active').removeClass('photo-browser-caption-active');
pb.$captionsContainerEl.find(`[data-caption-index="${captionIndex}"]`).addClass('photo-browser-caption-active');
}
// Stop Video
const previousSlideVideo = swiper.slides.eq(swiper.previousIndex).find('video');
if (previousSlideVideo.length > 0) {
if ('pause' in previousSlideVideo[0]) previousSlideVideo[0].pause();
}
}
onTouchStart() {
const pb = this;
const swipeToClose = pb.swipeToClose;
if (!swipeToClose.allow) return;
swipeToClose.isTouched = true;
}
onTouchMove(e) {
const pb = this;
const swipeToClose = pb.swipeToClose;
if (!swipeToClose.isTouched) return;
if (!swipeToClose.started) {
swipeToClose.started = true;
swipeToClose.start = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
swipeToClose.activeSlide = pb.swiper.slides.eq(pb.swiper.activeIndex);
swipeToClose.timeStart = Utils.now();
}
e.preventDefault();
swipeToClose.current = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
swipeToClose.diff = swipeToClose.start - swipeToClose.current;
const opacity = 1 - (Math.abs(swipeToClose.diff) / 300);
const color = pb.exposed || pb.params.theme === 'dark' ? 0 : 255;
swipeToClose.activeSlide.transform(`translate3d(0,${-swipeToClose.diff}px,0)`);
pb.swiper.$el.css('background-color', `rgba(${color}, ${color}, ${color}, ${opacity})`).transition(0);
}
onTouchEnd() {
const pb = this;
const swipeToClose = pb.swipeToClose;
swipeToClose.isTouched = false;
if (!swipeToClose.started) {
swipeToClose.started = false;
return;
}
swipeToClose.started = false;
swipeToClose.allow = false;
const diff = Math.abs(swipeToClose.diff);
const timeDiff = (new Date()).getTime() - swipeToClose.timeStart;
if ((timeDiff < 300 && diff > 20) || (timeDiff >= 300 && diff > 100)) {
Utils.nextTick(() => {
if (pb.$containerEl) {
if (swipeToClose.diff < 0) pb.$containerEl.addClass('swipe-close-to-bottom');
else pb.$containerEl.addClass('swipe-close-to-top');
}
pb.emit('local::swipeToClose', pb);
pb.close();
swipeToClose.allow = true;
});
return;
}
if (diff !== 0) {
swipeToClose.activeSlide.addClass('photo-browser-transitioning').transitionEnd(() => {
swipeToClose.allow = true;
swipeToClose.activeSlide.removeClass('photo-browser-transitioning');
});
} else {
swipeToClose.allow = true;
}
pb.swiper.$el.transition('').css('background-color', '');
swipeToClose.activeSlide.transform('');
}
// Render Functions
renderNavbar() {
const pb = this;
if (pb.params.renderNavbar) return pb.params.renderNavbar.call(pb);
let iconsColor = pb.params.iconsColor;
if (!pb.params.iconsColor && pb.params.theme === 'dark') iconsColor = 'white';
const backLinkText = pb.app.theme === 'ios' && pb.params.backLinkText ? pb.params.backLinkText : '';
const isPopup = pb.params.type !== 'page';
const navbarHtml = `
<div class="navbar">
<div class="navbar-inner sliding">
<div class="left">
<a href="#" class="link ${isPopup ? 'popup-close' : ''} ${!backLinkText ? 'icon-only' : ''} ${!isPopup ? 'back' : ''}" ${isPopup ? 'data-popup=".photo-browser-popup"' : ''}>
<i class="icon icon-back ${iconsColor ? `color-${iconsColor}` : ''}"></i>
${backLinkText ? `<span>${backLinkText}</span>` : ''}
</a>
</div>
<div class="title">
<span class="photo-browser-current"></span>
<span class="photo-browser-of">${pb.params.navbarOfText}</span>
<span class="photo-browser-total"></span>
</div>
<div class="right"></div>
</div>
</div>
`.trim();
return navbarHtml;
}
renderToolbar() {
const pb = this;
if (pb.params.renderToolbar) return pb.params.renderToolbar.call(pb);
let iconsColor = pb.params.iconsColor;
if (!pb.params.iconsColor && pb.params.theme === 'dark') iconsColor = 'white';
const toolbarHtml = `
<div class="toolbar tabbar toolbar-bottom-md">
<div class="toolbar-inner">
<a href="#" class="link photo-browser-prev">
<i class="icon icon-back ${iconsColor ? `color-${iconsColor}` : ''}"></i>
</a>
<a href="#" class="link photo-browser-next">
<i class="icon icon-forward ${iconsColor ? `color-${iconsColor}` : ''}"></i>
</a>
</div>
</div>
`.trim();
return toolbarHtml;
}
renderCaption(caption, index) {
const pb = this;
if (pb.params.renderCaption) return pb.params.renderCaption.call(pb, caption, index);
const captionHtml = `
<div class="photo-browser-caption" data-caption-index="${index}">
${caption}
</div>
`.trim();
return captionHtml;
}
renderObject(photo, index) {
const pb = this;
if (pb.params.renderObject) return pb.params.renderObject.call(pb, photo, index);
const objHtml = `
<div class="photo-browser-slide photo-browser-object-slide swiper-slide">${photo.html ? photo.html : photo}</div>
`;
return objHtml;
}
renderLazyPhoto(photo, index) {
const pb = this;
if (pb.params.renderLazyPhoto) return pb.params.renderLazyPhoto.call(pb, photo, index);
const photoHtml = `
<div class="photo-browser-slide photo-browser-slide-lazy swiper-slide">
<div class="preloader swiper-lazy-preloader ${pb.params.theme === 'dark' ? 'color-white' : ''}"></div>
<span class="swiper-zoom-container">
<img data-src="${photo.url ? photo.url : photo}" class="swiper-lazy">
</span>
</div>
`.trim();
return photoHtml;
}
renderPhoto(photo, index) {
const pb = this;
if (pb.params.renderPhoto) return pb.params.renderPhoto.call(pb, photo, index);
const photoHtml = `
<div class="photo-browser-slide swiper-slide">
<span class="swiper-zoom-container">
<img src="${photo.url ? photo.url : photo}">
</span>
</div>
`.trim();
return photoHtml;
}
render() {
const pb = this;
if (pb.params.render) return pb.params.render.call(pb, pb.params);
const html = `
<div class="photo-browser photo-browser-${pb.params.theme}">
<div class="view">
<div class="page photo-browser-page photo-browser-page-${pb.params.theme} no-toolbar ${!pb.params.navbar ? 'no-navbar' : ''}" data-name="photo-browser-page">
${pb.params.navbar ? pb.renderNavbar() : ''}
${pb.params.toolbar ? pb.renderToolbar() : ''}
<div class="photo-browser-captions photo-browser-captions-${pb.params.captionsTheme || pb.params.theme}">
${pb.params.photos.filter(photo => photo.caption).map((photo, index) => pb.renderCaption(photo.caption, index)).join(' ')}
</div>
<div class="photo-browser-swiper-container swiper-container">
<div class="photo-browser-swiper-wrapper swiper-wrapper">
${pb.params.photos.map((photo, index) => {
if (photo.html || ((typeof photo === 'string' || photo instanceof String) && photo.indexOf('<') >= 0 && photo.indexOf('>') >= 0)) {
return pb.renderObject(photo, index);
} else if (pb.params.swiper.lazy && pb.params.swiper.lazy.enabled) {
return pb.renderLazyPhoto(photo, index);
}
return pb.renderPhoto(photo, index);
}).join(' ')}
</div>
</div>
</div>
</div>
</div>
`.trim();
return html;
}
renderStandalone() {
const pb = this;
if (pb.params.renderStandalone) return pb.params.renderStandalone.call(pb);
const standaloneHtml = `<div class="popup photo-browser-popup photo-browser-standalone popup-tablet-fullscreen">${pb.render()}</div>`;
return standaloneHtml;
}
renderPage() {
const pb = this;
if (pb.params.renderPage) return pb.params.renderPage.call(pb);
const pageHtml = pb.render();
return pageHtml;
}
renderPopup() {
const pb = this;
if (pb.params.renderPopup) return pb.params.renderPopup.call(pb);
const popupHtml = `<div class="popup photo-browser-popup">${pb.render()}</div>`;
return popupHtml;
}
// Callbacks
onOpen(type, containerEl) {
const pb = this;
const app = pb.app;
const $containerEl = $(containerEl);
$containerEl[0].f7PhotoBrowser = pb;
pb.$containerEl = $containerEl;
pb.openedIn = type;
pb.opened = true;
pb.$swiperContainerEl = pb.$containerEl.find('.photo-browser-swiper-container');
pb.$swiperWrapperEl = pb.$containerEl.find('.photo-browser-swiper-wrapper');
pb.slides = pb.$containerEl.find('.photo-browser-slide');
pb.$captionsContainerEl = pb.$containerEl.find('.photo-browser-captions');
pb.captions = pb.$containerEl.find('.photo-browser-caption');
// Init Swiper
const swiperSettings = Utils.extend({}, pb.params.swiper, {
initialSlide: pb.activeIndex,
on: {
tap(e) {
pb.emit('local::tap', e);
},
click(e) {
if (pb.params.exposition) {
pb.expositionToggle();
}
pb.emit('local::click', e);
},
doubleTap(e) {
pb.emit('local::doubleTap', e);
},
transitionStart() {
const swiper = this;
pb.onTransitionStart(swiper);
pb.emit('local::transitionStart', swiper);
},
transitionEnd() {
const swiper = this;
pb.emit('local::transitionEnd', swiper);
},
slideChangeStart() {
const swiper = this;
pb.emit('local::slideChangeStart', swiper);
},
slideChangeEnd() {
const swiper = this;
pb.emit('local::slideChangeEnd', swiper);
},
lazyImageLoad(slideEl, imgEl) {
pb.emit('local::lazyImageLoad', slideEl, imgEl);
},
lazyImageReady(slideEl, imgEl) {
$(slideEl).removeClass('photo-browser-slide-lazy');
pb.emit('local::lazyImageReady', slideEl, imgEl);
},
},
});
if (pb.params.swipeToClose && pb.params.type !== 'page') {
Utils.extend(swiperSettings.on, {
touchStart(e) {
pb.onTouchStart(e);
},
touchMoveOpposite(e) {
pb.onTouchMove(e);
},
touchEnd(e) {
pb.onTouchEnd(e);
},
});
}
pb.swiper = app.swiper.create(pb.$swiperContainerEl, swiperSettings);
if (pb.activeIndex === 0) {
pb.onTransitionStart(pb.swiper);
}
pb.emit('local::open photoBrowserOpen', pb);
}
onOpened() {
const pb = this;
pb.emit('local::opened photoBrowserOpened', pb);
}
onClose() {
const pb = this;
if (pb.destroyed) return;
// Destroy Swiper
if (pb.swiper && pb.swiper.destroy) {
pb.swiper.destroy(true, false);
pb.swiper = null;
delete pb.swiper;
}
pb.emit('local::close photoBrowserClose', pb);
}
onClosed() {
const pb = this;
if (pb.destroyed) return;
pb.opened = false;
pb.$containerEl = null;
delete pb.$containerEl;
pb.emit('local::closed photoBrowserClosed', pb);
}
// Open
openPage() {
const pb = this;
if (pb.opened) return pb;
const pageHtml = pb.renderPage();
pb.view.router.navigate(pb.url, {
createRoute: {
content: pageHtml,
path: pb.url,
options: {
pageEvents: {
pageBeforeIn(e, page) {
pb.view.$el.addClass(`with-photo-browser-page with-photo-browser-page-${pb.params.theme}`);
pb.onOpen('page', page.el);
},
pageAfterIn(e, page) {
pb.onOpened('page', page.el);
},
pageBeforeOut(e, page) {
pb.view.$el.removeClass(`with-photo-browser-page with-photo-browser-page-exposed with-photo-browser-page-${pb.params.theme}`);
pb.onClose('page', page.el);
},
pageAfterOut(e, page) {
pb.onClosed('page', page.el);
},
},
},
},
});
return pb;
}
openStandalone() {
const pb = this;
if (pb.opened) return pb;
const standaloneHtml = pb.renderStandalone();
const popupParams = {
backdrop: false,
content: standaloneHtml,
on: {
popupOpen(popup) {
pb.onOpen('popup', popup.el);
},
popupOpened(popup) {
pb.onOpened('popup', popup.el);
},
popupClose(popup) {
pb.onClose('popup', popup.el);
},
popupClosed(popup) {
pb.onClosed('popup', popup.el);
},
},
};
if (pb.params.routableModals) {
pb.view.router.navigate(pb.url, {
createRoute: {
path: pb.url,
popup: popupParams,
},
});
} else {
pb.modal = pb.app.popup.create(popupParams).open();
}
return pb;
}
openPopup() {
const pb = this;
if (pb.opened) return pb;
const popupHtml = pb.renderPopup();
const popupParams = {
content: popupHtml,
on: {
popupOpen(popup) {
pb.onOpen('popup', popup.el);
},
popupOpened(popup) {
pb.onOpened('popup', popup.el);
},
popupClose(popup) {
pb.onClose('popup', popup.el);
},
popupClosed(popup) {
pb.onClosed('popup', popup.el);
},
},
};
if (pb.params.routableModals) {
pb.view.router.navigate(pb.url, {
createRoute: {
path: pb.url,
popup: popupParams,
},
});
} else {
pb.modal = pb.app.popup.create(popupParams).open();
}
return pb;
}
// Exposition
expositionEnable() {
const pb = this;
if (pb.params.type === 'page') {
pb.view.$el.addClass('with-photo-browser-page-exposed');
}
if (pb.$containerEl) pb.$containerEl.addClass('photo-browser-exposed');
if (pb.params.expositionHideCaptions) pb.$captionsContainerEl.addClass('photo-browser-captions-exposed');
pb.exposed = true;
return pb;
}
expositionDisable() {
const pb = this;
if (pb.params.type === 'page') {
pb.view.$el.removeClass('with-photo-browser-page-exposed');
}
if (pb.$containerEl) pb.$containerEl.removeClass('photo-browser-exposed');
if (pb.params.expositionHideCaptions) pb.$captionsContainerEl.removeClass('photo-browser-captions-exposed');
pb.exposed = false;
return pb;
}
expositionToggle() {
const pb = this;
if (pb.params.type === 'page') {
pb.view.$el.toggleClass('with-photo-browser-page-exposed');
}
if (pb.$containerEl) pb.$containerEl.toggleClass('photo-browser-exposed');
if (pb.params.expositionHideCaptions) pb.$captionsContainerEl.toggleClass('photo-browser-captions-exposed');
pb.exposed = !pb.exposed;
return pb;
}
open(index) {
const pb = this;
const type = pb.params.type;
if (pb.opened) {
if (pb.swiper && typeof index !== 'undefined') {
pb.swiper.slideTo(parseInt(index, 10));
}
return pb;
} else if (typeof index !== 'undefined') {
pb.activeIndex = index;
}
if (type === 'standalone') {
pb.openStandalone();
}
if (type === 'page') {
pb.openPage();
}
if (type === 'popup') {
pb.openPopup();
}
return pb;
}
close() {
const pb = this;
if (!pb.opened) return pb;
if (pb.params.routableModals || pb.openedIn === 'page') {
if (pb.view) pb.view.router.back();
} else {
pb.modal.once('modalClosed', () => {
Utils.nextTick(() => {
pb.modal.destroy();
delete pb.modal;
});
});
pb.modal.close();
}
return pb;
}
// eslint-disable-next-line
init() {}
destroy() {
let pb = this;
pb.emit('local::beforeDestroy photoBrowserBeforeDestroy', pb);
if (pb.$containerEl) {
pb.$containerEl.trigger('photobrowser:beforedestroy');
delete pb.$containerEl[0].f7PhotoBrowser;
}
Utils.deleteProps(pb);
pb = null;
}
}
export default PhotoBrowser;