Frontent dev env (#247)

* Added frontend development files/environment

* More items-categories related removals

* Improvements in pages templates (inc. static pages)

* Improvements in video player

* Added empty home page message + cta

* Updates in media, playlist and management pages

* Improvements in material icons font loading

* Replaced media & playlists links in frontend dev-env

* frontend package version update

* chnaged frontend dev url port

* static files update

* Changed default position of theme switcher

* enabled frontend docker container
This commit is contained in:
Yiannis Stergiou
2021-07-11 18:01:34 +03:00
committed by GitHub
parent 060bb45725
commit aa6520daac
555 changed files with 201927 additions and 66002 deletions

View File

@@ -0,0 +1,73 @@
import React, { useEffect } from 'react';
import { PageStore } from '../../utils/stores/';
import { useLayout, useItemListInlineSlider } from '../../utils/hooks/';
import { ItemsStaticListHandler } from './includes/itemLists/ItemsStaticListHandler';
import { ItemList } from './ItemList';
import { PendingItemsList } from './PendingItemsList';
import { ListItem, listItemProps } from '../list-item/ListItem';
export function InlineSliderItemList(props) {
const { visibleSidebar } = useLayout();
const [
items,
countedItems,
listHandler,
classname,
setListHandler,
onItemsCount,
onItemsLoad,
winResizeListener,
sidebarVisibilityChangeListener,
itemsListWrapperRef,
itemsListRef,
renderBeforeListWrap,
renderAfterListWrap,
] = useItemListInlineSlider(props);
useEffect(() => {
sidebarVisibilityChangeListener();
}, [visibleSidebar]);
useEffect(() => {
setListHandler(new ItemsStaticListHandler(props.items, props.pageItems, props.maxItems, onItemsCount, onItemsLoad));
PageStore.on('window_resize', winResizeListener);
return () => {
PageStore.removeListener('window_resize', winResizeListener);
if (listHandler) {
listHandler.cancelAll();
setListHandler(null);
}
};
}, []);
return !countedItems ? (
<PendingItemsList className={classname.listOuter} />
) : !items.length ? null : (
<div className={classname.listOuter}>
{renderBeforeListWrap()}
<div ref={itemsListWrapperRef} className="items-list-wrap">
<div ref={itemsListRef} className={classname.list}>
{items.map((itm, index) => (
<ListItem key={index} {...listItemProps(props, itm, index)} />
))}
</div>
</div>
{renderAfterListWrap()}
</div>
);
}
InlineSliderItemList.propTypes = {
...ItemList.propTypes,
};
InlineSliderItemList.defaultProps = {
...ItemList.defaultProps,
pageItems: 12,
};

View File

@@ -0,0 +1,82 @@
import React, { useEffect } from 'react';
import { PageStore } from '../../utils/stores/';
import { useLayout, useItemListInlineSlider } from '../../utils/hooks/';
import { ItemListAsync } from './ItemListAsync';
import { PendingItemsList } from './PendingItemsList';
import { ListItem, listItemProps } from '../list-item/ListItem';
import { ItemsListHandler } from './includes/itemLists/ItemsListHandler';
export function InlineSliderItemListAsync(props) {
const { visibleSidebar } = useLayout();
const [
items,
countedItems,
listHandler,
classname,
setListHandler,
onItemsCount,
onItemsLoad,
winResizeListener,
sidebarVisibilityChangeListener,
itemsListWrapperRef,
itemsListRef,
renderBeforeListWrap,
renderAfterListWrap,
] = useItemListInlineSlider(props);
useEffect(() => {
sidebarVisibilityChangeListener();
}, [visibleSidebar]);
useEffect(() => {
setListHandler(
new ItemsListHandler(
props.pageItems,
props.maxItems,
props.firstItemRequestUrl,
props.requestUrl,
onItemsCount,
onItemsLoad
)
);
PageStore.on('window_resize', winResizeListener);
return () => {
PageStore.removeListener('window_resize', winResizeListener);
if (listHandler) {
listHandler.cancelAll();
setListHandler(null);
}
};
}, []);
return !countedItems ? (
<PendingItemsList className={classname.listOuter} />
) : !items.length ? null : (
<div className={classname.listOuter}>
{renderBeforeListWrap()}
<div ref={itemsListWrapperRef} className="items-list-wrap">
<div ref={itemsListRef} className={classname.list}>
{items.map((itm, index) => (
<ListItem key={index} {...listItemProps(props, itm, index)} />
))}
</div>
</div>
{renderAfterListWrap()}
</div>
);
}
InlineSliderItemListAsync.propTypes = {
...ItemListAsync.propTypes,
};
InlineSliderItemListAsync.defaultProps = {
...ItemListAsync.defaultProps,
pageItems: 12,
};

View File

@@ -0,0 +1,105 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useItemListSync } from '../../utils/hooks/';
import { PositiveIntegerOrZero } from '../../utils/helpers';
import { PendingItemsList } from './PendingItemsList';
import { ListItem, listItemProps } from '../list-item/ListItem';
import { ItemsStaticListHandler } from './includes/itemLists/ItemsStaticListHandler';
export function ItemList(props) {
const [
countedItems,
items,
listHandler,
setListHandler,
classname,
itemsListWrapperRef,
itemsListRef,
onItemsCount,
onItemsLoad,
renderBeforeListWrap,
renderAfterListWrap,
] = useItemListSync(props);
useEffect(() => {
setListHandler(new ItemsStaticListHandler(props.items, props.pageItems, props.maxItems, onItemsCount, onItemsLoad));
return () => {
if (listHandler) {
listHandler.cancelAll();
setListHandler(null);
}
};
}, []);
return !countedItems ? (
<PendingItemsList className={classname.listOuter} />
) : !items.length ? null : (
<div className={classname.listOuter}>
{renderBeforeListWrap()}
<div ref={itemsListWrapperRef} className="items-list-wrap">
<div ref={itemsListRef} className={classname.list}>
{items.map((itm, index) => (
<ListItem key={index} {...listItemProps(props, itm, index)} />
))}
</div>
</div>
{renderAfterListWrap()}
</div>
);
}
ItemList.propTypes = {
items: PropTypes.array.isRequired,
className: PropTypes.string,
hideDate: PropTypes.bool,
hideViews: PropTypes.bool,
hideAuthor: PropTypes.bool,
hidePlaylistOptions: PropTypes.bool,
hidePlaylistOrderNumber: PropTypes.bool,
hideAllMeta: PropTypes.bool,
preferSummary: PropTypes.bool,
inPlaylistView: PropTypes.bool,
inPlaylistPage: PropTypes.bool,
playlistActiveItem: PositiveIntegerOrZero,
playlistId: PropTypes.string,
/* ################################################## */
maxItems: PropTypes.number.isRequired,
pageItems: PropTypes.number.isRequired,
horizontalItemsOrientation: PropTypes.bool.isRequired,
singleLinkContent: PropTypes.bool.isRequired,
inTagsList: PropTypes.bool,
inCategoriesList: PropTypes.bool,
itemsCountCallback: PropTypes.func,
itemsLoadCallback: PropTypes.func,
firstItemViewer: PropTypes.bool,
firstItemDescr: PropTypes.bool,
canEdit: PropTypes.bool,
};
ItemList.defaultProps = {
hideDate: false,
hideViews: false,
hideAuthor: false,
hidePlaylistOptions: true,
hidePlaylistOrderNumber: true,
hideAllMeta: false,
preferSummary: false,
inPlaylistView: false,
inPlaylistPage: false,
playlistActiveItem: 1,
playlistId: void 0,
/* ################################################## */
maxItems: 99999,
// pageItems: 48,
pageItems: 24,
horizontalItemsOrientation: false,
singleLinkContent: false,
inTagsList: false,
inCategoriesList: false,
firstItemViewer: false,
firstItemDescr: false,
canEdit: false,
};

View File

@@ -0,0 +1,149 @@
@import '../../../css/includes/_variables.scss';
@import '../../../css/includes/_variables_dimensions.scss';
@import '../../../css/config/index.scss';
.items-list-outer {
position: relative;
display: block;
&.list-inline.list-slider {
margin: 0 8px;
.previous-slide,
.next-slide {
position: absolute;
z-index: +1;
top: 0;
bottom: 0;
padding-top: 28.125%;
.circle-icon-button {
margin-top: -20px;
}
}
.previous-slide {
left: -12px;
}
.next-slide {
right: -12px;
}
}
@media (min-width: 420px) {
&.list-inline.list-slider {
margin: 0;
.previous-slide {
left: -20px;
}
.next-slide {
right: -20px;
}
}
}
@media (min-width: 600px) {
&.list-inline.list-slider {
.previous-slide,
.next-slide {
padding-top: calc(0.28125 * calc(var(--item-width, var(--default-item-width))));
}
.next-slide {
right: calc(-20px + var(--item-margin-right-width, var(--default-item-margin-right-width)));
}
}
}
}
.items-list-header,
.media-list-header {
display: block;
padding: 12px 0;
h2,
h3 {
display: inline-block;
margin: 12px 0;
font-weight: 500;
}
h2 {
font-size: 16px;
line-height: 1.25;
}
h3 {
font-size: 14px;
a {
margin: 10px 16px;
text-decoration: none;
color: var(--media-list-header-title-link-text-color);
}
}
}
.items-list-wrap {
position: relative;
display: block;
min-height: 218px;
.list-inline & {
overflow: auto;
white-space: nowrap;
will-change: width, scroll-position, scroll-behavior;
.item {
display: inline-block;
}
}
.list-slider & {
overflow: hidden;
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
}
}
.list-slider .items-list-wrap.resizing {
scroll-behavior: unset;
}
.items-list {
max-width: 100%;
word-break: break-word;
img,
picture {
display: block;
width: 100%;
border: 0;
}
}
button.load-more {
font-size: 13px;
font-weight: 500;
letter-spacing: 0.007px;
margin: 0 auto 24px 0;
padding: 0;
border: 0;
background: none;
color: var(--item-list-load-more-text-color);
&:hover,
&:focus {
color: var(--item-list-load-more-hover-text-color);
box-shadow: none;
}
}
@import '../list-item/Item.scss';
@import '../list-item/ItemVertical.scss';
@import '../list-item/ItemHorizontal.scss';

View File

@@ -0,0 +1,75 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useItemListSync } from '../../utils/hooks/';
import { ItemList } from './ItemList';
import { PendingItemsList } from './PendingItemsList';
import { ListItem, listItemProps } from '../list-item/ListItem';
import { ItemsListHandler } from './includes/itemLists/ItemsListHandler';
export function ItemListAsync(props) {
const [
countedItems,
items,
listHandler,
setListHandler,
classname,
itemsListWrapperRef,
itemsListRef,
onItemsCount,
onItemsLoad,
renderBeforeListWrap,
renderAfterListWrap,
] = useItemListSync(props);
useEffect(() => {
setListHandler(
new ItemsListHandler(
props.pageItems,
props.maxItems,
props.firstItemRequestUrl,
props.requestUrl,
onItemsCount,
onItemsLoad
)
);
return () => {
if (listHandler) {
listHandler.cancelAll();
setListHandler(null);
}
};
}, []);
return !countedItems ? (
<PendingItemsList className={classname.listOuter} />
) : !items.length ? null : (
<div className={classname.listOuter}>
{renderBeforeListWrap()}
<div ref={itemsListWrapperRef} className="items-list-wrap">
<div ref={itemsListRef} className={classname.list}>
{items.map((itm, index) => (
<ListItem key={index} {...listItemProps(props, itm, index)} />
))}
</div>
</div>
{renderAfterListWrap()}
</div>
);
}
ItemListAsync.propTypes = {
...ItemList.propTypes,
items: PropTypes.array, // Reset 'isRequired' feature.
requestUrl: PropTypes.string.isRequired,
firstItemRequestUrl: PropTypes.string,
};
ItemListAsync.defaultProps = {
...ItemList.defaultProps,
requestUrl: null,
firstItemRequestUrl: null,
pageItems: 24,
};

View File

@@ -0,0 +1,71 @@
import React, { useEffect } from 'react';
import { PageStore } from '../../utils/stores/';
import { useItemListLazyLoad } from '../../utils/hooks/';
import { ItemList } from './ItemList';
import { PendingItemsList } from './PendingItemsList';
import { ListItem, listItemProps } from '../list-item/ListItem';
import { ItemsStaticListHandler } from './includes/itemLists/ItemsStaticListHandler';
export function LazyLoadItemList(props) {
const [
items,
countedItems,
listHandler,
setListHandler,
classname,
onItemsCount,
onItemsLoad,
onWindowScroll,
onDocumentVisibilityChange,
itemsListWrapperRef,
itemsListRef,
renderBeforeListWrap,
renderAfterListWrap,
] = useItemListLazyLoad(props);
useEffect(() => {
setListHandler(new ItemsStaticListHandler(props.items, props.pageItems, props.maxItems, onItemsCount, onItemsLoad));
PageStore.on('window_scroll', onWindowScroll);
PageStore.on('document_visibility_change', onDocumentVisibilityChange);
onWindowScroll();
return () => {
PageStore.removeListener('window_scroll', onWindowScroll);
PageStore.removeListener('document_visibility_change', onDocumentVisibilityChange);
if (listHandler) {
listHandler.cancelAll();
setListHandler(null);
}
};
}, []);
return !countedItems ? (
<PendingItemsList className={classname.listOuter} />
) : !items.length ? null : (
<div className={classname.listOuter}>
{renderBeforeListWrap()}
<div ref={itemsListWrapperRef} className="items-list-wrap">
<div ref={itemsListRef} className={classname.list}>
{items.map((itm, index) => (
<ListItem key={index} {...listItemProps(props, itm, index)} />
))}
</div>
</div>
{renderAfterListWrap()}
</div>
);
}
LazyLoadItemList.propTypes = {
...ItemList.propTypes,
};
LazyLoadItemList.defaultProps = {
...ItemList.defaultProps,
pageItems: 2,
};

View File

@@ -0,0 +1,80 @@
import React, { useEffect } from 'react';
import { PageStore } from '../../utils/stores/';
import { useItemListLazyLoad } from '../../utils/hooks/';
import { ItemListAsync } from './ItemListAsync';
import { PendingItemsList } from './PendingItemsList';
import { ListItem, listItemProps } from '../list-item/ListItem';
import { ItemsListHandler } from './includes/itemLists/ItemsListHandler';
export function LazyLoadItemListAsync(props) {
const [
items,
countedItems,
listHandler,
setListHandler,
classname,
onItemsCount,
onItemsLoad,
onWindowScroll,
onDocumentVisibilityChange,
itemsListWrapperRef,
itemsListRef,
renderBeforeListWrap,
renderAfterListWrap,
] = useItemListLazyLoad(props);
useEffect(() => {
setListHandler(
new ItemsListHandler(
props.pageItems,
props.maxItems,
props.firstItemRequestUrl,
props.requestUrl,
onItemsCount,
onItemsLoad
)
);
PageStore.on('window_scroll', onWindowScroll);
PageStore.on('document_visibility_change', onDocumentVisibilityChange);
onWindowScroll();
return () => {
PageStore.removeListener('window_scroll', onWindowScroll);
PageStore.removeListener('document_visibility_change', onDocumentVisibilityChange);
if (listHandler) {
listHandler.cancelAll();
setListHandler(null);
}
};
}, []);
return !countedItems ? (
<PendingItemsList className={classname.listOuter} />
) : !items.length ? null : (
<div className={classname.listOuter}>
{renderBeforeListWrap()}
<div ref={itemsListWrapperRef} className="items-list-wrap">
<div ref={itemsListRef} className={classname.list}>
{items.map((itm, index) => (
<ListItem key={index} {...listItemProps(props, itm, index)} />
))}
</div>
</div>
{renderAfterListWrap()}
</div>
);
}
LazyLoadItemListAsync.propTypes = {
...ItemListAsync.propTypes,
};
LazyLoadItemListAsync.defaultProps = {
...ItemListAsync.defaultProps,
pageItems: 2,
};

View File

@@ -0,0 +1,12 @@
import React from 'react';
import { SpinnerLoader } from '../_shared/';
export function PendingItemsList(props) {
return (
<div className={props.className}>
<div className="items-list-wrap items-list-wrap-waiting">
<SpinnerLoader />
</div>
</div>
);
}

View File

@@ -0,0 +1,122 @@
function calcCurrentSlide(wrapperDom, itemWidth, currentSlide) {
return wrapperDom.scrollLeft ? 1 + Math.ceil(wrapperDom.scrollLeft / itemWidth) : currentSlide;
}
export default function ItemsInlineSlider(container, itemSelector) {
if (void 0 === container) {
return;
}
this.data = {
dom: {
wrapper: container,
firstItem: container.querySelector(itemSelector),
},
item: {
// selector: itemSelector,
width: null,
},
};
this.data.item.width = this.data.dom.firstItem.offsetWidth;
this.state = {
initedAllStateValues: false,
currentSlide: 1,
maxSlideIndex: null,
slideItemsFit: null,
slideItems: null,
totalItems: null,
wrapper: {
width: null,
scrollWidth: null,
},
};
}
ItemsInlineSlider.prototype.updateDataStateOnResize = function (totalItems, itemsLoadedAll) {
this.data.item.width = this.data.dom.firstItem.offsetWidth;
this.state.wrapper.width = this.data.dom.wrapper.offsetWidth;
this.state.wrapper.scrollWidth = this.data.dom.wrapper.scrollWidth;
this.state.slideItemsFit = Math.floor(this.state.wrapper.width / this.data.item.width);
this.state.slideItems = Math.max(1, this.state.slideItemsFit);
if (itemsLoadedAll && this.state.slideItems <= this.state.slideItemsFit) {
this.state.itemsLengthFit = this.state.slideItems;
}
this.state.totalItems = totalItems;
this.state.maxSlideIndex = Math.max(1 + (this.state.totalItems - this.state.slideItemsFit));
this.state.currentSlide = Math.min(this.state.currentSlide, this.state.maxSlideIndex || 1);
this.state.currentSlide = 0 >= this.state.currentSlide ? 1 : this.state.currentSlide;
};
ItemsInlineSlider.prototype.updateDataState = function (totalItems, itemsLoadedAll, forcedRefresh) {
if (forcedRefresh || !this.state.initedAllStateValues) {
this.state.initedAllStateValues = true;
this.state.wrapper.width = this.data.dom.wrapper.offsetWidth;
this.state.wrapper.scrollWidth = this.data.dom.wrapper.scrollWidth;
this.state.slideItemsFit = Math.floor(this.state.wrapper.width / this.data.item.width);
this.state.slideItems = Math.max(1, this.state.slideItemsFit);
if (itemsLoadedAll && this.state.slideItems <= this.state.slideItemsFit) {
this.state.itemsLengthFit = this.state.slideItems;
}
}
this.state.totalItems = totalItems;
this.state.maxSlideIndex = Math.max(1, 1 + (this.state.totalItems - this.state.slideItemsFit));
this.state.currentSlide = Math.min(this.state.currentSlide, this.state.maxSlideIndex);
this.state.currentSlide = 0 >= this.state.currentSlide ? 1 : this.state.currentSlide;
};
ItemsInlineSlider.prototype.nextSlide = function () {
this.state.currentSlide = Math.min(
calcCurrentSlide(this.data.dom.wrapper, this.data.item.width, this.state.currentSlide) + this.state.slideItems,
this.state.maxSlideIndex
);
};
ItemsInlineSlider.prototype.previousSlide = function () {
this.state.currentSlide = Math.max(
1,
calcCurrentSlide(this.data.dom.wrapper, this.data.item.width, this.state.currentSlide) - this.state.slideItems
);
};
ItemsInlineSlider.prototype.scrollToCurrentSlide = function () {
this.data.dom.wrapper.scrollLeft = this.data.item.width * (this.state.currentSlide - 1);
};
ItemsInlineSlider.prototype.hasNextSlide = function () {
return this.state.currentSlide < this.state.maxSlideIndex;
};
ItemsInlineSlider.prototype.hasPreviousSlide = function () {
return 1 < this.state.currentSlide;
};
ItemsInlineSlider.prototype.currentSlide = function () {
return this.state.currentSlide;
};
ItemsInlineSlider.prototype.loadItemsToFit = function () {
// Set slider minimum items length ( 2 * this.state.slideItemsFit ).
return 2 * this.state.slideItemsFit > this.state.totalItems;
};
ItemsInlineSlider.prototype.loadMoreItems = function () {
return this.state.currentSlide + this.state.slideItemsFit >= this.state.maxSlideIndex;
};
ItemsInlineSlider.prototype.itemsFit = function () {
return this.state.slideItemsFit;
};

View File

@@ -0,0 +1,166 @@
import { PageStore } from '../../../../utils/stores/';
import { formatInnerLink, getRequest } from '../../../../utils/helpers/';
export function ItemsListHandler(
itemsPerPage,
maxItems,
first_item_request_url,
request_url,
itemsCountCallback,
loadItemsCallback
) {
const config = {
maxItems: maxItems || 255,
pageItems: itemsPerPage ? Math.min(maxItems, itemsPerPage) : 1,
};
const state = {
totalItems: 0,
totalPages: 0,
nextRequestUrl: formatInnerLink(request_url, PageStore.get('config-site').url),
};
const waiting = {
pageItems: 0,
requestResponse: false,
};
let firstItemUrl = null;
const items = [];
const responseItems = [];
const callbacks = {
itemsCount: function () {
if ('function' === typeof itemsCountCallback) {
itemsCountCallback(state.totalItems);
}
},
itemsLoad: function () {
if ('function' === typeof loadItemsCallback) {
loadItemsCallback(items);
}
},
};
function loadNextItems(itemsLength) {
let itemsToLoad, needExtraRequest;
itemsLength = !isNaN(itemsLength) ? itemsLength : config.pageItems;
if (waiting.pageItems && waiting.pageItems <= responseItems.length) {
itemsToLoad = waiting.pageItems;
needExtraRequest = false;
waiting.pageItems = 0;
} else {
itemsToLoad = Math.min(itemsLength, responseItems.length);
needExtraRequest = itemsLength > responseItems.length && !!state.nextRequestUrl;
waiting.pageItems = needExtraRequest ? itemsLength - responseItems.length : 0;
}
if (itemsToLoad) {
let i = 0;
while (i < itemsToLoad) {
items.push(responseItems.shift());
i += 1;
}
callbacks.itemsLoad();
}
if (needExtraRequest) {
runRequest();
}
}
function runFirstItemRequest() {
function fn(response) {
if (!!!response || !!!response.data) {
} else {
let data = response.data;
let results = void 0 !== data.results ? data.results : data; // NOTE: The structure of response data in case of categories differs from the others.
if (results.length) {
firstItemUrl = results[0].url;
items.push(results[0]);
}
}
runRequest(true);
}
getRequest(formatInnerLink(first_item_request_url, PageStore.get('config-site').url), false, fn);
}
function runRequest(initialRequest) {
waiting.requestResponse = true;
function fn(response) {
waiting.requestResponse = false;
if (!!!response || !!!response.data) {
return;
}
let data = response.data;
let results = void 0 !== data.results ? data.results : data; // NOTE: The structure of response data in case of categories differs from the others.
let i = 0;
while (i < results.length && config.maxItems > responseItems.length) {
if (null === firstItemUrl || firstItemUrl !== results[i].url) {
responseItems.push(results[i]);
}
i += 1;
}
state.nextRequestUrl = !!data.next && config.maxItems > responseItems.length ? data.next : null;
if (initialRequest) {
// In some cases, (total) 'count' field is missing, but probably doesn't need (eg. in recommended media).
state.totalItems = !!data.count ? data.count : responseItems.length;
state.totalItems = Math.min(config.maxItems, state.totalItems);
state.totalPages = Math.ceil(state.totalItems / config.pageItems);
callbacks.itemsCount();
}
loadNextItems();
}
getRequest(state.nextRequestUrl, false, fn);
state.nextRequestUrl = null;
}
function loadItems(itemsLength) {
if (!waiting.requestResponse && items.length < state.totalItems) {
loadNextItems(itemsLength);
}
}
function totalPages() {
return state.totalPages;
}
function loadedAllItems() {
return items.length === state.totalItems;
}
function cancelAll() {
itemsCountCallback = null;
loadItemsCallback = null;
}
if (void 0 !== first_item_request_url && null !== first_item_request_url) {
runFirstItemRequest();
} else {
runRequest(true);
}
return {
loadItems,
totalPages,
loadedAllItems,
cancelAll,
};
}

View File

@@ -0,0 +1,84 @@
export function ItemsStaticListHandler(itemsArray, itemsPerPage, maxItems, itemsCountCallback, loadItemsCallback) {
const config = {
maxItems: maxItems || 255,
pageItems: itemsPerPage ? Math.min(maxItems, itemsPerPage) : 1,
};
const state = {
totalItems: 0,
totalPages: 0,
};
let results = itemsArray;
const items = [];
const responseItems = [];
const callbacks = {
itemsCount: function () {
if ('function' === typeof itemsCountCallback) {
itemsCountCallback(state.totalItems);
}
},
itemsLoad: function () {
if ('function' === typeof loadItemsCallback) {
loadItemsCallback(items);
}
},
};
function loadNextItems(itemsLength) {
itemsLength = !isNaN(itemsLength) ? itemsLength : config.pageItems;
let itemsToLoad = Math.min(itemsLength, responseItems.length);
if (itemsToLoad) {
let i = 0;
while (i < itemsToLoad) {
items.push(responseItems.shift());
i += 1;
}
callbacks.itemsLoad();
}
}
function loadItems(itemsLength) {
if (items.length < state.totalItems) {
loadNextItems(itemsLength);
}
}
function totalPages() {
return state.totalPages;
}
function loadedAllItems() {
return items.length === state.totalItems;
}
function cancelAll() {
itemsCountCallback = null;
loadItemsCallback = null;
}
let i = 0;
while (i < results.length && config.maxItems > responseItems.length) {
responseItems.push(results[i]);
i += 1;
}
state.totalItems = Math.min(config.maxItems, results.length);
state.totalPages = Math.ceil(state.totalItems / config.pageItems);
callbacks.itemsCount();
loadNextItems();
return {
loadItems,
totalPages,
loadedAllItems,
cancelAll,
};
}

View File

@@ -0,0 +1,53 @@
import MediaItemPreviewer from './MediaItemPreviewer';
let mediaPreviewerInstance = null;
var CSS_selectors = {
mediaItemPreviewer: '.item-img-preview',
};
var DataAttributes = {
mediaPreviewSrc: 'data-src',
mediaPreviewExt: 'data-ext',
};
export default class MediaItem {
constructor(item) {
if (!Node.prototype.isPrototypeOf(item)) {
return null;
}
this.element = item;
this.previewer = {
element: item.querySelector(CSS_selectors.mediaItemPreviewer),
};
let tmp;
if (this.previewer.element) {
tmp = this.previewer.element.getAttribute(DataAttributes.mediaPreviewSrc);
if (tmp) {
this.previewer.src = tmp.trim();
}
}
if (this.previewer.src) {
tmp = this.previewer.element.getAttribute(DataAttributes.mediaPreviewExt);
if (tmp) {
this.previewer.extensions = tmp.trim().split(',');
}
}
if (this.previewer.extensions) {
mediaPreviewerInstance = mediaPreviewerInstance || new MediaItemPreviewer(this.previewer.extensions);
mediaPreviewerInstance.elementEvents(this.element);
}
}
element() {
return this.element;
}
}

View File

@@ -0,0 +1,187 @@
import { PageStore } from '../../../../utils/stores/';
import { cancelAnimationFrame, requestAnimationFrame, addClassname } from '../../../../utils/helpers/';
Array.isArray =
Array.isArray ||
function (arg) {
'use strict';
return Object.prototype.toString.call(arg) === '[object Array]';
};
var hoverTimeoutID, requestAnimationFrameID;
var CSS_selectors = {
mediaItemPreviewer: '.item-img-preview',
};
var DataAttributes = {
mediaPreviewSrc: 'data-src',
};
export default class MediaItemPreviewer {
constructor(extensions) {
if (!Array.isArray(extensions)) {
return null;
}
this.extensions = {};
function onImageLoad(ins, evt) {
requestAnimationFrameID = requestAnimationFrame(function () {
if (ins.wrapperItem) {
addClassname(ins.wrapperItem, 'on-hover-preview');
requestAnimationFrameID = void 0;
ins.wrapperItem = void 0;
}
});
}
const fallback_ext = ['png', 'jpg', 'jpeg']; // NOTE: Keep extentions order.
let i, x;
this.element = null;
if (-1 < extensions.indexOf('webp')) {
x = document.createElement('source');
x.type = 'image/webp';
this.extensions.anim = this.extensions.anim || [];
this.extensions.anim.push({ elem: x, type: 'webp' });
if (1 === extensions.length) {
this.extensions.fallback = { elem: document.createElement('img'), type: 'webp' };
}
}
if (-1 < extensions.indexOf('gif')) {
x = document.createElement('source');
x.type = 'image/gif';
this.extensions.anim = this.extensions.anim || [];
this.extensions.anim.push({ elem: x, type: 'gif' });
this.extensions.fallback = { elem: document.createElement('img'), type: 'gif' };
}
if (-1 < extensions.indexOf('jpg')) {
x = document.createElement('source');
x.type = 'image/jpg';
this.extensions.anim = this.extensions.anim || [];
this.extensions.anim.push({ elem: x, type: 'jpg' });
this.extensions.fallback = { elem: document.createElement('img'), type: 'jpg' };
}
if (-1 < extensions.indexOf('jpeg')) {
x = document.createElement('source');
x.type = 'image/jpeg';
this.extensions.anim = this.extensions.anim || [];
this.extensions.anim.push({ elem: x, type: 'jpeg' });
this.extensions.fallback = { elem: document.createElement('img'), type: 'jpeg' };
}
if (!this.extensions.fallback.elem) {
i = 0;
while (i < fallback_extensions.length) {
if (-1 < extensions.indexOf(fallback_ext[i])) {
this.extensions.fallback = { elem: document.createElement('img'), type: fallback_ext[i] };
break;
}
i += 1;
}
}
if (this.extensions.anim.length || this.extensions.fallback.elem) {
this.element = document.createElement('picture');
if (this.extensions.anim.length) {
i = 0;
while (i < this.extensions.anim.length) {
this.element.appendChild(this.extensions.anim[i].elem);
i += 1;
}
}
if (this.extensions.fallback.elem) {
this.element.appendChild(this.extensions.fallback.elem);
}
this.image = this.element.querySelector('img');
this.image.addEventListener('load', onImageLoad.bind(null, this));
}
}
elementEvents(el) {
el.addEventListener('mouseenter', this.onMediaItemMouseEnter.bind(null, this));
el.addEventListener('mouseleave', this.onMediaItemMouseLeave.bind(null, this));
}
newImage(src, width, height, item) {
let i;
if (void 0 !== hoverTimeoutID) {
clearTimeout(hoverTimeoutID);
}
if (void 0 !== requestAnimationFrameID) {
cancelAnimationFrame(requestAnimationFrameID);
}
/*
* Set source (src).
*/
if (this.extensions.anim.length) {
i = 0;
while (i < this.extensions.anim.length) {
this.extensions.anim[i].elem.setAttribute('srcset', src + '.' + this.extensions.anim[i].type);
i += 1;
}
}
if (this.extensions.fallback.elem) {
this.extensions.fallback.elem.setAttribute('src', src + '.' + this.extensions.fallback.type);
}
/*
* Set dimensions (src).
*/
if (this.extensions.fallback.elem) {
this.extensions.fallback.elem.setAttribute('width', width + 'px');
this.extensions.fallback.elem.setAttribute('height', height + 'px');
}
/*
* Append previewer.
*/
item.querySelector(CSS_selectors.mediaItemPreviewer).appendChild(this.element);
/*
* Set previewer's container element.
*/
this.wrapperItem = item;
}
onMediaItemMouseEnter(ins, evt) {
var elem, src;
if (ins.image) {
elem = evt.target.querySelector(CSS_selectors.mediaItemPreviewer);
src =
PageStore.get('config-site').url + '/' + elem.getAttribute(DataAttributes.mediaPreviewSrc).replace(/^\//g, '');
hoverTimeoutID = setTimeout(function () {
ins.newImage(src, 1 + elem.offsetWidth, 1 + elem.offsetHeight, evt.target);
}, 100); // NOTE: Avoid loading unnecessary media, when mouse is moving fast over dom items.
}
}
onMediaItemMouseLeave(ins, evt) {
if (void 0 !== hoverTimeoutID) {
clearTimeout(hoverTimeoutID);
}
if (void 0 !== requestAnimationFrameID) {
cancelAnimationFrame(requestAnimationFrameID);
}
ins.wrapperItem = void 0;
}
}

View File

@@ -0,0 +1,39 @@
import MediaItem from './MediaItem';
import { hasClassname } from '../../../../utils/helpers/';
const _MediaItemsListData = {};
export default class MediaItemsList {
constructor(listContainer, initialItems) {
if (!Node.prototype.isPrototypeOf(listContainer)) {
return null;
}
_MediaItemsListData[
Object.defineProperty(this, 'id', { value: 'MediaItemsList_' + Object.keys(_MediaItemsListData).length }).id
] = {};
this.items = [];
this.container = listContainer;
this.horizontalItems = hasClassname(this.container, 'items-list-hor');
this.appendItems(initialItems);
}
dataObject() {
return _MediaItemsListData;
}
appendItems(items) {
var i;
if (NodeList.prototype.isPrototypeOf(items)) {
i = 0;
while (i < items.length) {
this.items.push(new MediaItem(items[i]));
i += 1;
}
} else if (Node.prototype.isPrototypeOf(items)) {
this.items.push(new MediaItem(items));
}
}
}

View File

@@ -0,0 +1,28 @@
import MediaItemsList from './MediaItemsList';
var CSS_selectors = {
mediaItems: '.item',
};
var mediaItemsListInstances = [];
export default function (lists) {
if (!lists.length) {
return null;
}
let items,
i = 0;
while (i < lists.length) {
items = lists[i].querySelectorAll(CSS_selectors.mediaItems);
if (items.length) {
mediaItemsListInstances = mediaItemsListInstances || [];
mediaItemsListInstances.push(new MediaItemsList(lists[i], items));
}
i += 1;
}
return mediaItemsListInstances;
}

View File

@@ -0,0 +1,7 @@
export * from './InlineSliderItemList.jsx';
export * from './InlineSliderItemListAsync.jsx';
export * from './ItemList.jsx';
export * from './ItemListAsync.jsx';
export * from './LazyLoadItemList.jsx';
export * from './LazyLoadItemListAsync.jsx';
export * from './PendingItemsList.jsx';