Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/implement placeholder #6

Merged
merged 17 commits into from
Dec 20, 2020
2 changes: 1 addition & 1 deletion html/src/accessibility.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {CloudinaryImage} from "@cloudinary/base/assets/CloudinaryImage";
import {plugin, accessibilityMode} from "./types";
import {ACCESSIBILITY_MODES} from './constants';
import {ACCESSIBILITY_MODES} from './internalConstnats';

/**
* Returns the accessibility plugin
Expand Down
11 changes: 0 additions & 11 deletions html/src/constants.ts

This file was deleted.

6 changes: 3 additions & 3 deletions html/src/htmlLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ export class HtmlLayer{
* @param plugins
*/
update(userCloudinaryImage: CloudinaryImage, plugins: any){
const clone = cloneDeep(userCloudinaryImage);
this.render(this.img, clone, plugins)
const pluginCloudinaryImage = cloneDeep(userCloudinaryImage);
strausr marked this conversation as resolved.
Show resolved Hide resolved
this.render(this.img, pluginCloudinaryImage, plugins)
.then(()=>{
this.img.setAttribute('src', clone.toURL());
this.img.setAttribute('src', pluginCloudinaryImage.toURL());
});
}

Expand Down
66 changes: 66 additions & 0 deletions html/src/internalConstnats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {colorize, grayscale, assistColorBlind} from "@cloudinary/base/actions/effect";
import {vectorize, pixelate, blur} from "@cloudinary/base/actions/effect";
import {Transformation} from "@cloudinary/base/transformation/Transformation";
import {pad, crop, fill} from "@cloudinary/base/actions/resize";
import {Background} from "@cloudinary/base/values/background";
import {compass} from "@cloudinary/base/values/gravity";
import {northEast} from "@cloudinary/base/values/gravity/qualifiers/compass/Compass";
import {format} from "@cloudinary/base/actions/delivery";
import {auto, svg} from "@cloudinary/base/values/format";

/**
* Predefined accessibility transformations
* @const {Object} Cloudinary.ACCESSIBILITY_MODES
*/
export const ACCESSIBILITY_MODES = {
'darkmode': colorize(70).color('black'),
'brightmode': colorize(40).color('white'),
'monochrome': grayscale(),
'colorblind': assistColorBlind()
};

/**
* Predefined vectorize placeholder transformation
*/
export const VECTORIZE =
new Transformation()
.effect(vectorize())
.delivery(format(svg()));

/**
* Predefined pixelate placeholder transformation
*/
export const PIXELATE =
new Transformation()
//@ts-ignore
.effect(pixelate())
.delivery(format(auto()));

/**
* Predefined blur placeholder transformation
*/
export const BLUR =
new Transformation()
//@ts-ignore
.effect(blur(2000))
.delivery(format(auto()));

/**
* Predefined predominant color placeholder transformation
*/
export const PREDOMINANT_COLOR_TRANSFORM =
new Transformation()
.resize(pad('iw_div_2').aspectRatio(1).background(Background.auto()))
.resize(crop(1,1).gravity(compass(northEast())))
.resize(fill().height('ih').width('iw'))
.delivery(format(auto()));

/**
* Predefined placeholder image options
*/
export const PLACEHOLDER_IMAGE_OPTIONS = {
'vectorize': VECTORIZE,
'pixelate': PIXELATE,
'blur': BLUR,
'predominant-color': PREDOMINANT_COLOR_TRANSFORM
};
66 changes: 47 additions & 19 deletions html/src/placeholder.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,55 @@
// import * as Effect from '@cloudinary/base/actions/effect/Effect';
import cloneDeep from 'lodash/cloneDeep'
import {isBrowser} from "./isBrowser";
import {CloudinaryImage} from "@cloudinary/base/assets/CloudinaryImage";
import {plugin} from "./types";
import {PLACEHOLDER_IMAGE_OPTIONS} from './internalConstnats';
import {placeholderMode} from './types';

export function placeholder(element: any, transformableImage: CloudinaryImage, toBeCanceled: any): Promise<void | string> | string {
if(isBrowser()){
const clonedObject = cloneDeep(transformableImage);
const plObject = clonedObject;
element.src = plObject.toURL();
console.log('placeholder loaded');
/**
* Returns the placeholder plugin
* @param mode Placeholder mode 'vectorize' | 'pixelate' | 'blur' | 'predominant-color'
*/
export function placeholder(mode='vectorize'): plugin{
return placeholderPlugin.bind(null, mode);
}

return new Promise((resolve: any) => {
toBeCanceled.push(()=>{
resolve('canceled');
clearTimeout(timeout); //signifies clearing events
});
/**
* Displays a placeholder image until the original image loads
* @param mode Placeholder mode 'vectorize' | 'pixelate' | 'blur' | 'predominant-color'
* @param element HTMLImageElement The image element
* @param pluginCloudinaryImage
* @param runningPlugins holds running plugins to be canceled
*/
function placeholderPlugin(mode?: placeholderMode, element?: HTMLImageElement, pluginCloudinaryImage?: CloudinaryImage, runningPlugins?: Function[]): Promise<void | string> | string {
const placeholderTransformation = preparePlaceholderTransformation(mode, pluginCloudinaryImage);
element.src = placeholderTransformation.toURL();

const timeout = setTimeout(() => {
console.log('img loaded');
resolve();
}, 5000); // arbitrary time
return new Promise((resolve: any) => {
runningPlugins.push(()=>{
element.src = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEAAAAALAAAAAABAAEAAAI=;';
resolve('canceled');
});
}else{
//transformableImage.effect(Effect.pixelate(50));

const largeImage = new Image();
largeImage.src = pluginCloudinaryImage.toURL();
largeImage.onload = () => {
resolve();
};
});
}

/**
* Prepares placeholder transformation by appending a placeholder-type transformation to the end of the URL
* @param mode Placeholder mode 'vectorize' | 'pixelate' | 'blur' | 'predominant-color'
* @param pluginCloudinaryImage
*/
function preparePlaceholderTransformation(mode?: placeholderMode, pluginCloudinaryImage?: CloudinaryImage){
const placeholderClonedImage = cloneDeep(pluginCloudinaryImage);

if(!PLACEHOLDER_IMAGE_OPTIONS[mode]){
mode = 'vectorize'
}
//appends a placeholder transformation on placeholderClonedImage
PLACEHOLDER_IMAGE_OPTIONS[mode].actions.forEach(transformation => placeholderClonedImage.addAction(transformation));

return placeholderClonedImage;
}
2 changes: 2 additions & 0 deletions html/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export type plugins = ((element: HTMLImageElement, cloudinaryImage: CloudinaryIm
export type plugin = (element: HTMLImageElement, cloudinaryImage: CloudinaryImage, runningPlugins: Function[]) => string | Promise<string | void>;

export type accessibilityMode = 'darkmode' | 'brightmode' | 'monochrome' | 'colorblind';

export type placeholderMode = 'vectorize' | 'pixelate' | 'blur' | 'predominant-color';
74 changes: 74 additions & 0 deletions react/__tests__/placeholder.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { CldImg, placeholder } from '../src'
import {CloudinaryImage} from "@cloudinary/base/assets/CloudinaryImage";
import CloudinaryConfig from "@cloudinary/base/config/CloudinaryConfig";
import {PLACEHOLDER_IMAGE_OPTIONS} from '../../html/src/internalConstnats';
import {mount} from 'enzyme';
import React from "react";
import {sepia} from "@cloudinary/base/actions/effect";

const CONFIG_INSTANCE = new CloudinaryConfig({
cloud: {
cloudName: 'demo'
}
});

let cl = new CloudinaryImage('sample').setConfig(CONFIG_INSTANCE);

describe('placeholder', () => {
strausr marked this conversation as resolved.
Show resolved Hide resolved
it("should apply default", function (done) {
let component = mount(<CldImg transformation={cl} plugins={[placeholder()]}/>);
setTimeout(()=>{
expect(component.html()).toBe(`<img src=\"https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.vectorize}/sample\">`);
done();
}, 0);// one tick
});

it("should apply 'vectorize'", function () {
let component = mount(<CldImg transformation={cl} plugins={[placeholder('vectorize')]}/>);
setTimeout(()=>{
expect(component.html()).toBe(`<img src=\"https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.vectorize}/sample\">`);
}, 0);// one tick
});

it("should apply pixelate", function (done) {
let component = mount(<CldImg transformation={cl} plugins={[placeholder('pixelate')]}/>);
setTimeout(()=>{
expect(component.html()).toBe(`<img src=\"https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.pixelate}/sample\">`);
done();
}, 0);// one tick
});

it("should apply blur", function (done) {
let component = mount(<CldImg transformation={cl} plugins={[placeholder('blur')]}/>);
setTimeout(()=>{
expect(component.html()).toBe(`<img src=\"https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.pixelate}/sample\">`);
done();
}, 0);// one tick
});

it("should apply predominant-color", function (done) {
let component = mount(<CldImg transformation={cl} plugins={[placeholder('predominant-color')]}/>);
setTimeout(()=>{
expect(component.html()).toBe(`<img src=\"https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS["predominant-color"]}/sample\">`);
done();
}, 0);// one tick
});

it("should default if supplied with incorrect mode", function (done) {
let component = mount(<CldImg transformation={cl} plugins={[placeholder('ddd')]}/>);
setTimeout(()=>{
expect(component.html()).toBe(`<img src=\"https://res.cloudinary.com/demo/image/upload/${PLACEHOLDER_IMAGE_OPTIONS.vectorize}/sample\">`);
done();
}, 0);// one tick
});

it("should append placeholder transformation", function (done) {
cl.effect(sepia());
let component = mount(<CldImg transformation={cl} plugins={[placeholder()]}/>);
setTimeout(()=>{
expect(component.html()).toBe(`<img src=\"https://res.cloudinary.com/demo/image/upload/e_sepia/${PLACEHOLDER_IMAGE_OPTIONS.vectorize}/sample\">`);
done();
}, 0);// one tick
});
});