Skip to content

Commit

Permalink
Implement orientation feature (#136)
Browse files Browse the repository at this point in the history
* Implement orientation feature and tests

* Improve gif
  • Loading branch information
MarioRodriguezS authored Apr 3, 2024
1 parent 50a21f0 commit afac459
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 23 deletions.
Binary file modified assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion dist/bundle.js

Large diffs are not rendered by default.

64 changes: 63 additions & 1 deletion src/controlPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@ export default class ControlPanel {
controlPanel: 'inline-image__control-panel',
tabWrapper: 'inline-image__tab-wrapper',
tab: 'inline-image__tab',
orientationWrapper: 'inline-image__orientation-wrapper',
orientationButton: 'inline-image__orientation-button',
embedButton: 'inline-image__embed-button',
search: 'inline-image__search',
imageGallery: 'inline-image__image-gallery',
noResults: 'inline-image__no-results',
imgWrapper: 'inline-image__img-wrapper',
thumb: 'inline-image__thumb',
landscapeImg: 'landscape-img',
portraitImg: 'portrait-img',
squarishImg: 'squarish-img',
active: 'active',
hidden: 'panel-hidden',
scroll: 'panel-scroll',
Expand All @@ -49,11 +54,15 @@ export default class ControlPanel {
unsplashPanel: null,
imageGallery: null,
searchInput: null,
landscapeButton: null,
portraitButton: null,
squarishButton: null,
};

this.unsplashClient = new UnsplashClient(this.config.unsplash);
this.searchTimeout = null;
this.showEmbedTab = this.config.embed ? this.config.embed.display : true;
this.queryOrientation = null;
}

/**
Expand Down Expand Up @@ -169,10 +178,12 @@ export default class ControlPanel {
contentEditable: !this.readOnly,
oninput: () => this.searchInputHandler(),
});
const orientationWrapper = this.buildOrientationWrapper();

searchInput.dataset.placeholder = 'Search for an image...';

wrapper.appendChild(searchInput);
wrapper.appendChild(orientationWrapper);
wrapper.appendChild(imageGallery);

this.nodes.searchInput = searchInput;
Expand Down Expand Up @@ -214,6 +225,7 @@ export default class ControlPanel {
const query = this.nodes.searchInput.innerHTML;
this.unsplashClient.searchImages(
query,
this.queryOrientation,
(results) => this.appendImagesToGallery(results),
);
}, 1000);
Expand Down Expand Up @@ -248,7 +260,10 @@ export default class ControlPanel {
*/
createThumbImage(image) {
const imgWrapper = make('div', this.cssClasses.imgWrapper);
const img = make('img', this.cssClasses.thumb, {
const imgClasses = [this.cssClasses.thumb];
if (this.queryOrientation) imgClasses.push(this.cssClasses[`${this.queryOrientation}Img`]);

const img = make('img', imgClasses, {
src: image.thumb,
onclick: () => this.downloadUnsplashImage(image),
});
Expand Down Expand Up @@ -285,4 +300,51 @@ export default class ControlPanel {
});
this.unsplashClient.downloadImage(downloadLocation);
}

/**
* Builds the orientation wrapper that wraps the orientation buttons.
* @returns {HTMLDivElement}
*/
buildOrientationWrapper() {
const orientationModes = ['Landscape', 'Portrait', 'Squarish'];
const orientationButtons = orientationModes.map((orientation) => `${orientation.toLowerCase()}Button`);
const wrapper = make('div', [this.cssClasses.orientationWrapper]);

orientationModes.forEach((orientation) => {
const button = make('button', [this.cssClasses.orientationButton], {
id: `${orientation.toLowerCase()}-button`,
innerHTML: orientation,
onclick: (e) => this.handleOrientationButtonClick(e, orientationButtons),
});
wrapper.appendChild(button);
this.nodes[`${orientation.toLowerCase()}Button`] = button;
});

return wrapper;
}

/**
* OnClick handler for orientation buttons
*
* @param {any} event handler event
* @param {Array} orientationButtons orientation HTML button elements.
* @returns {void}
*/
handleOrientationButtonClick(event, orientationButtons) {
const isActive = event.target.classList.contains(this.cssClasses.active);
orientationButtons.forEach((button) => {
this.nodes[button].classList.remove(this.cssClasses.active);
});

if (!isActive) {
event.target.classList.add(this.cssClasses.active);
this.queryOrientation = event.target.innerHTML.toLowerCase();
} else this.queryOrientation = null;

const query = this.nodes.searchInput.innerHTML;
if (query) {
this.showLoader();
this.performSearch();
}
}
}
66 changes: 54 additions & 12 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@
vertical-align: bottom;
}

.inline-image__caption[contentEditable=true][data-placeholder]:empty::before {
.inline-image__caption[contentEditable="true"][data-placeholder]:empty::before {
position: absolute;
content: attr(data-placeholder);
color: #707684;
font-weight: normal;
opacity: 0;
}
}

.inline-image__caption[contentEditable=true][data-placeholder]:empty::before {
.inline-image__caption[contentEditable="true"][data-placeholder]:empty::before {
opacity: 1;
}

.inline-image__caption[contentEditable=true][data-placeholder]:empty:focus::before {
.inline-image__caption[contentEditable="true"][data-placeholder]:empty:focus::before {
opacity: 0;
}

Expand All @@ -43,7 +43,6 @@
margin: 0 auto;
}


.inline-image__picture--withBorder {
border: 1px solid #e8e8eb;
padding: 1px;
Expand Down Expand Up @@ -94,8 +93,7 @@
padding: 10px;
border-radius: 4px;
box-shadow: rgba(15, 15, 15, 0.05) 0px 0px 0px 1px,
rgba(15, 15, 15, 0.1) 0px 3px 6px,
rgba(15, 15, 15, 0.2) 0px 9px 24px;
rgba(15, 15, 15, 0.1) 0px 3px 6px, rgba(15, 15, 15, 0.2) 0px 9px 24px;
}

.inline-image__image-gallery {
Expand All @@ -108,7 +106,7 @@
}

.inline-image__img-wrapper {
width: 25%;
width: 25%;
}

.inline-image__thumb {
Expand Down Expand Up @@ -138,12 +136,56 @@
width: 96%;
}

.inline-image__no-results{
.inline-image__no-results {
width: 100%;
text-align: center;
margin: 20px;
}

.inline-image__orientation-wrapper {
margin: 10px;
}

.inline-image__orientation-button {
margin-right: 5px;
border-radius: 3px;
border-style: solid;
border-width: 1px;
border-color: #f0f0f0;
background-color: transparent;
cursor: pointer;
}

.inline-image__orientation-button.active {
background-color: #388ae5;
color: white;
border-color: #388ae5;
}

.inline-image__orientation-button.active:hover {
background-color: #388ae5;
color: white;
}

.inline-image__orientation-button:hover {
background-color: #f0f0f0;
}

.landscape-img {
height: auto;
aspect-ratio: 3/2;
}

.portrait-img {
height: auto;
aspect-ratio: 4/5;
}

.squarish-img {
height: auto;
aspect-ratio: 1/1;
}

.panel-hidden {
display: none;
}
Expand All @@ -160,7 +202,7 @@
justify-content: center;
align-items: center;
overflow: hidden;
background-color: rgba(0,0,0,0.8);
background-color: rgba(0, 0, 0, 0.8);
}

.modal-content {
Expand All @@ -170,8 +212,8 @@

.modal-img {
object-fit: cover;
max-width:100%;
max-height:100%;
max-width: 100%;
max-height: 100%;
}

.close {
Expand Down
14 changes: 9 additions & 5 deletions src/unsplashClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@ export default class UnsplashClient {
* Search images
*
* @param {string} query Image search query term
* @param {string} orientation Image search orientation term
* @param {Function} callback Function for rendering image gallery
* @returns {void}
*/
searchImages(query, callback) {
searchImages(query, orientation, callback) {
const params = {
query,
per_page: this.perPage,
};

if (orientation) params.orientation = orientation;
axios.get(`${this.apiUrl}/search/photos`, {
params: {
query,
per_page: this.perPage,
},
params,
})
.then((response) => callback(this.parseResponse(response.data)))
.catch(() => callback([]));
Expand Down
20 changes: 19 additions & 1 deletion test/controlPanel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,25 @@ describe('ControlPanel', () => {

jest.runAllTimers();

expect(mockSearchImages).toHaveBeenCalledWith(query, expect.any(Function));
expect(mockSearchImages).toHaveBeenCalledWith(query, null, expect.any(Function));
});

it('triggers unsplash image search on input event with orientation', () => {
const mockSearchImages = jest.spyOn(UnsplashClient.prototype, 'searchImages')
.mockImplementation();
jest.useFakeTimers();

const query = 'pizza';
const input = unsplashPanel.querySelector('#unsplash-search');
input.innerHTML = query;
triggerEvent(input, 'input');

const button = unsplashPanel.querySelector('#landscape-button');
triggerEvent(button, 'click');

jest.runAllTimers();

expect(mockSearchImages).toHaveBeenCalledWith(query, 'landscape', expect.any(Function));
});

it('creates image gallery from unsplash data', () => {
Expand Down
7 changes: 4 additions & 3 deletions test/unsplashClient.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const mockApiRequest = (statusCode, response) => {
.query({
query: 'pizza',
per_page: 30,
orientation: 'landscape',
})
.reply(statusCode, response);
};
Expand Down Expand Up @@ -41,15 +42,15 @@ describe('UnsplashClient', () => {

const callback = mockCallback(parsedResponse, done);

unsplashClient.searchImages('pizza', callback);
unsplashClient.searchImages('pizza', 'landscape', callback);
});

it('calls callback passing an empty array if search has no results', (done) => {
mockApiRequest(200, unsplashEmptyResponse);

const callback = mockCallback([], done);

unsplashClient.searchImages('pizza', callback);
unsplashClient.searchImages('pizza', 'landscape', callback);
});
});

Expand All @@ -59,7 +60,7 @@ describe('UnsplashClient', () => {

const callback = mockCallback([], done);

unsplashClient.searchImages('pizza', callback);
unsplashClient.searchImages('pizza', 'landscape', callback);
});
});
});
Expand Down

0 comments on commit afac459

Please sign in to comment.