react-editable-photo-grid 1.0.0 → 2.3.0

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.
@@ -0,0 +1,188 @@
1
+ import React, { useEffect, useState, TouchEvent } from 'react'
2
+ import { imgSrcProperty, GalleryButtonArrows, PhotoItem } from '../types';
3
+ import { getImageSrcProperty, photoHasDetails } from "../utils";
4
+
5
+ type GalleryProps = {
6
+ photos: PhotoItem[],
7
+ activeKey: number,
8
+ setActiveKey: (key: number) => void,
9
+ imageSrcPrefix: string;
10
+ imageSrcProperty: imgSrcProperty;
11
+ buttonArrows?: GalleryButtonArrows;
12
+ onGallerySwipe?: (photo: PhotoItem) => void;
13
+ }
14
+
15
+ const NonScrollGallery = (props: GalleryProps) => {
16
+ const [touchPosition, setTouchPosition] = useState<number | null>(null),
17
+ [visible, setVisible] = useState<boolean>(false);
18
+
19
+ const getSrcProperty = (photo: PhotoItem): string => {
20
+ const property = props.imageSrcProperty as string;
21
+ return getImageSrcProperty(photo, property);
22
+ }
23
+
24
+ const showGallery = () => {
25
+ setVisible(true);
26
+ }
27
+
28
+ const handleSwipe = () => {
29
+ if (props.onGallerySwipe) {
30
+ const photoKey = props.activeKey,
31
+ photo: PhotoItem = props.photos[photoKey];
32
+
33
+ if (photo) {
34
+ props.onGallerySwipe(photo);
35
+ }
36
+ }
37
+ }
38
+
39
+ const shouldRenderImage = (index: number) => {
40
+ const key = props.activeKey;
41
+ let validIndexes = [key];
42
+
43
+ if (key > 0) {
44
+ validIndexes.push(key - 1);
45
+ }
46
+
47
+ if (key < props.photos.length - 1) {
48
+ validIndexes.push(key + 1);
49
+ }
50
+
51
+ return validIndexes.includes(index);
52
+ }
53
+
54
+ const prevItem = (e: React.MouseEvent<HTMLButtonElement>) => {
55
+ e.preventDefault()
56
+ if (props.activeKey > 0) {
57
+ props.setActiveKey(props.activeKey - 1);
58
+ handleSwipe();
59
+ }
60
+ }
61
+
62
+ const nextItem = (e: React.MouseEvent<HTMLButtonElement>) => {
63
+ e.preventDefault()
64
+ if (props.activeKey < props.photos.length - 1) {
65
+ props.setActiveKey(props.activeKey + 1);
66
+ handleSwipe();
67
+ }
68
+ }
69
+
70
+ const keydownHandler = (e: KeyboardEvent) => {
71
+ if (e.keyCode === 39) {
72
+ if (props.activeKey < props.photos.length - 1) {
73
+ props.setActiveKey(props.activeKey + 1);
74
+ }
75
+ } else if (e.keyCode === 37) {
76
+ if (props.activeKey > 0) {
77
+ props.setActiveKey(props.activeKey - 1);
78
+ }
79
+ }
80
+ handleSwipe();
81
+ }
82
+
83
+ const hideGallery = () => {
84
+ setVisible(false);
85
+ props.setActiveKey(-1);
86
+ }
87
+
88
+ const disableRightClick = () => {
89
+ return false;
90
+ }
91
+
92
+ const handleTouchStart = (e: TouchEvent<HTMLDivElement>) => {
93
+ const touchDown = e.touches[0].clientX;
94
+ setTouchPosition(touchDown);
95
+ }
96
+
97
+ const handleTouchMove = (e: TouchEvent<HTMLDivElement>) => {
98
+ if (touchPosition === null) {
99
+ return;
100
+ }
101
+
102
+ const currentTouch = e.touches[0].clientX,
103
+ diff = touchPosition - currentTouch;
104
+
105
+ if (diff > 5) {
106
+ if (props.activeKey < props.photos.length - 1) {
107
+ props.setActiveKey(props.activeKey + 1);
108
+ }
109
+ }
110
+
111
+ if (diff < -5) {
112
+ if (props.activeKey > 0) {
113
+ props.setActiveKey(props.activeKey - 1);
114
+ }
115
+ }
116
+
117
+ handleSwipe();
118
+ setTouchPosition(null);
119
+ }
120
+
121
+ useEffect(() => {
122
+ if (props.activeKey > -1) {
123
+ if (visible === false) {
124
+ showGallery()
125
+ }
126
+ }
127
+
128
+ document.addEventListener('keydown', keydownHandler);
129
+
130
+ return function cleanup() {
131
+ document.removeEventListener('keydown', keydownHandler);
132
+ };
133
+
134
+ }, [props.activeKey])
135
+
136
+ return (
137
+ <div
138
+ className="custom__gallery"
139
+ style={{ display: visible === true ? 'block' : 'none' }}
140
+ onTouchStart={handleTouchStart}
141
+ onTouchMove={handleTouchMove}
142
+ >
143
+ <div className="clearfix">
144
+ <button className="close__gallery" onClick={hideGallery} type="button">&times;</button>
145
+ </div>
146
+ <div className="custom__gallery__container">
147
+ {props.activeKey > 0 &&
148
+ <button
149
+ onClick={prevItem}
150
+ className="previous__item"
151
+ dangerouslySetInnerHTML={{ __html: props.buttonArrows ? props.buttonArrows.prev : '&#8592' }}
152
+ />
153
+ }
154
+ <div className="custom__gallery__images">
155
+ {props.photos.length &&
156
+ props.photos.map((photo, i) =>
157
+ <div key={`${i}`} className={props.activeKey == i ? "custom__gallery__item active" : "custom__gallery__item"}>
158
+ <img
159
+ src={shouldRenderImage(i) ? `${props.imageSrcPrefix}${getSrcProperty(photo)}` : ''}
160
+ alt="gallery item"
161
+ onContextMenu={disableRightClick}
162
+ />
163
+ {photoHasDetails(photo) === true && (
164
+ <div className="custom__gallery__photo__overlay">
165
+ {photo.name != undefined && photo.name !== '' && (
166
+ <h4>{photo.name}</h4>
167
+ )}
168
+ {photo.description != undefined && photo.description !== '' && (
169
+ <p>{photo.description}</p>
170
+ )}
171
+ </div>
172
+ )}
173
+ </div>
174
+ )}
175
+ </div>
176
+ {props.activeKey < props.photos.length - 1 &&
177
+ <button
178
+ onClick={nextItem}
179
+ className="next__item"
180
+ dangerouslySetInnerHTML={{ __html: props.buttonArrows ? props.buttonArrows.next : '&#8594' }}
181
+ />
182
+ }
183
+ </div>
184
+ </div>
185
+ )
186
+ }
187
+
188
+ export default NonScrollGallery;
@@ -1,61 +1,57 @@
1
1
  import React from 'react';
2
2
  import { PhotoControlsProps } from '../types';
3
3
  import { castRowKey } from '../utils';
4
- import'../styles.css';
4
+ import '../styles.css';
5
5
 
6
6
  const PhotoControls = (props: PhotoControlsProps) => {
7
7
  return (
8
8
  <ul className="photogrid--photo__controls">
9
9
  {props.photo.column > 1 &&
10
10
  <li>
11
- <button
12
- type="button"
13
- className="photo__control"
14
- onClick={props.movePhotoLeft}
15
- data-id={props.photo.id}
11
+ <button
12
+ type="button"
13
+ className="photo__control move__photo__left"
14
+ onClick={props.movePhotoLeft}
15
+ data-id={props.photo.id}
16
16
  data-row={props.rowKey}
17
- >
18
- &#8592;
19
- </button>
17
+ dangerouslySetInnerHTML={{ __html: props.buttonArrows ? props.buttonArrows.left : '&#8592' }}
18
+ />
20
19
  </li>
21
20
  }
22
21
  {castRowKey(props.rowKey) > 1 &&
23
22
  <li>
24
- <button
25
- type="button"
26
- className="photo__control"
27
- onClick={props.movePhotoUp}
28
- data-id={props.photo.id}
23
+ <button
24
+ type="button"
25
+ className="photo__control move__photo__up"
26
+ onClick={props.movePhotoUp}
27
+ data-id={props.photo.id}
29
28
  data-row={props.rowKey}
30
- >
31
- &#8593;
32
- </button>
29
+ dangerouslySetInnerHTML={{ __html: props.buttonArrows ? props.buttonArrows.up : '&#8593' }}
30
+ />
33
31
  </li>
34
32
  }
35
33
  {castRowKey(props.rowKey) < props.rowCount || castRowKey(props.rowKey) === props.rowCount && props.photoCount > 1 ?
36
34
  <li>
37
- <button
38
- type="button"
39
- className="photo__control"
40
- onClick={props.movePhotoDown}
41
- data-id={props.photo.id}
35
+ <button
36
+ type="button"
37
+ className="photo__control move__photo__down"
38
+ onClick={props.movePhotoDown}
39
+ data-id={props.photo.id}
42
40
  data-row={props.rowKey}
43
- >
44
- &#8595;
45
- </button>
41
+ dangerouslySetInnerHTML={{ __html: props.buttonArrows ? props.buttonArrows.down : '&#8595' }}
42
+ />
46
43
  </li> : null
47
44
  }
48
45
  {props.photo.column < props.photoCount &&
49
46
  <li>
50
- <button
51
- type="button"
52
- className="photo__control"
53
- onClick={props.movePhotoRight}
54
- data-id={props.photo.id}
47
+ <button
48
+ type="button"
49
+ className="photo__control move__photo__right"
50
+ onClick={props.movePhotoRight}
51
+ data-id={props.photo.id}
55
52
  data-row={props.rowKey}
56
- >
57
- &#8594;
58
- </button>
53
+ dangerouslySetInnerHTML={{ __html: props.buttonArrows ? props.buttonArrows.right : '&#8594' }}
54
+ />
59
55
  </li>
60
56
  }
61
57
  </ul>
@@ -9,23 +9,21 @@ const RowControls = (props: RowControlsProps) => {
9
9
  {castRowKey(props.rowKey) > 1 &&
10
10
  <li>
11
11
  <button
12
- className="row__control"
12
+ className="row__control move__row__up"
13
13
  onClick={props.moveRowUp}
14
14
  data-row={props.rowKey}
15
- >
16
- &#8593;
17
- </button>
15
+ dangerouslySetInnerHTML={{ __html: props.buttonArrows ? props.buttonArrows.up : '&#8593' }}
16
+ />
18
17
  </li>
19
18
  }
20
19
  {castRowKey(props.rowKey) < props.rowCount &&
21
20
  <li>
22
21
  <button
23
- className="row__control"
22
+ className="row__control move__row__down"
24
23
  onClick={props.moveRowDown}
25
24
  data-row={props.rowKey}
26
- >
27
- &#8595;
28
- </button>
25
+ dangerouslySetInnerHTML={{ __html: props.buttonArrows ? props.buttonArrows.down : '&#8595' }}
26
+ />
29
27
  </li>
30
28
  }
31
29
  </ul>
@@ -0,0 +1,190 @@
1
+ import React, { useState, useEffect, useRef, useCallback } from 'react';
2
+ import { imgSrcProperty, GalleryButtonArrows, PhotoItem } from '../types';
3
+ import { photoHasDetails } from "../utils";
4
+ import debounce from 'lodash.debounce';
5
+
6
+ type Props = {
7
+ photos: PhotoItem[],
8
+ activeKey: number,
9
+ setActiveKey: (key: number) => void,
10
+ imageSrcPrefix: string;
11
+ imageSrcProperty: imgSrcProperty;
12
+ onGallerySwipe?: (photo: PhotoItem) => void;
13
+ buttonArrows?: GalleryButtonArrows;
14
+ };
15
+
16
+ const ScrollGallery: React.FC<Props> = ({ photos, imageSrcPrefix, imageSrcProperty, onGallerySwipe, activeKey, setActiveKey, buttonArrows }) => {
17
+ const [visible, setVisible] = useState<boolean>(false),
18
+ scrollContainer = useRef<HTMLDivElement | null>(null),
19
+ imgRefs = useRef<(HTMLImageElement | null)[]>([]),
20
+ [swipeEnabled, setSwipeEnabled] = useState(false);
21
+
22
+ const getImageSrcProperty = (photo: PhotoItem, property: string): string => {
23
+ switch (property) {
24
+ case 'id':
25
+ return photo.id;
26
+ case 'thumbnail_path':
27
+ return photo.thumbnail_path;
28
+ case 'image_path':
29
+ return photo.image_path;
30
+ }
31
+
32
+ return photo.image_path;
33
+ }
34
+
35
+ const handleSwipe = (index: number) => {
36
+ if (onGallerySwipe && swipeEnabled) {
37
+ const photo: PhotoItem = photos[index];
38
+
39
+ if (photo) {
40
+ onGallerySwipe(photo);
41
+ }
42
+ }
43
+ }
44
+
45
+ const hideGallery = () => {
46
+ setVisible(false);
47
+ setActiveKey(-1);
48
+ }
49
+
50
+ const jumpToImage = useCallback(
51
+ debounce((index: number) => {
52
+ setSwipeEnabled(false);
53
+
54
+ const imgElement = imgRefs.current[index];
55
+ const containerElement = scrollContainer.current;
56
+
57
+ if (imgElement && containerElement) {
58
+ const imgRect = imgElement.getBoundingClientRect();
59
+ const containerRect = containerElement.getBoundingClientRect();
60
+
61
+ const scrollLeft = imgRect.left - containerRect.left + containerElement.scrollLeft;
62
+
63
+ containerElement.scrollTo({
64
+ left: scrollLeft,
65
+ behavior: 'smooth'
66
+ });
67
+ }
68
+
69
+ setSwipeEnabled(true);
70
+ }, 100),
71
+ []
72
+ );
73
+
74
+ const disableRightClick = (e: React.MouseEvent<HTMLImageElement>) => {
75
+ e.preventDefault();
76
+ return false;
77
+ }
78
+
79
+ const scrollLeft = (e: React.MouseEvent<HTMLButtonElement>) => {
80
+ e.preventDefault();
81
+ if (scrollContainer.current != undefined) {
82
+ scrollContainer.current.scrollBy({
83
+ top: 0,
84
+ left: -1,
85
+ behavior: 'smooth'
86
+ });
87
+ }
88
+ }
89
+
90
+ const scrollRight = (e: React.MouseEvent<HTMLButtonElement>) => {
91
+ e.preventDefault();
92
+ if (scrollContainer.current != undefined) {
93
+ scrollContainer.current.scrollBy({
94
+ top: 0,
95
+ left: 800,
96
+ behavior: 'smooth'
97
+ });
98
+ }
99
+ }
100
+
101
+ const loadImagesSrc = () => {
102
+ imgRefs.current.forEach((img, index) => {
103
+ if (img) {
104
+ const src = img.getAttribute('data-src');
105
+ if (src) {
106
+ img.src = src;
107
+ }
108
+ }
109
+ })
110
+ }
111
+
112
+ const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
113
+ return;
114
+ const target = e.currentTarget as HTMLDivElement;
115
+ const { scrollLeft } = target;
116
+ const itemWidth = target.clientWidth;
117
+ const index = Math.round(scrollLeft / itemWidth);
118
+ setActiveKey(index);
119
+ handleSwipe(index);
120
+ }
121
+
122
+ useEffect(() => {
123
+ if (activeKey > -1 && visible === false) {
124
+ setVisible(true);
125
+ jumpToImage(activeKey);
126
+ }
127
+ }, [activeKey]);
128
+
129
+ useEffect(() => {
130
+ loadImagesSrc();
131
+ }, [photos]);
132
+
133
+ return (
134
+ <div
135
+ className={`${visible ? 'block' : 'hidden'} photogrid__gallery`}
136
+ >
137
+ <div className="photogrid__gallery__hide">
138
+ <button onClick={hideGallery} type="button">&times;</button>
139
+ </div>
140
+ <div
141
+ className="photogrid__prev"
142
+ >
143
+ <button
144
+ type="button"
145
+ onClick={scrollLeft}
146
+ dangerouslySetInnerHTML={{ __html: buttonArrows ? buttonArrows.prev : '&#8592' }}
147
+ />
148
+ </div>
149
+ <div
150
+ ref={scrollContainer}
151
+ className="photogrid__gallery_scrollcontainer"
152
+ onScroll={handleScroll}
153
+ >
154
+ {photos.length &&
155
+ photos.map((photo, i) =>
156
+ <div key={`${i}`} className="photogrid__gallery_item">
157
+ <img
158
+ ref={el => imgRefs.current[i] = el}
159
+ data-index={i}
160
+ data-src={`${imageSrcPrefix}${getImageSrcProperty(photo, imageSrcProperty)}`}
161
+ alt="gallery item"
162
+ onContextMenu={disableRightClick}
163
+ />
164
+ {photoHasDetails(photo) === true && (
165
+ <div className="photogrid__gallery_item__overlay">
166
+ {photo.name != undefined && photo.name !== '' && (
167
+ <h4>{photo.name}</h4>
168
+ )}
169
+ {photo.description != undefined && photo.description !== '' && (
170
+ <p>{photo.description}</p>
171
+ )}
172
+ </div>
173
+ )}
174
+ </div>
175
+ )}
176
+ </div>
177
+ <div
178
+ className="photogrid__next"
179
+ >
180
+ <button
181
+ type="button"
182
+ onClick={scrollRight}
183
+ dangerouslySetInnerHTML={{ __html: buttonArrows ? buttonArrows.next : '&#8594' }}
184
+ />
185
+ </div>
186
+ </div>
187
+ );
188
+ }
189
+
190
+ export default ScrollGallery;
package/src/index.js CHANGED
@@ -1,3 +1,6 @@
1
1
  import PhotoGrid from './PhotoGrid';
2
+ import Checkbox from './components/Checkbox';
3
+ import { PhotoRows, PhotoItem, imgSrcProperty } from './types';
4
+ import { sortPhotosIntoRows } from './utils';
2
5
 
3
- export { PhotoGrid };
6
+ export { PhotoGrid, PhotoRows, PhotoItem, imgSrcProperty, sortPhotosIntoRows, Checkbox };