Skip to content
This repository was archived by the owner on Apr 21, 2022. It is now read-only.

Commit ed1b0f1

Browse files
Merge pull request #94 from therocketsloth/compthree/image_vis
Image Carousel Block
2 parents 3dc8054 + ecd2852 commit ed1b0f1

File tree

6 files changed

+427
-1
lines changed

6 files changed

+427
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Comp Three Image Carousel
2+
3+
This image carousel component allows for the display of images. These images can be provided either as image URLs which are publicly accessible or as a Base64 string in the database.
4+
696 KB
Loading
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
export const CAROUSELCSS = `
2+
.carousel .control-arrow, .carousel.carousel-slider .control-arrow {
3+
-webkit-transition: all 0.25s ease-in;
4+
-moz-transition: all 0.25s ease-in;
5+
-ms-transition: all 0.25s ease-in;
6+
-o-transition: all 0.25s ease-in;
7+
transition: all 0.25s ease-in;
8+
opacity: 0.4;
9+
filter: alpha(opacity=40);
10+
position: absolute;
11+
z-index: 2;
12+
top: 20px;
13+
background: none;
14+
border: 0;
15+
font-size: 32px;
16+
cursor: pointer; }
17+
.carousel .control-arrow:hover {
18+
opacity: 1;
19+
filter: alpha(opacity=100); }
20+
.carousel .control-arrow:before, .carousel.carousel-slider .control-arrow:before {
21+
margin: 0 5px;
22+
display: inline-block;
23+
border-top: 8px solid transparent;
24+
border-bottom: 8px solid transparent;
25+
content: ''; }
26+
.carousel .control-disabled.control-arrow {
27+
opacity: 0;
28+
filter: alpha(opacity=0);
29+
cursor: inherit;
30+
display: none; }
31+
.carousel .control-prev.control-arrow {
32+
left: 0; }
33+
.carousel .control-prev.control-arrow:before {
34+
border-right: 8px solid #fff; }
35+
.carousel .control-next.control-arrow {
36+
right: 0; }
37+
.carousel .control-next.control-arrow:before {
38+
border-left: 8px solid #fff; }
39+
40+
.carousel {
41+
position: relative;
42+
width: 100%; }
43+
.carousel * {
44+
-webkit-box-sizing: border-box;
45+
-moz-box-sizing: border-box;
46+
box-sizing: border-box; }
47+
.carousel img {
48+
width: 100%;
49+
display: inline-block;
50+
pointer-events: none; }
51+
.carousel .carousel {
52+
position: relative; }
53+
.carousel .control-arrow {
54+
outline: 0;
55+
border: 0;
56+
background: none;
57+
top: 50%;
58+
margin-top: -13px;
59+
font-size: 18px; }
60+
.carousel .thumbs-wrapper {
61+
margin: 20px;
62+
overflow: hidden; }
63+
.carousel .thumbs {
64+
-webkit-transition: all 0.15s ease-in;
65+
-moz-transition: all 0.15s ease-in;
66+
-ms-transition: all 0.15s ease-in;
67+
-o-transition: all 0.15s ease-in;
68+
transition: all 0.15s ease-in;
69+
-webkit-transform: translate3d(0, 0, 0);
70+
-moz-transform: translate3d(0, 0, 0);
71+
-ms-transform: translate3d(0, 0, 0);
72+
-o-transform: translate3d(0, 0, 0);
73+
transform: translate3d(0, 0, 0);
74+
position: relative;
75+
list-style: none;
76+
white-space: nowrap; }
77+
.carousel .thumb {
78+
-webkit-transition: border 0.15s ease-in;
79+
-moz-transition: border 0.15s ease-in;
80+
-ms-transition: border 0.15s ease-in;
81+
-o-transition: border 0.15s ease-in;
82+
transition: border 0.15s ease-in;
83+
display: inline-block;
84+
width: 80px;
85+
margin-right: 6px;
86+
white-space: nowrap;
87+
overflow: hidden;
88+
border: 3px solid #fff;
89+
padding: 2px; }
90+
.carousel .thumb:focus {
91+
border: 3px solid #ccc;
92+
outline: none; }
93+
.carousel .thumb.selected, .carousel .thumb:hover {
94+
border: 3px solid #333; }
95+
.carousel .thumb img {
96+
vertical-align: top; }
97+
.carousel.carousel-slider {
98+
position: relative;
99+
margin: 0;
100+
overflow: hidden; }
101+
.carousel.carousel-slider .control-arrow {
102+
top: 0;
103+
color: #fff;
104+
font-size: 26px;
105+
bottom: 0;
106+
margin-top: 0;
107+
padding: 5px; }
108+
.carousel.carousel-slider .control-arrow:hover {
109+
background: rgba(0, 0, 0, 0.2); }
110+
.carousel .slider-wrapper {
111+
overflow: hidden;
112+
margin: auto;
113+
width: 100%;
114+
-webkit-transition: height 0.15s ease-in;
115+
-moz-transition: height 0.15s ease-in;
116+
-ms-transition: height 0.15s ease-in;
117+
-o-transition: height 0.15s ease-in;
118+
transition: height 0.15s ease-in; }
119+
.carousel .slider-wrapper.axis-horizontal .slider {
120+
-ms-box-orient: horizontal;
121+
display: -webkit-box;
122+
display: -moz-box;
123+
display: -ms-flexbox;
124+
display: -moz-flex;
125+
display: -webkit-flex;
126+
display: flex; }
127+
.carousel .slider-wrapper.axis-horizontal .slider .slide {
128+
flex-direction: column;
129+
flex-flow: column; }
130+
.carousel .slider-wrapper.axis-vertical {
131+
-ms-box-orient: horizontal;
132+
display: -webkit-box;
133+
display: -moz-box;
134+
display: -ms-flexbox;
135+
display: -moz-flex;
136+
display: -webkit-flex;
137+
display: flex; }
138+
.carousel .slider-wrapper.axis-vertical .slider {
139+
-webkit-flex-direction: column;
140+
flex-direction: column; }
141+
.carousel .slider {
142+
margin: 0;
143+
padding: 0;
144+
position: relative;
145+
list-style: none;
146+
width: 100%; }
147+
.carousel .slider.animated {
148+
-webkit-transition: all 0.35s ease-in-out;
149+
-moz-transition: all 0.35s ease-in-out;
150+
-ms-transition: all 0.35s ease-in-out;
151+
-o-transition: all 0.35s ease-in-out;
152+
transition: all 0.35s ease-in-out; }
153+
.carousel .slide {
154+
min-width: 100%;
155+
margin: 0;
156+
position: relative;
157+
text-align: center;
158+
background: #000; }
159+
.carousel .slide img {
160+
width: 100%;
161+
vertical-align: top;
162+
border: 0; }
163+
.carousel .slide iframe {
164+
display: inline-block;
165+
width: calc(100% - 80px);
166+
margin: 0 40px 40px;
167+
border: 0; }
168+
.carousel .slide .legend {
169+
-webkit-transition: all 0.5s ease-in-out;
170+
-moz-transition: all 0.5s ease-in-out;
171+
-ms-transition: all 0.5s ease-in-out;
172+
-o-transition: all 0.5s ease-in-out;
173+
transition: all 0.5s ease-in-out;
174+
position: absolute;
175+
bottom: 40px;
176+
left: 50%;
177+
margin-left: -45%;
178+
width: 90%;
179+
border-radius: 10px;
180+
background: #000;
181+
color: #fff;
182+
padding: 10px;
183+
font-size: 12px;
184+
text-align: center;
185+
opacity: 0.25;
186+
-webkit-transition: opacity 0.35s ease-in-out;
187+
-moz-transition: opacity 0.35s ease-in-out;
188+
-ms-transition: opacity 0.35s ease-in-out;
189+
-o-transition: opacity 0.35s ease-in-out;
190+
transition: opacity 0.35s ease-in-out; }
191+
.carousel .control-dots {
192+
position: absolute;
193+
bottom: 0;
194+
margin: 10px 0;
195+
text-align: center;
196+
width: 100%; }
197+
@media (min-width: 960px) {
198+
.carousel .control-dots {
199+
bottom: 0; } }
200+
.carousel .control-dots .dot {
201+
-webkit-transition: opacity 0.25s ease-in;
202+
-moz-transition: opacity 0.25s ease-in;
203+
-ms-transition: opacity 0.25s ease-in;
204+
-o-transition: opacity 0.25s ease-in;
205+
transition: opacity 0.25s ease-in;
206+
opacity: 0.3;
207+
filter: alpha(opacity=30);
208+
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9);
209+
background: #fff;
210+
border-radius: 50%;
211+
width: 8px;
212+
height: 8px;
213+
cursor: pointer;
214+
display: inline-block;
215+
margin: 0 8px; }
216+
.carousel .control-dots .dot.selected, .carousel .control-dots .dot:hover {
217+
opacity: 1;
218+
filter: alpha(opacity=100); }
219+
.carousel .carousel-status {
220+
position: absolute;
221+
top: 0;
222+
right: 0;
223+
padding: 5px;
224+
font-size: 10px;
225+
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.9);
226+
color: #fff; }
227+
.carousel:hover .slide .legend {
228+
opacity: 1; }
229+
`;
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import React from 'react';
2+
import { Carousel } from 'react-responsive-carousel';
3+
4+
const URLREGEX = new RegExp("((http|https)(:\/\/))?([a-zA-Z0-9]+[.]{1}){2}[a-zA-Z0-9]+(\/{1}[a-zA-Z0-9]+)*\/?", "igm");
5+
// Regular expression to check formal correctness of base64 encoded strings
6+
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob
7+
const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
8+
9+
const DOTS_THRESHOLD = 15;
10+
11+
const isUrlCheck = (strToCheck) => {
12+
return URLREGEX.test(strToCheck);
13+
}
14+
15+
const isBase64StringCheck = (strToCheck) => {
16+
if (strToCheck && strToCheck.length > 150 && b64re.test(strToCheck)) {
17+
try {
18+
return btoa(atob(strToCheck)) === strToCheck;
19+
} catch (err) {
20+
return false;
21+
}
22+
}
23+
return false;
24+
}
25+
26+
// NOTE: this method loops over column key names without looking at column order
27+
const findImageCol = (stateData) => {
28+
let tmpImageColData = {
29+
type: {
30+
url: false,
31+
base64: false
32+
},
33+
name: null
34+
}
35+
36+
for (let row of stateData) {
37+
for (let colName of Object.keys(row)) {
38+
if (isUrlCheck(row[colName].value)) {
39+
tmpImageColData.type.url = true;
40+
tmpImageColData.type.base64 = false; // set this in case we found b64 before finding a url
41+
tmpImageColData.name = colName;
42+
return tmpImageColData;
43+
} else if (isBase64StringCheck(row[colName].value)) {
44+
tmpImageColData.type.base64 = true;
45+
tmpImageColData.name = colName;
46+
}
47+
}
48+
// stop looping rows if we found a valid image column
49+
if (tmpImageColData.name) {
50+
return tmpImageColData;
51+
}
52+
}
53+
// resets this.state.imageColData to falsy if no valid image col was found
54+
return tmpImageColData;
55+
}
56+
57+
// Create (or import) our react component
58+
export default class ImageViewer extends React.Component {
59+
constructor () {
60+
super();
61+
62+
// Set initial state to a loading or no data message, initialize imageColData
63+
this.state = {
64+
data: null,
65+
queryResponse: null,
66+
imageColData: { // flatten
67+
type: {
68+
url: false,
69+
base64: false
70+
},
71+
name: null
72+
}
73+
};
74+
}
75+
76+
// component mount/recv props, should component update - lifecycle method
77+
// if there is new data, call again to find column ...
78+
79+
// render our data
80+
render() {
81+
if (!this.state.data) {
82+
return (
83+
<div>No Image Data Found</div>
84+
);
85+
}
86+
87+
if (!this.state.imageColData.name) {
88+
this.state.imageColData = findImageCol(this.state.data);
89+
}
90+
91+
// stop if no valid image data column found
92+
if (!this.state.imageColData.name) {
93+
return (
94+
<div>Please select at least one field with an image url or a base64 encoded image.</div>
95+
);
96+
}
97+
98+
// check first row for the image column, if it is not present find the new valid image column
99+
// Rerun the image column check to make sure there is still a valid column, this needs to be refreshed when the
100+
// explore is changed in looker.
101+
if (typeof this.state.data[0][this.state.imageColData.name] === 'undefined') {
102+
this.state.imageColData = findImageCol(this.state.data);
103+
}
104+
105+
let tableRows = this.state.data.map((row, idx) => {
106+
let val = row[this.state.imageColData.name].value; // image url or base64 string
107+
108+
if (this.state.imageColData.type.base64) {
109+
val = `data:image/image;base64,${val}`;
110+
}
111+
112+
return (
113+
<div key={idx}>
114+
<img src={val} />
115+
</div>
116+
);
117+
});
118+
119+
// display image index linked dots in bottom of carousel, dots will stack into additional rows they overrun the carousel width
120+
let showIndicatorsBool = true;
121+
if (tableRows.length > DOTS_THRESHOLD) {
122+
showIndicatorsBool = false;
123+
}
124+
125+
return (
126+
<Carousel dynamicHeight={true} showIndicators={showIndicatorsBool} showThumbs={false} swipeable={false}>
127+
{tableRows}
128+
</Carousel>
129+
);
130+
}
131+
}

0 commit comments

Comments
 (0)