This repository has been archived by the owner on Apr 21, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 158
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #94 from therocketsloth/compthree/image_vis
Image Carousel Block
- Loading branch information
Showing
6 changed files
with
427 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Comp Three Image Carousel | ||
|
||
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. | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
export const CAROUSELCSS = ` | ||
.carousel .control-arrow, .carousel.carousel-slider .control-arrow { | ||
-webkit-transition: all 0.25s ease-in; | ||
-moz-transition: all 0.25s ease-in; | ||
-ms-transition: all 0.25s ease-in; | ||
-o-transition: all 0.25s ease-in; | ||
transition: all 0.25s ease-in; | ||
opacity: 0.4; | ||
filter: alpha(opacity=40); | ||
position: absolute; | ||
z-index: 2; | ||
top: 20px; | ||
background: none; | ||
border: 0; | ||
font-size: 32px; | ||
cursor: pointer; } | ||
.carousel .control-arrow:hover { | ||
opacity: 1; | ||
filter: alpha(opacity=100); } | ||
.carousel .control-arrow:before, .carousel.carousel-slider .control-arrow:before { | ||
margin: 0 5px; | ||
display: inline-block; | ||
border-top: 8px solid transparent; | ||
border-bottom: 8px solid transparent; | ||
content: ''; } | ||
.carousel .control-disabled.control-arrow { | ||
opacity: 0; | ||
filter: alpha(opacity=0); | ||
cursor: inherit; | ||
display: none; } | ||
.carousel .control-prev.control-arrow { | ||
left: 0; } | ||
.carousel .control-prev.control-arrow:before { | ||
border-right: 8px solid #fff; } | ||
.carousel .control-next.control-arrow { | ||
right: 0; } | ||
.carousel .control-next.control-arrow:before { | ||
border-left: 8px solid #fff; } | ||
.carousel { | ||
position: relative; | ||
width: 100%; } | ||
.carousel * { | ||
-webkit-box-sizing: border-box; | ||
-moz-box-sizing: border-box; | ||
box-sizing: border-box; } | ||
.carousel img { | ||
width: 100%; | ||
display: inline-block; | ||
pointer-events: none; } | ||
.carousel .carousel { | ||
position: relative; } | ||
.carousel .control-arrow { | ||
outline: 0; | ||
border: 0; | ||
background: none; | ||
top: 50%; | ||
margin-top: -13px; | ||
font-size: 18px; } | ||
.carousel .thumbs-wrapper { | ||
margin: 20px; | ||
overflow: hidden; } | ||
.carousel .thumbs { | ||
-webkit-transition: all 0.15s ease-in; | ||
-moz-transition: all 0.15s ease-in; | ||
-ms-transition: all 0.15s ease-in; | ||
-o-transition: all 0.15s ease-in; | ||
transition: all 0.15s ease-in; | ||
-webkit-transform: translate3d(0, 0, 0); | ||
-moz-transform: translate3d(0, 0, 0); | ||
-ms-transform: translate3d(0, 0, 0); | ||
-o-transform: translate3d(0, 0, 0); | ||
transform: translate3d(0, 0, 0); | ||
position: relative; | ||
list-style: none; | ||
white-space: nowrap; } | ||
.carousel .thumb { | ||
-webkit-transition: border 0.15s ease-in; | ||
-moz-transition: border 0.15s ease-in; | ||
-ms-transition: border 0.15s ease-in; | ||
-o-transition: border 0.15s ease-in; | ||
transition: border 0.15s ease-in; | ||
display: inline-block; | ||
width: 80px; | ||
margin-right: 6px; | ||
white-space: nowrap; | ||
overflow: hidden; | ||
border: 3px solid #fff; | ||
padding: 2px; } | ||
.carousel .thumb:focus { | ||
border: 3px solid #ccc; | ||
outline: none; } | ||
.carousel .thumb.selected, .carousel .thumb:hover { | ||
border: 3px solid #333; } | ||
.carousel .thumb img { | ||
vertical-align: top; } | ||
.carousel.carousel-slider { | ||
position: relative; | ||
margin: 0; | ||
overflow: hidden; } | ||
.carousel.carousel-slider .control-arrow { | ||
top: 0; | ||
color: #fff; | ||
font-size: 26px; | ||
bottom: 0; | ||
margin-top: 0; | ||
padding: 5px; } | ||
.carousel.carousel-slider .control-arrow:hover { | ||
background: rgba(0, 0, 0, 0.2); } | ||
.carousel .slider-wrapper { | ||
overflow: hidden; | ||
margin: auto; | ||
width: 100%; | ||
-webkit-transition: height 0.15s ease-in; | ||
-moz-transition: height 0.15s ease-in; | ||
-ms-transition: height 0.15s ease-in; | ||
-o-transition: height 0.15s ease-in; | ||
transition: height 0.15s ease-in; } | ||
.carousel .slider-wrapper.axis-horizontal .slider { | ||
-ms-box-orient: horizontal; | ||
display: -webkit-box; | ||
display: -moz-box; | ||
display: -ms-flexbox; | ||
display: -moz-flex; | ||
display: -webkit-flex; | ||
display: flex; } | ||
.carousel .slider-wrapper.axis-horizontal .slider .slide { | ||
flex-direction: column; | ||
flex-flow: column; } | ||
.carousel .slider-wrapper.axis-vertical { | ||
-ms-box-orient: horizontal; | ||
display: -webkit-box; | ||
display: -moz-box; | ||
display: -ms-flexbox; | ||
display: -moz-flex; | ||
display: -webkit-flex; | ||
display: flex; } | ||
.carousel .slider-wrapper.axis-vertical .slider { | ||
-webkit-flex-direction: column; | ||
flex-direction: column; } | ||
.carousel .slider { | ||
margin: 0; | ||
padding: 0; | ||
position: relative; | ||
list-style: none; | ||
width: 100%; } | ||
.carousel .slider.animated { | ||
-webkit-transition: all 0.35s ease-in-out; | ||
-moz-transition: all 0.35s ease-in-out; | ||
-ms-transition: all 0.35s ease-in-out; | ||
-o-transition: all 0.35s ease-in-out; | ||
transition: all 0.35s ease-in-out; } | ||
.carousel .slide { | ||
min-width: 100%; | ||
margin: 0; | ||
position: relative; | ||
text-align: center; | ||
background: #000; } | ||
.carousel .slide img { | ||
width: 100%; | ||
vertical-align: top; | ||
border: 0; } | ||
.carousel .slide iframe { | ||
display: inline-block; | ||
width: calc(100% - 80px); | ||
margin: 0 40px 40px; | ||
border: 0; } | ||
.carousel .slide .legend { | ||
-webkit-transition: all 0.5s ease-in-out; | ||
-moz-transition: all 0.5s ease-in-out; | ||
-ms-transition: all 0.5s ease-in-out; | ||
-o-transition: all 0.5s ease-in-out; | ||
transition: all 0.5s ease-in-out; | ||
position: absolute; | ||
bottom: 40px; | ||
left: 50%; | ||
margin-left: -45%; | ||
width: 90%; | ||
border-radius: 10px; | ||
background: #000; | ||
color: #fff; | ||
padding: 10px; | ||
font-size: 12px; | ||
text-align: center; | ||
opacity: 0.25; | ||
-webkit-transition: opacity 0.35s ease-in-out; | ||
-moz-transition: opacity 0.35s ease-in-out; | ||
-ms-transition: opacity 0.35s ease-in-out; | ||
-o-transition: opacity 0.35s ease-in-out; | ||
transition: opacity 0.35s ease-in-out; } | ||
.carousel .control-dots { | ||
position: absolute; | ||
bottom: 0; | ||
margin: 10px 0; | ||
text-align: center; | ||
width: 100%; } | ||
@media (min-width: 960px) { | ||
.carousel .control-dots { | ||
bottom: 0; } } | ||
.carousel .control-dots .dot { | ||
-webkit-transition: opacity 0.25s ease-in; | ||
-moz-transition: opacity 0.25s ease-in; | ||
-ms-transition: opacity 0.25s ease-in; | ||
-o-transition: opacity 0.25s ease-in; | ||
transition: opacity 0.25s ease-in; | ||
opacity: 0.3; | ||
filter: alpha(opacity=30); | ||
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.9); | ||
background: #fff; | ||
border-radius: 50%; | ||
width: 8px; | ||
height: 8px; | ||
cursor: pointer; | ||
display: inline-block; | ||
margin: 0 8px; } | ||
.carousel .control-dots .dot.selected, .carousel .control-dots .dot:hover { | ||
opacity: 1; | ||
filter: alpha(opacity=100); } | ||
.carousel .carousel-status { | ||
position: absolute; | ||
top: 0; | ||
right: 0; | ||
padding: 5px; | ||
font-size: 10px; | ||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.9); | ||
color: #fff; } | ||
.carousel:hover .slide .legend { | ||
opacity: 1; } | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import React from 'react'; | ||
import { Carousel } from 'react-responsive-carousel'; | ||
|
||
const URLREGEX = new RegExp("((http|https)(:\/\/))?([a-zA-Z0-9]+[.]{1}){2}[a-zA-Z0-9]+(\/{1}[a-zA-Z0-9]+)*\/?", "igm"); | ||
// Regular expression to check formal correctness of base64 encoded strings | ||
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob | ||
const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/; | ||
|
||
const DOTS_THRESHOLD = 15; | ||
|
||
const isUrlCheck = (strToCheck) => { | ||
return URLREGEX.test(strToCheck); | ||
} | ||
|
||
const isBase64StringCheck = (strToCheck) => { | ||
if (strToCheck && strToCheck.length > 150 && b64re.test(strToCheck)) { | ||
try { | ||
return btoa(atob(strToCheck)) === strToCheck; | ||
} catch (err) { | ||
return false; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
// NOTE: this method loops over column key names without looking at column order | ||
const findImageCol = (stateData) => { | ||
let tmpImageColData = { | ||
type: { | ||
url: false, | ||
base64: false | ||
}, | ||
name: null | ||
} | ||
|
||
for (let row of stateData) { | ||
for (let colName of Object.keys(row)) { | ||
if (isUrlCheck(row[colName].value)) { | ||
tmpImageColData.type.url = true; | ||
tmpImageColData.type.base64 = false; // set this in case we found b64 before finding a url | ||
tmpImageColData.name = colName; | ||
return tmpImageColData; | ||
} else if (isBase64StringCheck(row[colName].value)) { | ||
tmpImageColData.type.base64 = true; | ||
tmpImageColData.name = colName; | ||
} | ||
} | ||
// stop looping rows if we found a valid image column | ||
if (tmpImageColData.name) { | ||
return tmpImageColData; | ||
} | ||
} | ||
// resets this.state.imageColData to falsy if no valid image col was found | ||
return tmpImageColData; | ||
} | ||
|
||
// Create (or import) our react component | ||
export default class ImageViewer extends React.Component { | ||
constructor () { | ||
super(); | ||
|
||
// Set initial state to a loading or no data message, initialize imageColData | ||
this.state = { | ||
data: null, | ||
queryResponse: null, | ||
imageColData: { // flatten | ||
type: { | ||
url: false, | ||
base64: false | ||
}, | ||
name: null | ||
} | ||
}; | ||
} | ||
|
||
// component mount/recv props, should component update - lifecycle method | ||
// if there is new data, call again to find column ... | ||
|
||
// render our data | ||
render() { | ||
if (!this.state.data) { | ||
return ( | ||
<div>No Image Data Found</div> | ||
); | ||
} | ||
|
||
if (!this.state.imageColData.name) { | ||
this.state.imageColData = findImageCol(this.state.data); | ||
} | ||
|
||
// stop if no valid image data column found | ||
if (!this.state.imageColData.name) { | ||
return ( | ||
<div>Please select at least one field with an image url or a base64 encoded image.</div> | ||
); | ||
} | ||
|
||
// check first row for the image column, if it is not present find the new valid image column | ||
// Rerun the image column check to make sure there is still a valid column, this needs to be refreshed when the | ||
// explore is changed in looker. | ||
if (typeof this.state.data[0][this.state.imageColData.name] === 'undefined') { | ||
this.state.imageColData = findImageCol(this.state.data); | ||
} | ||
|
||
let tableRows = this.state.data.map((row, idx) => { | ||
let val = row[this.state.imageColData.name].value; // image url or base64 string | ||
|
||
if (this.state.imageColData.type.base64) { | ||
val = `data:image/image;base64,${val}`; | ||
} | ||
|
||
return ( | ||
<div key={idx}> | ||
<img src={val} /> | ||
</div> | ||
); | ||
}); | ||
|
||
// display image index linked dots in bottom of carousel, dots will stack into additional rows they overrun the carousel width | ||
let showIndicatorsBool = true; | ||
if (tableRows.length > DOTS_THRESHOLD) { | ||
showIndicatorsBool = false; | ||
} | ||
|
||
return ( | ||
<Carousel dynamicHeight={true} showIndicators={showIndicatorsBool} showThumbs={false} swipeable={false}> | ||
{tableRows} | ||
</Carousel> | ||
); | ||
} | ||
} |
Oops, something went wrong.