Updated readme and tslint fixes

This commit is contained in:
Hugo Bernier 2021-03-21 00:32:07 -04:00
parent bea779b5fc
commit fe6481c156
18 changed files with 632 additions and 587 deletions

View File

@ -19,9 +19,10 @@ extensions:
## Summary
This solution contains an SPFx webpart that shows an HTML Image Editor based on canvas and [Office UI Fabric](https://developer.microsoft.com/fluentui/).
This solution contains an SPFx web part that shows an HTML Image Editor based on canvas and [Office UI Fabric](https://developer.microsoft.com/fluentui/).
Key features of the Editor
* Resize
* Crop
* Flip
@ -36,10 +37,13 @@ The Placeholder and FilePicker are components from the [sp-dev-fx-controls-react
![react-image-editor in action](assets/react-image-editor.gif)
## Used SharePoint Framework Version
## Compatibility
![version](https://img.shields.io/badge/version-1.4.0-green.svg)
> SharePoint 2019 and SharePoint Online
![SPFx 1.4.0](https://img.shields.io/badge/SPFx-1.4.0-green.svg)
![Node.js LTS 6.x](https://img.shields.io/badge/Node.js-LTS%206.x-green.svg)
![SharePoint 2016 | 2019 | Online](https://img.shields.io/badge/SharePoint-2016%20%7C%202019%20%7C%20Online-green.svg)
![Teams No: Not designed for Microsoft Teams](https://img.shields.io/badge/Teams-No-red.svg "Not designed for Microsoft Teams")
![Workbench Local | Hosted](https://img.shields.io/badge/Workbench-Local%20%7C%20Hosted-green.svg)
References to office-ui-fabric-react version 5.x because of SharePoint 2019 Support
@ -86,6 +90,7 @@ Version|Date|Comments
> Include any additional steps as needed.
## Usage
* PNP Placeholder control if not Configured
* PNP WebpartTitle control (toggle Show/Hide in property pane)
* PNP FilePicker control to pick Images (is mocked on localworkbench)

View File

@ -2,11 +2,11 @@
{
"name": "pnp-sp-dev-spfx-web-parts-react-image-editor",
"source": "pnp",
"title": "React Image Editor",
"shortDescription": "This solution contains an SPFx webpart that shows an HTML Image Editor based on canvas and Office UI Fabric",
"title": "Image Editor",
"shortDescription": "This solution contains an SPFx web part that shows an HTML Image Editor based on canvas and Office UI Fabric",
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/master/samples/react-image-editor",
"longDescription": [
"This solution contains an SPFx webpart that shows an HTML Image Editor based on canvas and Office UI Fabric ",
"This solution contains an SPFx web part that shows an HTML Image Editor based on canvas and Office UI Fabric ",
"Key features of the Editor",
"* Resize",
"* Crop",

View File

@ -1,25 +1,23 @@
import { IImageFilter } from "./IImageFilter";
import { IImageFilter } from './IImageFilter';
export class GrayscaleFilter implements IImageFilter {
public process(imageData: ImageData, width: number, height: number, nvalue?: number, svalue?: string): ImageData {
var data = imageData.data;
const data: Uint8ClampedArray = imageData.data;
//Get length of all pixels in image each pixel made up of 4 elements for each pixel, one for Red, Green, Blue and Alpha
var arraylength = width * height * 4;
//Common formula for converting to grayscale.
//gray = 0.3*R + 0.59*G + 0.11*B
for (var i = arraylength - 1; i > 0; i -= 4) {
//R= i-3, G = i-2 and B = i-1
//Get our gray shade using the formula
var gray = 0.3 * data[i - 3] + 0.59 * data[i - 2] + 0.11 * data[i - 1];
// Get length of all pixels in image each pixel made up of
// 4 elements for each pixel, one for Red, Green, Blue and Alpha
const arraylength: number = width * height * 4;
// Common formula for converting to grayscale.
// gray = 0.3*R + 0.59*G + 0.11*B
for (let i: number = arraylength - 1; i > 0; i -= 4) {
// R= i-3, G = i-2 and B = i-1
// Get our gray shade using the formula
const gray: number = 0.3 * data[i - 3] + 0.59 * data[i - 2] + 0.11 * data[i - 1];
data[i - 3] = gray;
data[i - 2] = gray;
data[i - 1] = gray;
}
return (imageData);
}
}

View File

@ -1,6 +1,6 @@
export interface IImageFilter {
//describing function,
//parameters are in left side parenthesis,
//right side 'string' is return type
// describing function,
// parameters are in left side parenthesis,
// right side 'string' is return type
process(imageData: ImageData, width: number, height: number, nvalue?: number, svalue?: string): ImageData;
}

View File

@ -1,14 +1,25 @@
import { IImageFilter } from "./IImageFilter";
import { IImageFilter } from './IImageFilter';
export class SepiaFilter implements IImageFilter {
private r: number[] = [0, 0, 0, 1, 1, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 44, 45, 47, 48, 49, 52, 54, 55, 57, 59, 60, 62, 65, 67, 69, 70, 72, 74, 77, 79, 81, 83, 86, 88, 90, 92, 94, 97, 99, 101, 103, 107, 109, 111, 112, 116, 118, 120, 124, 126, 127, 129, 133, 135, 136, 140, 142, 143, 145, 149, 150, 152, 155, 157, 159, 162, 163, 165, 167, 170, 171, 173, 176, 177, 178, 180, 183, 184, 185, 188, 189, 190, 192, 194, 195, 196, 198, 200, 201, 202, 203, 204, 206, 207, 208, 209, 211, 212, 213, 214, 215, 216, 218, 219, 219, 220, 221, 222, 223, 224, 225, 226, 227, 227, 228, 229, 229, 230, 231, 232, 232, 233, 234, 234, 235, 236, 236, 237, 238, 238, 239, 239, 240, 241, 241, 242, 242, 243, 244, 244, 245, 245, 245, 246, 247, 247, 248, 248, 249, 249, 249, 250, 251, 251, 252, 252, 252, 253, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255];
private g: number[] = [0, 0, 1, 2, 2, 3, 5, 5, 6, 7, 8, 8, 10, 11, 11, 12, 13, 15, 15, 16, 17, 18, 18, 19, 21, 22, 22, 23, 24, 26, 26, 27, 28, 29, 31, 31, 32, 33, 34, 35, 35, 37, 38, 39, 40, 41, 43, 44, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 76, 77, 79, 80, 81, 83, 84, 85, 86, 88, 89, 90, 92, 93, 94, 95, 96, 97, 100, 101, 102, 103, 105, 106, 107, 108, 109, 111, 113, 114, 115, 117, 118, 119, 120, 122, 123, 124, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 142, 144, 145, 146, 148, 149, 150, 151, 153, 154, 155, 157, 158, 159, 160, 162, 163, 164, 166, 167, 168, 169, 171, 172, 173, 174, 175, 176, 177, 178, 179, 181, 182, 183, 184, 186, 186, 187, 188, 189, 190, 192, 193, 194, 195, 195, 196, 197, 199, 200, 201, 202, 202, 203, 204, 205, 206, 207, 208, 208, 209, 210, 211, 212, 213, 214, 214, 215, 216, 217, 218, 219, 219, 220, 221, 222, 223, 223, 224, 225, 226, 226, 227, 228, 228, 229, 230, 231, 232, 232, 232, 233, 234, 235, 235, 236, 236, 237, 238, 238, 239, 239, 240, 240, 241, 242, 242, 242, 243, 244, 245, 245, 246, 246, 247, 247, 248, 249, 249, 249, 250, 251, 251, 252, 252, 252, 253, 254, 255];
private b: number[] = [53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 61, 61, 61, 62, 62, 63, 63, 63, 64, 65, 65, 65, 66, 66, 67, 67, 67, 68, 69, 69, 69, 70, 70, 71, 71, 72, 73, 73, 73, 74, 74, 75, 75, 76, 77, 77, 78, 78, 79, 79, 80, 81, 81, 82, 82, 83, 83, 84, 85, 85, 86, 86, 87, 87, 88, 89, 89, 90, 90, 91, 91, 93, 93, 94, 94, 95, 95, 96, 97, 98, 98, 99, 99, 100, 101, 102, 102, 103, 104, 105, 105, 106, 106, 107, 108, 109, 109, 110, 111, 111, 112, 113, 114, 114, 115, 116, 117, 117, 118, 119, 119, 121, 121, 122, 122, 123, 124, 125, 126, 126, 127, 128, 129, 129, 130, 131, 132, 132, 133, 134, 134, 135, 136, 137, 137, 138, 139, 140, 140, 141, 142, 142, 143, 144, 145, 145, 146, 146, 148, 148, 149, 149, 150, 151, 152, 152, 153, 153, 154, 155, 156, 156, 157, 157, 158, 159, 160, 160, 161, 161, 162, 162, 163, 164, 164, 165, 165, 166, 166, 167, 168, 168, 169, 169, 170, 170, 171, 172, 172, 173, 173, 174, 174, 175, 176, 176, 177, 177, 177, 178, 178, 179, 180, 180, 181, 181, 181, 182, 182, 183, 184, 184, 184, 185, 185, 186, 186, 186, 187, 188, 188, 188, 189, 189, 189, 190, 190, 191, 191, 192, 192, 193, 193, 193, 194, 194, 194, 195, 196, 196, 196, 197, 197, 197, 198, 199];
// tslint:disable-next-line: max-line-length
private r: number[] = [
0, 0, 0, 1, 1, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41, 42, 44, 45, 47, 48, 49, 52, 54, 55, 57, 59, 60, 62, 65, 67, 69, 70, 72, 74, 77, 79, 81, 83, 86, 88, 90, 92, 94, 97, 99, 101, 103, 107, 109, 111, 112, 116, 118, 120, 124, 126, 127, 129, 133, 135, 136, 140, 142, 143, 145, 149, 150, 152, 155, 157, 159, 162, 163, 165, 167, 170, 171, 173, 176, 177, 178, 180, 183, 184, 185, 188, 189, 190, 192, 194, 195, 196, 198, 200, 201, 202, 203, 204, 206, 207, 208, 209, 211, 212, 213, 214, 215, 216, 218, 219, 219, 220, 221, 222, 223, 224, 225, 226, 227, 227, 228, 229, 229, 230, 231, 232, 232, 233, 234, 234, 235, 236, 236, 237, 238, 238, 239, 239, 240, 241, 241, 242, 242, 243, 244, 244, 245, 245, 245, 246, 247, 247, 248, 248, 249, 249, 249, 250, 251, 251, 252, 252, 252, 253, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
];
// tslint:disable-next-line: max-line-length
private g: number[] = [
0, 0, 1, 2, 2, 3, 5, 5, 6, 7, 8, 8, 10, 11, 11, 12, 13, 15, 15, 16, 17, 18, 18, 19, 21, 22, 22, 23, 24, 26, 26, 27, 28, 29, 31, 31, 32, 33, 34, 35, 35, 37, 38, 39, 40, 41, 43, 44, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 76, 77, 79, 80, 81, 83, 84, 85, 86, 88, 89, 90, 92, 93, 94, 95, 96, 97, 100, 101, 102, 103, 105, 106, 107, 108, 109, 111, 113, 114, 115, 117, 118, 119, 120, 122, 123, 124, 126, 127, 128, 129, 131, 132, 133, 135, 136, 137, 138, 140, 141, 142, 144, 145, 146, 148, 149, 150, 151, 153, 154, 155, 157, 158, 159, 160, 162, 163, 164, 166, 167, 168, 169, 171, 172, 173, 174, 175, 176, 177, 178, 179, 181, 182, 183, 184, 186, 186, 187, 188, 189, 190, 192, 193, 194, 195, 195, 196, 197, 199, 200, 201, 202, 202, 203, 204, 205, 206, 207, 208, 208, 209, 210, 211, 212, 213, 214, 214, 215, 216, 217, 218, 219, 219, 220, 221, 222, 223, 223, 224, 225, 226, 226, 227, 228, 228, 229, 230, 231, 232, 232, 232, 233, 234, 235, 235, 236, 236, 237, 238, 238, 239, 239, 240, 240, 241, 242, 242, 242, 243, 244, 245, 245, 246, 246, 247, 247, 248, 249, 249, 249, 250, 251, 251, 252, 252, 252, 253, 254, 255
];
// tslint:disable-next-line: max-line-length
private b: number[] = [
53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 61, 61, 61, 62, 62, 63, 63, 63, 64, 65, 65, 65, 66, 66, 67, 67, 67, 68, 69, 69, 69, 70, 70, 71, 71, 72, 73, 73, 73, 74, 74, 75, 75, 76, 77, 77, 78, 78, 79, 79, 80, 81, 81, 82, 82, 83, 83, 84, 85, 85, 86, 86, 87, 87, 88, 89, 89, 90, 90, 91, 91, 93, 93, 94, 94, 95, 95, 96, 97, 98, 98, 99, 99, 100, 101, 102, 102, 103, 104, 105, 105, 106, 106, 107, 108, 109, 109, 110, 111, 111, 112, 113, 114, 114, 115, 116, 117, 117, 118, 119, 119, 121, 121, 122, 122, 123, 124, 125, 126, 126, 127, 128, 129, 129, 130, 131, 132, 132, 133, 134, 134, 135, 136, 137, 137, 138, 139, 140, 140, 141, 142, 142, 143, 144, 145, 145, 146, 146, 148, 148, 149, 149, 150, 151, 152, 152, 153, 153, 154, 155, 156, 156, 157, 157, 158, 159, 160, 160, 161, 161, 162, 162, 163, 164, 164, 165, 165, 166, 166, 167, 168, 168, 169, 169, 170, 170, 171, 172, 172, 173, 173, 174, 174, 175, 176, 176, 177, 177, 177, 178, 178, 179, 180, 180, 181, 181, 181, 182, 182, 183, 184, 184, 184, 185, 185, 186, 186, 186, 187, 188, 188, 188, 189, 189, 189, 190, 190, 191, 191, 192, 192, 193, 193, 193, 194, 194, 194, 195, 196, 196, 196, 197, 197, 197, 198, 199
];
private noise: number = 0;
public process(imageData: ImageData, width: number, height: number, nvalue?: number, svalue?: string): ImageData {
//var data: Uint8ClampedArray = imageData.data;
public process(imageData: ImageData, _width: number, _height: number, nvalue?: number, _svalue?: string): ImageData {
// var data: Uint8ClampedArray = imageData.data;
this.noise = nvalue;
for (var i = 0; i < imageData.data.length; i += 4) {
for (let i: number = 0; i < imageData.data.length; i += 4) {
// change image colors
imageData.data[i] = this.r[imageData.data[i]];
imageData.data[i + 1] = this.g[imageData.data[i + 1]];
@ -16,14 +27,13 @@ export class SepiaFilter implements IImageFilter {
if (this.noise > 0) {
this.noise = Math.round(this.noise - Math.random() * this.noise);
for (var j = 0; j < 3; j++) {
var iPN = this.noise + imageData.data[i + j];
for (let j: number = 0; j < 3; j++) {
const iPN: number = this.noise + imageData.data[i + j];
imageData.data[i + j] = (iPN > 255) ? 255 : iPN;
}
}
}
return (imageData);
}
}

View File

@ -2,18 +2,25 @@ import { Icon } from 'office-ui-fabric-react';
import * as React from 'react';
import styles from './ImageManipulation.module.scss';
import { IImageManipulationSettings, manipulationTypeData } from './ImageManipulation.types';
import {
IImageManipulationSettings,
manipulationTypeData,
IManipulationTypeDataDetails } from './ImageManipulation.types';
export const historyItem = (item:IImageManipulationSettings, index:number): JSX.Element => {
if(!item) {
// tslint:disable-next-line: typedef
export const historyItem = (item: IImageManipulationSettings, _index: number): JSX.Element => {
if (!item) {
return undefined;
}
const data=manipulationTypeData[item.type];
const data: IManipulationTypeDataDetails = manipulationTypeData[item.type];
const detailrender = data.toHTML(item);
const detailrender: JSX.Element = data.toHTML(item);
return (
<span className={styles.historyItem}>
<span className={styles.historyItemIcon}>{data.svgIcon ? <img className={styles.historyItemSvg} src={data.svgIcon} /> : <Icon iconName={data.iconName} />}</span>
<span className={styles.historyItemIcon}>{data.svgIcon ?
// tslint:disable-next-line: react-a11y-img-has-alt
<img className={styles.historyItemSvg} src={data.svgIcon} /> :
<Icon iconName={data.iconName} />}</span>
<span className={styles.historyItemText}>{data.text}</span>
<span className={styles.historyItemDetails}>{detailrender}</span>
</span>

View File

@ -1,6 +1,17 @@
import { DisplayMode } from '@microsoft/sp-core-library';
import { clone } from '@microsoft/sp-lodash-subset';
import { Checkbox, DefaultButton, findIndex, Icon, IconButton, IsFocusVisibleClassName, ISlider, Panel, PanelType, Slider, TextField } from 'office-ui-fabric-react';
import {
Checkbox,
DefaultButton,
findIndex,
Icon,
IconButton,
ISlider,
Panel,
PanelType,
Slider,
TextField
} from 'office-ui-fabric-react';
import * as React from 'react';
import ImageCrop from './components/ImageCrop';
@ -9,12 +20,28 @@ import ItemOrder from './components/ItemOrder';
import { GrayscaleFilter } from './Filter/GrayscaleFilter';
import { SepiaFilter } from './Filter/SepiaFilter';
import { historyItem } from './HistoryItem';
import { historyItem } from './historyItem';
import styles from './ImageManipulation.module.scss';
import { FilterType, filterTypeData, ICrop, ICropSettings, IFilterSettings, IFlipSettings, IImageManipulationSettings, IManipulationTypeDataDetails, IResizeSettings, IRotateSettings, IScaleSettings, ManipulationType, manipulationTypeData, SettingPanelType } from './ImageManipulation.types';
import {
FilterType,
filterTypeData,
ICrop,
ICropSettings,
IFilterSettings,
IFlipSettings,
IImageManipulationSettings,
IManipulationTypeDataDetails,
IResizeSettings,
IRotateSettings,
IScaleSettings,
ManipulationType,
manipulationTypeData,
SettingPanelType
} from './ImageManipulation.types';
// tslint:disable-next-line: no-any
const flipVerticalIcon: any = require('../../svg/flipVertical.svg');
// tslint:disable-next-line: no-any
const flipHorizontalIcon: any = require('../../svg/flipHorizontal.svg');
import * as strings from 'ImageManipulationStrings';
@ -39,16 +66,14 @@ export interface IImageManipulationState {
}
export class ImageManipulation extends React.Component<IImageManipulationProps, IImageManipulationState> {
private img: HTMLImageElement = null;
private wrapperRef: HTMLDivElement = null;
private bufferRef: HTMLCanvasElement = null;
private bufferCtx: CanvasRenderingContext2D = null;
private canvasRef: HTMLCanvasElement = null;
private canvasCtx: CanvasRenderingContext2D = null;
private manipulateRef: HTMLCanvasElement = null;
private manipulateCtx: CanvasRenderingContext2D = null;
private img: HTMLImageElement = undefined;
private wrapperRef: HTMLDivElement = undefined;
private bufferRef: HTMLCanvasElement = undefined;
private bufferCtx: CanvasRenderingContext2D = undefined;
private canvasRef: HTMLCanvasElement = undefined;
private canvasCtx: CanvasRenderingContext2D = undefined;
private manipulateRef: HTMLCanvasElement = undefined;
private manipulateCtx: CanvasRenderingContext2D = undefined;
constructor(props: IImageManipulationProps) {
super(props);
@ -65,11 +90,12 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
this.setManipulateRef = this.setManipulateRef.bind(this);
this.setScale = this.setScale.bind(this);
this.closeFilter = this.closeFilter.bind(this);
}
public componentDidMount(): void {
this.imageChanged(this.props.src);
}
public componentDidUpdate(prevProps: IImageManipulationProps): void {
if (prevProps.src !== this.props.src) {
this.imageChanged(this.props.src);
@ -78,187 +104,10 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
}
}
private imageChanged(url: string) {
this.img = new Image();
this.img.src = url;
this.img.crossOrigin = "Anonymous";
this.img.onload = () => {
this.applySettings();
};
this.img.onerror = (event: Event | string) => {
if (this.props.imgLoadError) {
this.props.imgLoadError();
}
};
}
private applySettings(): void {
this.canvasRef.width = this.img.width;
this.canvasRef.height = this.img.height;
this.bufferRef.width = this.img.width;
this.bufferRef.height = this.img.height;
this.manipulateRef.width = this.img.width;
this.manipulateRef.height = this.img.height;
let currentwidth = this.img.width;
let currentheight = this.img.height;
let newwidth = currentwidth;
let newheight = currentheight;
this.bufferCtx.clearRect(0, 0, currentwidth, currentheight);
this.bufferCtx.drawImage(this.img, 0, 0);
if (this.props.settings) {
this.props.settings.forEach((element, index) => {
this.manipulateCtx.clearRect(0, 0, currentwidth, currentheight);
this.manipulateRef.width = currentwidth;
this.manipulateRef.height = currentheight;
this.manipulateCtx.save();
let nothingToDo: boolean = false;
switch (element.type) {
case ManipulationType.Flip:
const filp = element as IFlipSettings;
if (filp.flipY) {
this.manipulateCtx.translate(0, currentheight);
this.manipulateCtx.scale(1, -1);
}
if (filp.flipX) {
this.manipulateCtx.translate(currentwidth, 0);
this.manipulateCtx.scale(-1, 1);
}
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
break;
case ManipulationType.Rotate:
const rotate = element as IRotateSettings;
if (!rotate.rotate || rotate.rotate === 0 || isNaN(rotate.rotate)) {
nothingToDo = true;
break;
}
if (rotate.rotate) {
const angelcalc = rotate.rotate * Math.PI / 180;
const oldwidth = currentwidth;
const oldheight = currentheight;
let offsetwidth = 0;
let offsetheight = 0;
var a = oldwidth * Math.abs(Math.cos(angelcalc));
var b = oldheight * Math.abs(Math.sin(angelcalc));
var p = oldwidth * Math.abs(Math.sin(angelcalc));
var q = oldheight * Math.abs(Math.cos(angelcalc));
newwidth = a + b;
newheight = p + q;
offsetwidth = (newwidth - oldwidth) / 2;
offsetheight = (newheight - oldheight) / 2;
this.manipulateRef.width = newwidth;
this.manipulateRef.height = newheight;
this.manipulateCtx.translate(newwidth / 2, newheight / 2);
this.manipulateCtx.rotate(angelcalc);
this.manipulateCtx.translate(newwidth / 2 * -1, newheight / 2 * -1);
this.manipulateCtx.drawImage(this.bufferRef, offsetwidth, offsetheight);
}
break;
case ManipulationType.Scale:
const scale = element as IScaleSettings;
if (scale.scale) {
this.manipulateCtx.translate(currentwidth / 2, currentheight / 2);
this.manipulateCtx.scale(scale.scale, scale.scale);
this.manipulateCtx.translate(currentwidth / 2 * -1, currentheight / 2 * -1);
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
}
break;
case ManipulationType.Filter:
nothingToDo = true;
const filter = element as IFilterSettings;
var imageData = this.bufferCtx.getImageData(0, 0, currentwidth, currentheight);
switch (filter.filterType) {
case FilterType.Grayscale:
imageData = new GrayscaleFilter().process(imageData, currentwidth, currentheight, undefined, undefined);
break;
case FilterType.Sepia:
imageData = new SepiaFilter().process(imageData, currentwidth, currentheight, undefined, undefined);
break;
}
this.bufferCtx.putImageData(imageData, 0, 0);
break;
case ManipulationType.Crop:
const last = this.props.settings.length === index + 1;
if (last && this.state.settingPanel === SettingPanelType.Crop) {
//Do nothingis last and current edit
nothingToDo = true;
} else {
const crop = element as ICropSettings;
const sourceX = crop.sx;
const sourceY = crop.sy;
newwidth = crop.width;
newheight = crop.height;
this.manipulateRef.width = newwidth;
this.manipulateRef.height = newheight;
this.manipulateCtx.drawImage(this.bufferRef, sourceX, sourceY, newwidth, newheight, 0, 0, newwidth, newheight);
}
break;
case ManipulationType.Resize:
const resize = element as IResizeSettings;
newwidth = resize.width;
newheight = resize.height;
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
}
this.manipulateCtx.restore();
if (!nothingToDo) {
this.bufferCtx.clearRect(0, 0, currentwidth, currentheight);
this.bufferRef.width = newwidth;
this.bufferRef.height = newheight;
this.bufferCtx.clearRect(0, 0, newwidth, newheight);
this.bufferCtx.drawImage(this.manipulateRef, 0, 0, newwidth, newheight);
currentwidth = newwidth;
currentheight = newheight;
}
});
}
/*this.canvasCtx.clearRect(0, 0, this.canvasRef.width, this.canvasRef.height)
// this.canvasCtx.drawImage(this.bufferRef, 0, 0);
const sourceX = 400;
const sourceY = 200;
const sourceWidth = 1200;
const sourceHeight = 600;
this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, this.canvasRef.width, this.canvasRef.height);
*/
this.canvasCtx.clearRect(0, 0, currentwidth, currentheight);
this.canvasRef.width = currentwidth;
this.canvasRef.height = currentheight;
this.canvasCtx.drawImage(this.bufferRef, 0, 0);
// this.canvasCtx.drawImage(this.bufferRef, 0, 0, currentwidth, currentheight);
this.wrapperRef.style.width = currentwidth + 'px';
//this.wrapperRef.style.height = currentheight + 'px';
// let height = this.canvasRef.height;
// let width = this.canvasRef.width;
//this.canvasctx.translate(this.canvasRef.width / 2 * -1, this.canvasRef.height / 2 * -1);
}
public render(): React.ReactElement<IImageManipulationProps> {
return (
<div className={styles.imageEditor} style={{
marginTop: this.props.displyMode === DisplayMode.Edit ? '40px' : '0px',
marginTop: this.props.displyMode === DisplayMode.Edit ? '40px' : '0px'
}} >
{this.props.displyMode === DisplayMode.Edit && this.getCommandBar()}
<div className={styles.imageplaceholder + ' ' + this.getMaxWidth()}
@ -280,8 +129,189 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
</div>
);
}
private imageChanged(url: string): void {
this.img = new Image();
this.img.src = url;
this.img.crossOrigin = 'Anonymous';
this.img.onload = () => {
this.applySettings();
};
this.img.onerror = (event: Event | string) => {
if (this.props.imgLoadError) {
this.props.imgLoadError();
}
};
}
private applySettings(): void {
this.canvasRef.width = this.img.width;
this.canvasRef.height = this.img.height;
this.bufferRef.width = this.img.width;
this.bufferRef.height = this.img.height;
this.manipulateRef.width = this.img.width;
this.manipulateRef.height = this.img.height;
let currentwidth: number = this.img.width;
let currentheight: number = this.img.height;
let newwidth: number = currentwidth;
let newheight: number = currentheight;
this.bufferCtx.clearRect(0, 0, currentwidth, currentheight);
this.bufferCtx.drawImage(this.img, 0, 0);
if (this.props.settings) {
this.props.settings.forEach((element, index) => {
this.manipulateCtx.clearRect(0, 0, currentwidth, currentheight);
this.manipulateRef.width = currentwidth;
this.manipulateRef.height = currentheight;
this.manipulateCtx.save();
let nothingToDo: boolean = false;
switch (element.type) {
case ManipulationType.Flip:
const filp: IFlipSettings = element as IFlipSettings;
if (filp.flipY) {
this.manipulateCtx.translate(0, currentheight);
this.manipulateCtx.scale(1, -1);
}
if (filp.flipX) {
this.manipulateCtx.translate(currentwidth, 0);
this.manipulateCtx.scale(-1, 1);
}
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
break;
case ManipulationType.Rotate:
const rotate: IRotateSettings = element as IRotateSettings;
if (!rotate.rotate || rotate.rotate === 0 || isNaN(rotate.rotate)) {
nothingToDo = true;
break;
}
if (rotate.rotate) {
const angelcalc: number = rotate.rotate * Math.PI / 180;
const oldwidth: number = currentwidth;
const oldheight: number = currentheight;
let offsetwidth: number = 0;
let offsetheight: number = 0;
const a: number = oldwidth * Math.abs(Math.cos(angelcalc));
const b: number = oldheight * Math.abs(Math.sin(angelcalc));
const p: number = oldwidth * Math.abs(Math.sin(angelcalc));
const q: number = oldheight * Math.abs(Math.cos(angelcalc));
newwidth = a + b;
newheight = p + q;
offsetwidth = (newwidth - oldwidth) / 2;
offsetheight = (newheight - oldheight) / 2;
this.manipulateRef.width = newwidth;
this.manipulateRef.height = newheight;
this.manipulateCtx.translate(newwidth / 2, newheight / 2);
this.manipulateCtx.rotate(angelcalc);
this.manipulateCtx.translate(newwidth / 2 * -1, newheight / 2 * -1);
this.manipulateCtx.drawImage(this.bufferRef, offsetwidth, offsetheight);
}
break;
case ManipulationType.Scale:
const scale: IScaleSettings = element as IScaleSettings;
if (scale.scale) {
this.manipulateCtx.translate(currentwidth / 2, currentheight / 2);
this.manipulateCtx.scale(scale.scale, scale.scale);
this.manipulateCtx.translate(currentwidth / 2 * -1, currentheight / 2 * -1);
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
}
break;
case ManipulationType.Filter:
nothingToDo = true;
const filter: IFilterSettings = element as IFilterSettings;
let imageData: ImageData = this.bufferCtx.getImageData(0, 0, currentwidth, currentheight);
switch (filter.filterType) {
case FilterType.Grayscale:
imageData = new GrayscaleFilter().process(imageData, currentwidth, currentheight, undefined, undefined);
break;
case FilterType.Sepia:
imageData = new SepiaFilter().process(imageData, currentwidth, currentheight, undefined, undefined);
break;
}
this.bufferCtx.putImageData(imageData, 0, 0);
break;
case ManipulationType.Crop:
const last: boolean = this.props.settings.length === index + 1;
if (last && this.state.settingPanel === SettingPanelType.Crop) {
// Do nothing is last and current edit
nothingToDo = true;
} else {
const crop: ICropSettings = element as ICropSettings;
const sourceX: number = crop.sx;
const sourceY: number = crop.sy;
newwidth = crop.width;
newheight = crop.height;
this.manipulateRef.width = newwidth;
this.manipulateRef.height = newheight;
this.manipulateCtx.drawImage(
this.bufferRef,
sourceX,
sourceY,
newwidth,
newheight,
0,
0,
newwidth,
newheight);
}
break;
case ManipulationType.Resize:
const resize: IResizeSettings = element as IResizeSettings;
newwidth = resize.width;
newheight = resize.height;
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
}
this.manipulateCtx.restore();
if (!nothingToDo) {
this.bufferCtx.clearRect(0, 0, currentwidth, currentheight);
this.bufferRef.width = newwidth;
this.bufferRef.height = newheight;
this.bufferCtx.clearRect(0, 0, newwidth, newheight);
this.bufferCtx.drawImage(this.manipulateRef, 0, 0, newwidth, newheight);
currentwidth = newwidth;
currentheight = newheight;
}
});
}
/*this.canvasCtx.clearRect(0, 0, this.canvasRef.width, this.canvasRef.height)
// this.canvasCtx.drawImage(this.bufferRef, 0, 0);
const sourceX = 400;
const sourceY = 200;
const sourceWidth = 1200;
const sourceHeight = 600;
this.canvasCtx.drawImage(
this.bufferRef, sourceX, sourceY, sourceWidth,
sourceHeight, 0, 0, this.canvasRef.width, this.canvasRef.height);
*/
this.canvasCtx.clearRect(0, 0, currentwidth, currentheight);
this.canvasRef.width = currentwidth;
this.canvasRef.height = currentheight;
this.canvasCtx.drawImage(this.bufferRef, 0, 0);
// this.canvasCtx.drawImage(this.bufferRef, 0, 0, currentwidth, currentheight);
this.wrapperRef.style.width = currentwidth + 'px';
// this.wrapperRef.style.height = currentheight + 'px';
// let height = this.canvasRef.height;
// let width = this.canvasRef.width;
// this.canvasctx.translate(this.canvasRef.width / 2 * -1, this.canvasRef.height / 2 * -1);
}
private getCropGrid(): JSX.Element {
const lastset = this.getLastManipulation() as ICropSettings;
const lastset: ICropSettings = this.getLastManipulation() as ICropSettings;
let lastdata: ICrop = { sx: 0, sy: 0, width: 0, height: 0 };
if (lastset && lastset.type === ManipulationType.Crop) {
@ -300,7 +330,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getResizeGrid(): JSX.Element {
const lastset = this.getLastManipulation() as IResizeSettings;
const lastset: IResizeSettings = this.getLastManipulation() as IResizeSettings;
if (lastset && lastset.type === ManipulationType.Resize) {
return (<ImageGrid
width={lastset.width} height={lastset.height}
@ -310,7 +340,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
return (<ImageGrid
onChange={(size) => this.setResize(size.width, size.height, undefined)}
//aspect={this.getAspect()}
// aspect={this.getAspect()}
width={this.canvasRef.width} height={this.canvasRef.height} />);
}
@ -323,7 +353,9 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private isFilterActive(type: FilterType): boolean {
return (this.props.settings && this.props.settings.filter((f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type).length > 0);
return (this.props.settings && this.props.settings.filter(
(f) => f.type === ManipulationType.Filter
&& (f as IFilterSettings).filterType === type).length > 0);
}
private closeFilter(): void {
this.setState({
@ -379,7 +411,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}, () => this.toggleEditMode(true));
}
private toggleEditMode(mode: boolean) {
private toggleEditMode(mode: boolean): void {
if (this.props.editMode) {
this.props.editMode(mode);
}
@ -408,8 +440,6 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
this.props.settingschanged(newhist);
}
}
}}
onRenderItem={historyItem}
/>);
@ -431,10 +461,12 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private toggleFilter(type: FilterType, nvalue: number = undefined, svalue: string = undefined): void {
let tmpsettings = clone(this.props.settings);
let tmpsettings: IImageManipulationSettings[] = clone(this.props.settings);
if (!tmpsettings) { tmpsettings = []; }
if (tmpsettings.filter((f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type).length > 0) {
const removeindex = findIndex(tmpsettings, (f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type);
if (tmpsettings.filter(
(f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type).length > 0) {
const removeindex: number = findIndex(tmpsettings,
(f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type);
tmpsettings.splice(removeindex, 1);
} else {
tmpsettings.push({
@ -453,12 +485,15 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
return (<div className={styles.buttonHolderPanel} >
<IconButton
iconProps={{ iconName: 'SwitcherStartEnd' }}
onRenderIcon={() => { return (<img className={styles.svgbuttonPanel} src={flipVerticalIcon} />); }}
onRenderIcon={() => {
// tslint:disable-next-line: react-a11y-img-has-alt
return (<img className={styles.svgbuttonPanel} src={flipVerticalIcon} />);
}}
title={strings.FlipHorizontal}
ariaLabel={strings.FlipHorizontal}
onClick={() => {
let last = this.getLastManipulation();
const last: IImageManipulationSettings = this.getLastManipulation();
if (last && last.type === ManipulationType.Flip) {
(last as IFlipSettings).flipX = !(last as IFlipSettings).flipX;
if ((last as IFlipSettings).flipX === false &&
@ -473,11 +508,14 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}}
/>
<IconButton
onRenderIcon={() => { return (<img className={styles.svgbuttonPanel} src={flipHorizontalIcon} />); }}
onRenderIcon={() => {
// tslint:disable-next-line: react-a11y-img-has-alt
return (<img className={styles.svgbuttonPanel} src={flipHorizontalIcon} />);
}}
title={strings.FlipVertical}
ariaLabel={strings.FlipVertical}
onClick={() => {
let last = this.getLastManipulation();
const last: IImageManipulationSettings = this.getLastManipulation();
if (last && last.type === ManipulationType.Flip) {
(last as IFlipSettings).flipY = !(last as IFlipSettings).flipY;
if ((last as IFlipSettings).flipX === false &&
@ -495,8 +533,8 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
</div>);
}
private getRotateSettings(): JSX.Element {
const lastvalue = this.getLastManipulation();
let rotatevalue = 0;
const lastvalue: IImageManipulationSettings = this.getLastManipulation();
let rotatevalue: number = 0;
if (lastvalue && lastvalue.type === ManipulationType.Rotate) {
rotatevalue = (lastvalue as IRotateSettings).rotate ? (lastvalue as IRotateSettings).rotate : 0;
}
@ -506,7 +544,6 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
let icon: string = 'CompassNW';
if (value !== 0) { icon = 'Rotate'; }
return (<DefaultButton
key={'rotate' + index}
onClick={() => {
@ -522,7 +559,6 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
<span className={styles.imgtext} >{'' + value}</span></DefaultButton>);
})}
</div>
<Slider
label=''
@ -533,14 +569,17 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
onChange={this.setRotate}
showValue={true}
componentRef={(component: ISlider | null) => {
//Initial Value has a bug 0 is min value only min value is negative
const correctBugComponent = component as any;
if (correctBugComponent && correctBugComponent.state && correctBugComponent.value != correctBugComponent.props.value) {
// Initial Value has a bug 0 is min value only min value is negative
// tslint:disable-next-line: no-any
const correctBugComponent: any = component as any;
if (correctBugComponent
&& correctBugComponent.state
&& correctBugComponent.value !== correctBugComponent.props.value) {
correctBugComponent.setState({ value: 0, renderedValue: 0 });
}
}}
//originFromZero
// originFromZero
/>
<IconButton
key={'resetrotate'}
@ -554,7 +593,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getCropSettings(): JSX.Element {
let crop: ICropSettings = this.getCropValues();
const crop: ICropSettings = this.getCropValues();
return (<div>
<Checkbox
label={strings.LockAspect}
@ -569,16 +608,31 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}}
/>
<TextField label={strings.SourceX} value={'' + crop.sx} onChanged={(x) => this.setCrop(parseInt(x), undefined, undefined, undefined, crop.aspect)} />
<TextField label={strings.SourceY} value={'' + crop.sy} onChanged={(y) => this.setCrop(undefined, parseInt(y), undefined, undefined, crop.aspect)} />
<TextField label={strings.Width} value={'' + crop.width} onChanged={(w) => this.setCrop(undefined, undefined, parseInt(w), undefined, crop.aspect)} />
<TextField label={strings.Height} value={'' + crop.height} onChanged={(h) => this.setCrop(undefined, undefined, undefined, parseInt(h), crop.aspect)} />
<TextField
label={strings.SourceX}
value={'' + crop.sx}
// tslint:disable-next-line: radix
onChanged={(x) => this.setCrop(parseInt(x), undefined, undefined, undefined, crop.aspect)} />
<TextField
label={strings.SourceY}
value={'' + crop.sy}
// tslint:disable-next-line: radix
onChanged={(y) => this.setCrop(undefined, parseInt(y), undefined, undefined, crop.aspect)} />
<TextField
label={strings.Width}
value={'' + crop.width}
// tslint:disable-next-line: radix
onChanged={(w) => this.setCrop(undefined, undefined, parseInt(w), undefined, crop.aspect)} />
<TextField
label={strings.Height}
value={'' + crop.height}
// tslint:disable-next-line: radix
onChanged={(h) => this.setCrop(undefined, undefined, undefined, parseInt(h), crop.aspect)} />
</div>);
}
private getResizeSettings(): JSX.Element {
let resize: IResizeSettings = this.getResizeValues();
const resize: IResizeSettings = this.getResizeValues();
return (<div>
<Checkbox
@ -594,8 +648,15 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}}
/>
<TextField label={strings.Width} value={'' + resize.width} onChanged={(w) => this.setResize(parseInt(w), undefined, resize.aspect)} />
<TextField label={strings.Height} value={'' + resize.height} onChanged={(h) => this.setResize(undefined, parseInt(h), resize.aspect)} />
<TextField label={strings.Width}
value={'' + resize.width}
// tslint:disable-next-line: radix
onChanged={(w) => this.setResize(parseInt(w), undefined, resize.aspect)}
/>
<TextField label={strings.Height} value={'' + resize.height}
// tslint:disable-next-line: radix
onChanged={(h) => this.setResize(undefined, parseInt(h), resize.aspect)}
/>
</div>);
}
@ -604,10 +665,11 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getScaleSettings(): JSX.Element {
const lastvalue = this.getLastManipulation();
let scalevalue = 1;
const lastvalue: IImageManipulationSettings = this.getLastManipulation();
let scalevalue: number = 1;
if (lastvalue && lastvalue.type === ManipulationType.Scale) {
scalevalue = (lastvalue as IScaleSettings).scale ? (lastvalue as IScaleSettings).scale : 1;
scalevalue = (lastvalue as IScaleSettings).scale ?
(lastvalue as IScaleSettings).scale : 1;
}
return (<div>
@ -633,7 +695,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getResizeValues(): IResizeSettings {
let state: IImageManipulationSettings = this.getLastManipulation();
const state: IImageManipulationSettings = this.getLastManipulation();
let values: IResizeSettings = {
type: ManipulationType.Resize,
height: this.bufferRef.height,
@ -646,7 +708,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private setResize(width: number, height: number, aspect: number): void {
let values: IResizeSettings = this.getResizeValues();
const values: IResizeSettings = this.getResizeValues();
if (width) {
values.width = width;
if (aspect) {
@ -664,7 +726,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getCropValues(): ICropSettings {
let state: IImageManipulationSettings = this.getLastManipulation();
const state: IImageManipulationSettings = this.getLastManipulation();
let values: ICropSettings = {
type: ManipulationType.Crop,
sx: 0,
@ -679,7 +741,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private setCrop(sx: number, sy: number, width: number, height: number, aspect: number): void {
let values = this.getCropValues();
const values: ICropSettings = this.getCropValues();
const currentheight: number = this.bufferRef.height;
const currentwidth: number = this.bufferRef.width;
if (!isNaN(sx) && sx >= 0) {
@ -722,22 +784,21 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
}
if (isNaN(values.aspect) && !isNaN(aspect)) {
//aspect added
// aspect added
//limit w
// limit w
if ((values.width + values.sx) > currentwidth) {
values.width = currentwidth - values.sx;
}
values.height = values.width / aspect;
//limit h adn recalulate w
// limit h adn recalulate w
if ((values.height + values.sy) > currentheight) {
values.height = currentheight - values.sy;
values.width = values.height * aspect;
}
}
values.aspect = aspect;
if (aspect && (!isNaN(sx) || !isNaN(width))) {
values.height = values.width / aspect;
@ -762,8 +823,8 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
});
}
private calcRotate(value: number): void {
const lastVal = this.getLastManipulation();
let cvalue = 0;
const lastVal: IImageManipulationSettings = this.getLastManipulation();
let cvalue: number = 0;
if (lastVal && lastVal.type === ManipulationType.Rotate) {
cvalue = (lastVal as IRotateSettings).rotate;
}
@ -810,18 +871,17 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
return undefined;
}
private addOrUpdateLastManipulation(changed: IImageManipulationSettings): void {
let state = clone(this.props.settings);
let state: IImageManipulationSettings[] = clone(this.props.settings);
if (!state) {
state = [];
}
if (state.length > 0 && state[state.length - 1].type === changed.type) {
state[state.length - 1] = changed;
} else {
state.push(changed);
}
if (this.state.redosettings && this.state.redosettings.length > 0) {
this.setState({ redosettings: [] }, () => {
if (this.props.settingschanged) {
@ -837,7 +897,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
private removeLastManipulation(): void {
if (this.props.settings && this.props.settings.length > 0) {
let state = clone(this.props.settings);
const state: IImageManipulationSettings[] = clone(this.props.settings);
state.splice(state.length - 1, 1);
if (this.props.settingschanged) {
this.props.settingschanged(clone(state));
@ -850,6 +910,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
iconProps={{ iconName: options.iconName }}
onRenderIcon={(p, defaultrenderer) => {
if (options.svgIcon) {
// tslint:disable-next-line: react-a11y-img-has-alt
return (<img className={styles.svgbutton} src={options.svgIcon} />);
}
return defaultrenderer(p);
@ -875,9 +936,9 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
ariaLabel={strings.CommandBarUndo}
disabled={!this.props.settings || this.props.settings.length < 1}
onClick={() => {
const settings = clone(this.props.settings);
const last = settings.pop();
const redo = clone(this.state.redosettings);
const settings: IImageManipulationSettings[] = clone(this.props.settings);
const last: IImageManipulationSettings = settings.pop();
const redo: IImageManipulationSettings[] = clone(this.state.redosettings);
redo.push(last);
this.setState({ redosettings: redo },
() => {
@ -894,9 +955,9 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
ariaLabel={strings.CommandBarRedo}
disabled={!this.state.redosettings || this.state.redosettings.length < 1}
onClick={() => {
const redosettings = clone(this.state.redosettings);
const redo = redosettings.pop();
const settings = clone(this.props.settings);
const redosettings: IImageManipulationSettings[] = clone(this.state.redosettings);
const redo: IImageManipulationSettings = redosettings.pop();
const settings: IImageManipulationSettings[] = clone(this.props.settings);
settings.push(redo);
this.setState({ redosettings: redosettings },
() => {
@ -930,7 +991,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
onClick={() => this.openPanel(SettingPanelType.History)}
/>
<Panel
isOpen={this.state.settingPanel != SettingPanelType.Closed}
isOpen={this.state.settingPanel !== SettingPanelType.Closed}
type={PanelType.smallFixedFar}
onDismiss={this.closeFilter}
headerText={this.getPanelHeader(this.state.settingPanel)}

View File

@ -1,8 +1,12 @@
import * as React from 'react';
// tslint:disable-next-line: no-any
const colorFilterIcon: any = require('../../svg/colorFilter.svg');
// tslint:disable-next-line: no-any
const cropIcon: any = require('../../svg/crop.svg');
// tslint:disable-next-line: no-any
const flipVerticalIcon: any = require('../../svg/flipVertical.svg');
// tslint:disable-next-line: no-any
const resizeIcon: any = require('../../svg/resize.svg');
import * as strings from 'ImageManipulationStrings';
@ -28,7 +32,7 @@ export enum SettingPanelType {
export enum FilterType {
Grayscale,
Sepia,
Sepia
/*
Blur,
Emboss,
@ -42,7 +46,6 @@ export enum FilterType {
ColorOverLay*/
}
export interface IManipulationBase {
type: ManipulationType;
}
@ -84,8 +87,13 @@ export interface IResizeSettings extends IManipulationBase, IResize {
}
export type IImageManipulationSettings = IFilterSettings | IRotateSettings | IScaleSettings | IFlipSettings | ICropSettings | IResizeSettings;
export type IImageManipulationSettings =
IFilterSettings
| IRotateSettings
| IScaleSettings
| IFlipSettings
| ICropSettings
| IResizeSettings;
export const filterTypeData: IFilterTypeData = {
0: strings.FilterTypeGrayscale,
@ -99,6 +107,7 @@ export interface IFilterTypeData {
export interface IManipulationTypeDataBase {
text: string;
iconName?: string;
// tslint:disable-next-line: no-any
svgIcon?: any;
settingPanelType: SettingPanelType;
}
@ -117,7 +126,7 @@ export const manipulationTypeData: IManipulationTypeData = {
svgIcon: cropIcon,
toHTML: (item: ICropSettings) => {
return (<span></span>);
//return (<span>{`X:${item.sx} Y:${item.sy}`}</span>);
// return (<span>{`X:${item.sx} Y:${item.sy}`}</span>);
},
settingPanelType: SettingPanelType.Crop
},
@ -153,8 +162,5 @@ export const manipulationTypeData: IManipulationTypeData = {
svgIcon: resizeIcon,
toHTML: (item: IResizeSettings) => { return (<span></span>); },
settingPanelType: SettingPanelType.Resize
},
}
};

View File

@ -1,5 +1,3 @@
import { noWrap } from 'office-ui-fabric-react';
import { IPosition } from 'office-ui-fabric-react/lib-es2015/utilities/positioning';
import * as React from 'react';
import { ICrop } from '../ImageManipulation.types';
@ -7,11 +5,10 @@ import { nodePoition } from './Enums';
import styles from './ImageCrop.module.scss';
import { ICropData, IMousePosition } from './Interfaces';
function clamp(num, min, max) {
function clamp(num: number, min: number, max: number): number {
return Math.min(Math.max(num, min), max);
}
export interface IImageCropProps {
crop: ICrop;
@ -21,7 +18,8 @@ export interface IImageCropProps {
onDragStart?: (e: MouseEvent) => void;
onComplete?: (crop: ICrop) => void;
onChange?: (crop: ICrop) => void;
onDragEnd?: (e) => void;
// tslint:disable-next-line: no-any
onDragEnd?: (e: any) => void;
}
export interface IImageCropState {
@ -30,17 +28,14 @@ export interface IImageCropState {
reloadtimestamp: string;
}
// Feature detection
// tslint:disable-next-line: max-line-length
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners
let passiveSupported = false;
export default class ImageCrop extends React.Component<IImageCropProps, IImageCropState> {
export default class ImageCrop extends
React.Component<IImageCropProps, IImageCropState> {
private controlRef: HTMLDivElement = null;
private controlRef: HTMLDivElement = undefined;
private dragStarted: boolean = false;
private mouseDownOnCrop: boolean = false;
@ -61,31 +56,24 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
}
public componentDidMount(): void {
const { crop, sourceHeight, sourceWidth } = this.props;
const { crop } = this.props;
if (crop && this.isValid(crop) &&
(crop.sx !== 0 || crop.sy !== 0 || crop.width !== 0 && crop.height !== 0)
) {
this.setState({ cropIsActive: true });
} else {
//Requireed because first renderer has no ref
// Requireed because first renderer has no ref
this.setState({ reloadtimestamp: new Date().getTime().toString() });
}
}
public componentWillUnmount(): void {
}
public render(): React.ReactElement<IImageCropProps> {
const { crop } = this.props;
const { cropIsActive, newCropIsBeingDrawn } = this.state;
const cropSelection = this.isValid(crop) && this.controlRef ? this.createSelectionGrid() : null;
const cropSelection: JSX.Element = this.isValid(crop) && this.controlRef ? this.createSelectionGrid() : undefined;
// tslint:disable:react-a11y-event-has-role
return (
<div ref={this.setControlRef}
className={styles.ImgGridShadowOverlay}
@ -110,12 +98,14 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
</div>
</div>
);
// tslint:
}
private createSelectionGrid(): JSX.Element {
const { showRuler } = this.props;
const style = this.getCropStyle();
const style: { top: string, left: string, width: string, height: string } = this.getCropStyle();
// tslint:disable:react-a11y-event-has-role
return (
<div
style={style}
@ -124,7 +114,6 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
onTouchStart={this.onCropMouseTouchDown}
>
<div className={styles.dragBar_n} data-ord={nodePoition.N} />
<div className={styles.dragBar_e} data-ord={nodePoition.E} />
<div className={styles.dragBar_s} data-ord={nodePoition.S} />
@ -139,7 +128,6 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
<div className={[styles.dragHandle, styles.sw].join(' ')} data-ord={nodePoition.SW} />
<div className={[styles.dragHandle, styles.w].join(' ')} data-ord={nodePoition.W} />
{showRuler && (
<div>
<div className={styles.ruleOfThirdsHZ} />
@ -148,6 +136,7 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
)}
</div>
);
// tslint:enable
}
private makeNewCrop(): ICrop {
@ -155,32 +144,18 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
return crop;
}
private getCropStyle() {
const crop = this.makeNewCrop();
const unit = 'px';
private getCropStyle(): { top: string, left: string, width: string, height: string } {
const crop: ICrop = this.makeNewCrop();
const unit: string = 'px';
return {
top: `${crop.sy}${unit}`,
left: `${crop.sx}${unit}`,
width: `${crop.width}${unit}`,
height: `${crop.height}${unit}`,
height: `${crop.height}${unit}`
};
}
private getCurrentPosition(e: MouseEvent | any): IMousePosition {
let { pageX, pageY } = e;
if (e.touches) {
[{ pageX, pageY }] = e.touches;
}
let refpos = this.controlRef.getBoundingClientRect();
let startx: number = pageX - refpos.left;
let starty: number = pageY - refpos.top;
return ({
x: startx,
y: starty
});
}
// tslint:disable-next-line: no-any
private onDocMouseTouchMove(e: React.MouseEvent<HTMLDivElement> | any): void {
const { crop, onChange, onDragStart } = this.props;
if (!this.mouseDownOnCrop) {
@ -191,15 +166,12 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
if (!this.dragStarted) {
this.dragStarted = true;
if (onDragStart) {
// tslint:disable-next-line: no-any
onDragStart(e as any);
}
}
const pos = this.getCurrentPosition(e);
const clientPos = this.getClientPos(e);
const clientPos: IMousePosition = this.getClientPos(e);
/*
if (this.evData.isResize && this.props.aspect && this.evData.cropOffset) {
clientPos.y = this.straightenYPath(clientPos.x);
@ -209,7 +181,7 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
this.evData.xDiff = clientPos.x - this.evData.clientStartX;
this.evData.yDiff = clientPos.y - this.evData.clientStartY;
let nextCrop;
let nextCrop: ICrop;
if (this.evData.isResize) {
nextCrop = this.resizeCrop();
@ -226,10 +198,10 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
}
private dragCrop() {
private dragCrop(): ICrop {
const { evData } = this;
let nextCrop: ICrop = this.makeNewCrop();
const nextCrop: ICrop = this.makeNewCrop();
const width: number = this.controlRef.clientWidth;
const height: number = this.controlRef.clientHeight;
nextCrop.sx = clamp(evData.cropStartX + evData.xDiff, 0, width - nextCrop.width);
@ -238,9 +210,9 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
return nextCrop;
}
private resizeCrop() {
private resizeCrop(): ICrop {
const { evData } = this;
let nextCrop = this.makeNewCrop();
const nextCrop: ICrop = this.makeNewCrop();
const { pos } = evData;
if (evData.xInversed) {
@ -251,11 +223,10 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
evData.yDiff -= evData.cropStartHeight * 2;
}
const newSize = this.getNewSize();
const newSize: { width: number, height: number } = this.getNewSize();
let newX = evData.cropStartX;
let newY = evData.cropStartY;
let newX: number = evData.cropStartX;
let newY: number = evData.cropStartY;
if (evData.xInversed) {
newX = nextCrop.sx + (nextCrop.width - newSize.width);
@ -273,7 +244,11 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
aspect: this.props.crop.aspect
};
if (this.props.crop.aspect || (pos === nodePoition.NW || pos === nodePoition.SE || pos === nodePoition.SW || pos === nodePoition.NE)) {
if (this.props.crop.aspect
|| (pos === nodePoition.NW
|| pos === nodePoition.SE
|| pos === nodePoition.SW
|| pos === nodePoition.NE)) {
nextCrop.sx = containedCrop.sx;
nextCrop.sy = containedCrop.sy;
nextCrop.width = containedCrop.width;
@ -288,12 +263,11 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
return nextCrop;
}
private getNewSize(): { width: number, height: number } {
const { crop, sourceWidth, sourceHeight } = this.props;
const { evData } = this;
let newWidth = evData.cropStartWidth + evData.xDiff;
let newWidth: number = evData.cropStartWidth + evData.xDiff;
if (evData.xInversed) {
newWidth = Math.abs(newWidth);
@ -302,7 +276,7 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
newWidth = clamp(newWidth, 0, sourceWidth);
// New height.
let newHeight;
let newHeight: number;
if (crop.aspect) {
newHeight = newWidth / crop.aspect;
@ -323,16 +297,14 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
return {
width: newWidth,
height: newHeight,
height: newHeight
};
}
// tslint:disable-next-line: no-any
private onDocMouseTouchEnd(e: MouseEvent | any): void {
const { crop, onDragEnd, onComplete } = this.props;
let elecord = this.controlRef.getBoundingClientRect();
if (this.mouseDownOnCrop) {
this.mouseDownOnCrop = false;
this.dragStarted = false;
@ -347,11 +319,12 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
}
}
// tslint:disable-next-line: no-any
private onCropMouseTouchDown(e: MouseEvent | any): void {
const { crop } = this.props;
e.preventDefault(); // Stop drag selection.
const mousepos = this.getClientPos(e);
const mousepos: IMousePosition = this.getClientPos(e);
const { ord } = e.target.dataset;
let xInversed: boolean = false;
@ -382,14 +355,14 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
this.setState({ cropIsActive: true });
}
private setControlRef(element: HTMLDivElement): void {
this.controlRef = element;
}
// tslint:disable-next-line: no-any
private getClientPos(e: MouseEvent | any): IMousePosition {
let pageX;
let pageY;
let pageX: number;
let pageY: number;
if (e.touches) {
[{ pageX, pageY }] = e.touches;
@ -399,62 +372,30 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
return {
x: pageX,
y: pageY,
y: pageY
};
}
private isValid(crop: ICrop) {
private isValid(crop: ICrop): boolean {
return crop && !isNaN(crop.width) && !isNaN(crop.height);
}
private makeAspectCrop(crop: ICrop) {
if (isNaN(this.props.crop.aspect)) {
return crop;
}
const calcCrop: ICrop = crop;
if (crop.width) {
calcCrop.height = calcCrop.width / this.props.crop.aspect;
}
if (crop.height) {
calcCrop.width = calcCrop.height * this.props.crop.aspect;
}
if (calcCrop.sy + calcCrop.height > this.props.sourceHeight) {
calcCrop.height = this.props.sourceHeight - calcCrop.sy;
calcCrop.width = calcCrop.height * this.props.crop.aspect;
}
if (calcCrop.sx + calcCrop.width > this.props.sourceWidth) {
calcCrop.width = this.props.sourceWidth - calcCrop.sx;
calcCrop.height = calcCrop.width / this.props.crop.aspect;
}
return calcCrop;
}
private resolveCrop(pixelCrop: ICrop) {
if (this.props.crop.aspect && (!pixelCrop.width || !pixelCrop.height)) {
return this.makeAspectCrop(pixelCrop);
}
return pixelCrop;
}
// tslint:disable-next-line: no-any
private onMouseTouchDown(e: MouseEvent | any): void {
const { crop, onChange } = this.props;
e.preventDefault(); // Stop drag selection.
const mousepos = this.getClientPos(e);
const mousepos: IMousePosition = this.getClientPos(e);
let refpos = this.controlRef.getBoundingClientRect();
let startx: number = mousepos.x - refpos.left;
let starty: number = mousepos.y - refpos.top;
//is mousePos in current pos
// tslint:disable-next-line: no-any
const refpos: any = this.controlRef.getBoundingClientRect();
const startx: number = mousepos.x - refpos.left;
const starty: number = mousepos.y - refpos.top;
// is mousePos in current pos
if (crop) {
if (crop.sx - 5 <= startx && crop.sx + crop.width + 5 >= startx &&
crop.sy - 5 <= starty && crop.sy + crop.height + 5 >= starty
) {
//Position in current crop do Nothing
// Position in current crop do Nothing
return;
}
}
@ -479,7 +420,7 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
isResize: true,
xDiff: 0,
yDiff: 0,
pos: nodePoition.NW,
pos: nodePoition.NW
};
this.mouseDownOnCrop = true;
@ -488,5 +429,4 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
this.setState({ cropIsActive: true, newCropIsBeingDrawn: true });
}
}

View File

@ -1,4 +1,3 @@
import { Overlay } from 'office-ui-fabric-react';
import * as React from 'react';
import { IResize } from '../ImageManipulation.types';
@ -12,7 +11,9 @@ export interface IImageGridProps {
aspect?: number;
onChange: (size: IResize) => void;
onComplete?: (size: IResize) => void;
// tslint:disable-next-line: no-any
onDragEnd?: (e: MouseEvent | any) => void;
// tslint:disable-next-line: no-any
onDragStart?: (e: MouseEvent | any) => void;
}
@ -30,7 +31,7 @@ export interface IResizeData {
export default class ImageGrid extends React.Component<IImageGridProps, IImageGridState> {
private evData: IResizeData = null;
private evData: IResizeData = undefined;
private dragStarted: boolean = false;
constructor(props: IImageGridProps) {
super(props);
@ -43,22 +44,23 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
}
public componentDidMount(): void {
window.document.addEventListener("mousemove", this.onDocMouseTouchMove);
window.document.addEventListener("touchmove", this.onDocMouseTouchMove);
window.document.addEventListener('mousemove', this.onDocMouseTouchMove);
window.document.addEventListener('touchmove', this.onDocMouseTouchMove);
window.document.addEventListener('mouseup', this.onDocMouseTouchEnd);
window.document.addEventListener('touchend', this.onDocMouseTouchEnd);
window.document.addEventListener('touchcancel', this.onDocMouseTouchEnd);
}
public componentWillUnmount(): void {
window.document.removeEventListener("mousemove", this.onDocMouseTouchMove);
window.document.removeEventListener("touchmove", this.onDocMouseTouchMove);
window.document.removeEventListener('mousemove', this.onDocMouseTouchMove);
window.document.removeEventListener('touchmove', this.onDocMouseTouchMove);
window.document.removeEventListener('mouseup', this.onDocMouseTouchEnd);
window.document.removeEventListener('touchend', this.onDocMouseTouchEnd);
window.document.removeEventListener('touchcancel', this.onDocMouseTouchEnd);
}
public render(): React.ReactElement<IImageGridProps> {
// tslint:disable:react-a11y-event-has-role
return (
<div className={styles.ImgGridShadowOverlay}>
<div className={styles.ImgGridVisible}
@ -110,10 +112,12 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
</div>
</div>
);
// tslint:enable
}
// tslint:disable-next-line: no-any
private onStartResizing(e: MouseEvent | any): void {
const mousePos = this.getClientPos(e);
const mousePos: IMousePosition = this.getClientPos(e);
let xInversed: boolean = false;
let yInversed: boolean = false;
const { ord } = e.target.dataset;
@ -141,8 +145,9 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
};
}
// tslint:disable-next-line: no-any
private onDocMouseTouchMove(e: React.MouseEvent<HTMLDivElement> | any): void {
const { aspect ,onChange } = this.props;
const { aspect, onChange } = this.props;
if (!this.dragStarted) {
return;
}
@ -151,29 +156,35 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
}
e.preventDefault();
const mousePos = this.getClientPos(e);
const mousePos: IMousePosition = this.getClientPos(e);
let xDiff: number = 0;
let yDiff: number = 0;
if (this.evData.pos == nodePoition.E || this.evData.pos == nodePoition.SE || this.evData.pos == nodePoition.NE) {
if (this.evData.pos === nodePoition.E
|| this.evData.pos === nodePoition.SE
|| this.evData.pos === nodePoition.NE) {
xDiff = mousePos.x - this.evData.clientStartX;
} else if (this.evData.pos == nodePoition.W || this.evData.pos == nodePoition.SW || this.evData.pos == nodePoition.NW) {
} else if (this.evData.pos === nodePoition.W
|| this.evData.pos === nodePoition.SW
|| this.evData.pos === nodePoition.NW) {
xDiff = this.evData.clientStartX - mousePos.x;
}
if (this.evData.pos == nodePoition.N || this.evData.pos == nodePoition.NW || this.evData.pos == nodePoition.NE) {
if (this.evData.pos === nodePoition.N || this.evData.pos === nodePoition.NW || this.evData.pos === nodePoition.NE) {
yDiff = this.evData.clientStartY - mousePos.y;
} else if (this.evData.pos == nodePoition.S || this.evData.pos == nodePoition.SW || this.evData.pos == nodePoition.SE) {
} else if (this.evData.pos === nodePoition.S
|| this.evData.pos === nodePoition.SW
|| this.evData.pos === nodePoition.SE) {
yDiff = mousePos.y - this.evData.clientStartY;
}
let nextsize: IResize = {
const nextsize: IResize = {
width: this.evData.width + xDiff,
height: this.evData.height + yDiff
};
if(aspect) {
if(this.evData.pos !== nodePoition.N && this.evData.pos !== nodePoition.S) {
if (aspect) {
if (this.evData.pos !== nodePoition.N && this.evData.pos !== nodePoition.S) {
nextsize.height = nextsize.width / aspect;
} else {
nextsize.width = nextsize.height * aspect;
@ -184,6 +195,7 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
}
}
// tslint:disable-next-line: no-any
private onDocMouseTouchEnd(e: MouseEvent | any): void {
const { width, height, onDragEnd, onComplete } = this.props;
if (this.dragStarted) {
@ -196,14 +208,13 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
onComplete({ width: width, height: height });
this.setState({ cropIsActive: false, newCropIsBeingDrawn: false });
}
}
}
// tslint:disable-next-line: no-any
private getClientPos(e: MouseEvent | any): IMousePosition {
let pageX;
let pageY;
let pageX: number;
let pageY: number;
if (e.touches) {
[{ pageX, pageY }] = e.touches;
@ -213,8 +224,7 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
return {
x: pageX,
y: pageY,
y: pageY
};
}
}

View File

@ -1,13 +1,10 @@
import { nodePoition } from "./Enums";
import { nodePoition } from './Enums';
export interface IMousePosition {
x: number;
y: number;
}
export interface ICropData {
clientStartX: number;
clientStartY: number;
@ -22,4 +19,3 @@ export interface ICropData {
xDiff: number;
yDiff: number;
}

View File

@ -1,12 +1,13 @@
import { isEqual } from '@microsoft/sp-lodash-subset';
import { EventGroup, IButtonStyles, IconButton, ISelection, Label } from 'office-ui-fabric-react';
import { DragDropHelper, IDragDropContext, IDragDropHelperParams } from 'office-ui-fabric-react/lib-es2015/utilities/dragdrop';
import { DragDropHelper, IDragDropContext } from 'office-ui-fabric-react/lib-es2015/utilities/dragdrop';
import * as React from 'react';
import styles from './ItemOrder.module.scss';
export interface IItemOrderProps {
label: string;
disabled: boolean;
// tslint:disable-next-line: no-any
items: Array<any>;
textProperty?: string;
moveUpIconName: string;
@ -14,45 +15,47 @@ export interface IItemOrderProps {
disableDragAndDrop: boolean;
removeArrows: boolean;
maxHeight?: number;
// tslint:disable-next-line: no-any
valueChanged: (newValue: Array<any>) => void;
// tslint:disable-next-line: no-any
onRenderItem?: (item: any, index: number) => JSX.Element;
}
export interface IItemOrderState {
// tslint:disable-next-line: no-any
items: Array<any>;
}
export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrderState> {
// tslint:disable-next-line: no-any
private _draggedItem: any;
private _selection: ISelection;
private _ddHelper: DragDropHelper;
private _refs: Array<HTMLElement>;
// tslint:disable-next-line: no-any
private _ddSubs: Array<any>;
private _lastBox: HTMLElement;
constructor(props: IItemOrderProps) {
super(props);
this._selection = null;
this._selection = undefined;
this._ddHelper = new DragDropHelper({
selection: this._selection
});
this._refs = new Array<HTMLElement>();
// tslint:disable-next-line: no-any
this._ddSubs = new Array<any>();
this._draggedItem = null;
this._draggedItem = undefined;
this.state = {
items: []
};
}
public render(): JSX.Element {
const {
items
@ -60,9 +63,12 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
return (
<div className={styles.propertyFieldOrder}>
{this.props.label && <Label>{this.props.label}</Label>}
<ul style={{ maxHeight: this.props.maxHeight ? this.props.maxHeight + 'px' : '100%' }} className={!this.props.disabled ? styles.enabled : styles.disabled}>
<ul
style={{ maxHeight: this.props.maxHeight ? this.props.maxHeight + 'px' : '100%' }}
className={!this.props.disabled ? styles.enabled : styles.disabled}>
{
(items && items.length > 0) && (
// tslint:disable-next-line: no-any
items.map((value: any, index: number) => {
return (
<li
@ -76,69 +82,15 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
)
}
{
(items && items.length > 0) && <div className={styles.lastBox} ref={(ref: HTMLElement) => { this._lastBox = ref; }} />
(items && items.length > 0) && <div
className={styles.lastBox}
ref={(ref: HTMLElement) => { this._lastBox = ref; }} />
}
</ul>
</div>
);
}
private renderItem(item: any, index: number): JSX.Element {
return (
<div>
<div className={styles.itemBox}>
{this.renderDisplayValue(item, index)}
</div>
{!this.props.removeArrows &&
<div>{this.renderArrows(index)}</div>
}
</div>
);
}
private renderDisplayValue(item: any, index: number): JSX.Element {
if (typeof this.props.onRenderItem === "function") {
return this.props.onRenderItem(item, index);
} else {
return (
<span>{this.props.textProperty ? item[this.props.textProperty] : item.toString()}</span>
);
}
}
private renderArrows(index: number): JSX.Element {
let arrowButtonStyles: Partial<IButtonStyles> = {
root: {
width: '14px',
height: '100%',
display: 'inline-block !important'
},
rootDisabled: {
backgroundColor: 'transparent'
},
icon: {
fontSize: "10px"
}
};
return (
<div>
<IconButton
disabled={this.props.disabled || index === 0}
iconProps={{ iconName: this.props.moveUpIconName }}
onClick={() => { this.onMoveUpClick(index); }}
styles={arrowButtonStyles}
/>
<IconButton
disabled={this.props.disabled || index === this.props.items.length - 1}
iconProps={{ iconName: this.props.moveDownIconName }}
onClick={() => { this.onMoveDownClick(index); }}
styles={arrowButtonStyles}
/>
</div>
);
}
public componentWillMount(): void {
this.setState({
items: this.props.items || []
@ -167,6 +119,64 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
this.cleanupSubscriptions();
}
// tslint:disable-next-line: no-any
private renderItem(item: any, index: number): JSX.Element {
return (
<div>
<div className={styles.itemBox}>
{this.renderDisplayValue(item, index)}
</div>
{!this.props.removeArrows &&
<div>{this.renderArrows(index)}</div>
}
</div>
);
}
// tslint:disable-next-line: no-any
private renderDisplayValue(item: any, index: number): JSX.Element {
if (typeof this.props.onRenderItem === 'function') {
return this.props.onRenderItem(item, index);
} else {
return (
<span>{this.props.textProperty ? item[this.props.textProperty] : item.toString()}</span>
);
}
}
private renderArrows(index: number): JSX.Element {
const arrowButtonStyles: Partial<IButtonStyles> = {
root: {
width: '14px',
height: '100%',
display: 'inline-block !important'
},
rootDisabled: {
backgroundColor: 'transparent'
},
icon: {
fontSize: '10px'
}
};
return (
<div>
<IconButton
disabled={this.props.disabled || index === 0}
iconProps={{ iconName: this.props.moveUpIconName }}
onClick={() => { this.onMoveUpClick(index); }}
styles={arrowButtonStyles}
/>
<IconButton
disabled={this.props.disabled || index === this.props.items.length - 1}
iconProps={{ iconName: this.props.moveDownIconName }}
onClick={() => { this.onMoveDownClick(index); }}
styles={arrowButtonStyles}
/>
</div>
);
}
private registerRef = (ref: HTMLElement): void => {
this._refs.push(ref);
}
@ -177,7 +187,8 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
this._ddSubs.push(this._ddHelper.subscribe(value, new EventGroup(value), {
eventMap: [
{
callback: (context: IDragDropContext, event?: any) => {
// tslint:disable-next-line: no-any
callback: (context: IDragDropContext, _event?: any) => {
this._draggedItem = context.data;
},
eventName: 'dragstart'
@ -185,20 +196,22 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
],
selectionIndex: index,
context: { data: this.state.items[index], index: index },
updateDropState: (isDropping: boolean, event: DragEvent) => {
updateDropState: (isDropping: boolean, _event: DragEvent) => {
if (isDropping) {
value.classList.add(styles.dragEnter);
} else {
value.classList.remove(styles.dragEnter);
}
},
canDrop: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => {
canDrop: (_dropContext?: IDragDropContext, _dragContext?: IDragDropContext) => {
return true;
},
canDrag: (item?: any) => {
// tslint:disable-next-line: no-any
canDrag: (_item?: any) => {
return true;
},
onDrop: (item?: any, event?: DragEvent) => {
// tslint:disable-next-line: no-any
onDrop: (item?: any, _event?: DragEvent) => {
if (this._draggedItem) {
this.insertBeforeItem(item);
}
@ -207,14 +220,15 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
//Never called for some reason, so using eventMap above
this._draggedItem = item;
},*/
onDragEnd: (item?: any, event?: DragEvent) => {
this._draggedItem = null;
// tslint:disable-next-line: no-any
onDragEnd: (_item?: any, _event?: DragEvent) => {
this._draggedItem = undefined;
}
}));
});
//Create dropable area below list to allow items to be dragged to the bottom
if (this._refs.length && typeof this._lastBox !== "undefined") {
// Create droppable area below list to allow items to be dragged to the bottom
if (this._refs.length && typeof this._lastBox !== 'undefined') {
this._ddSubs.push(this._ddHelper.subscribe(this._lastBox, new EventGroup(this._lastBox), {
selectionIndex: this._refs.length,
context: { data: {}, index: this._refs.length },
@ -225,12 +239,13 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
this._refs[this._refs.length - 1].classList.remove(styles.dragLast);
}
},
canDrop: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => {
canDrop: (_dropContext?: IDragDropContext, _dragContext?: IDragDropContext) => {
return true;
},
onDrop: (item?: any, event?: DragEvent) => {
// tslint:disable-next-line: no-any
onDrop: (_item?: any, _event?: DragEvent) => {
if (this._draggedItem) {
let itemIndex: number = this.state.items.indexOf(this._draggedItem);
const itemIndex: number = this.state.items.indexOf(this._draggedItem);
this.moveItemAtIndexToTargetIndex(itemIndex, this.state.items.length - 1);
}
}
@ -241,13 +256,15 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
private cleanupSubscriptions = (): void => {
while (this._ddSubs.length) {
let sub: any = this._ddSubs.pop();
// tslint:disable-next-line: no-any
const sub: any = this._ddSubs.pop();
sub.dispose();
}
}
// tslint:disable-next-line: no-any
private insertBeforeItem = (item: any) => {
let itemIndex: number = this.state.items.indexOf(this._draggedItem);
const itemIndex: number = this.state.items.indexOf(this._draggedItem);
let targetIndex: number = this.state.items.indexOf(item);
if (itemIndex < targetIndex) {
targetIndex -= 1;
@ -255,7 +272,6 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
this.moveItemAtIndexToTargetIndex(itemIndex, targetIndex);
}
private onMoveUpClick = (itemIndex: number): void => {
if (itemIndex > 0) {
this.moveItemAtIndexToTargetIndex(itemIndex, itemIndex - 1);
@ -269,8 +285,12 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
}
private moveItemAtIndexToTargetIndex = (itemIndex: number, targetIndex: number): void => {
if (itemIndex !== targetIndex && itemIndex > -1 && targetIndex > -1 && itemIndex < this.state.items.length && targetIndex < this.state.items.length) {
let items: Array<any> = this.state.items;
if (itemIndex !== targetIndex
&& itemIndex > -1 && targetIndex > -1
&& itemIndex < this.state.items.length
&& targetIndex < this.state.items.length) {
// tslint:disable-next-line: no-any
const items: Array<any> = this.state.items;
items.splice(targetIndex, 0, ...items.splice(itemIndex, 1)[0]);
this.setState({

View File

@ -10,4 +10,3 @@ export {
IFilterSettings, IRotateSettings, IScaleSettings, IFlipSettings, ICropSettings, IResizeSettings,
FilterType
} from './ImageManipulation.types';

View File

@ -7,7 +7,6 @@ declare interface IImageManipulationStrings {
ManipulationTypeCrop: string;
ManipulationTypeResize: string;
FilterTypeGrayscale: string;
FilterTypeSepia: string;

View File

@ -4,17 +4,13 @@
"alias": "ReactImageEditorWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" },
"title": { "default": "react-image-editor" },
"description": { "default": "react-image-editor description" },

View File

@ -4,7 +4,6 @@ import { Version } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneToggle
} from '@microsoft/sp-webpart-base';
@ -12,8 +11,6 @@ import * as strings from 'ReactImageEditorWebPartStrings';
import ReactImageEditor, { IReactImageEditorBaseProps, IReactImageEditorProps } from './components/ReactImageEditor';
import { IImageManipulationSettings } from '../../components';
export interface IReactImageEditorWebPartProps extends IReactImageEditorBaseProps {
}
@ -32,11 +29,12 @@ export default class ReactImageEditorWebPart extends BaseClientSideWebPart<IReac
url: this.properties.url,
settings: this.properties.settings,
updateTitleProperty: (value: string) => {this.properties.title = value;},
updateTitleProperty: (value: string) => { this.properties.title = value; },
updateUrlProperty: (value: string) => {
if(this.properties.url !== value)
// tslint:disable-next-line: curly
if (this.properties.url !== value)
this.properties.url = value;
this.properties.settings=[];
this.properties.settings = [];
this.render();
},
updateManipulationSettingsProperty: (value: IImageManipulationSettings[]) => {
@ -58,7 +56,6 @@ export default class ReactImageEditorWebPart extends BaseClientSideWebPart<IReac
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
@ -70,10 +67,9 @@ export default class ReactImageEditorWebPart extends BaseClientSideWebPart<IReac
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneToggle('showTitle',{
PropertyPaneToggle('showTitle', {
label: strings.ShowTitleFieldLabel
})
]
}
]

View File

@ -1,32 +1,30 @@
import * as React from 'react';
import styles from './ReactImageEditor.module.scss';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import { WebPartTitle } from '@pnp/spfx-controls-react/lib/WebPartTitle';
import { DisplayMode, Environment, EnvironmentType } from '@microsoft/sp-core-library';
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
import { Placeholder } from '@pnp/spfx-controls-react/lib/Placeholder';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { FilePicker, IFilePickerResult } from '@pnp/spfx-controls-react/lib/FilePicker';
import { ImageManipulation, IImageManipulationSettings } from '../../../components/ImageManipulation';
import { ImageManipulation, IImageManipulationSettings } from '../../../components/ImageManipulation'
export interface IReactImageEditorBaseProps {
showTitle: boolean;
title: string;
url?: string;
settings?: IImageManipulationSettings[];
}
export interface IReactImageEditorProps extends IReactImageEditorBaseProps {
displayMode: DisplayMode;
context: WebPartContext;
updateTitleProperty: (value: string) => void;
updateUrlProperty: (value: string) => void;
updateManipulationSettingsProperty: (value: IImageManipulationSettings[]) => void;
}
export interface IReactImageEditorBaseProps {
showTitle: boolean;
title: string;
url?: string
settings?: IImageManipulationSettings[];
}
export interface IReactImageEditorState {
isFilePickerOpen: boolean,
isFilePickerOpen: boolean;
statekey: string;
}
@ -36,7 +34,7 @@ export default class ReactImageEditor extends React.Component<IReactImageEditorP
this.state = {
isFilePickerOpen: false,
statekey: 'init'
}
};
this._onConfigure = this._onConfigure.bind(this);
this._onUrlChanged = this._onUrlChanged.bind(this);
this._onSettingsChanged = this._onSettingsChanged.bind(this);
@ -44,7 +42,7 @@ export default class ReactImageEditor extends React.Component<IReactImageEditorP
public render(): React.ReactElement<IReactImageEditorProps> {
const { url, settings } = this.props;
const { isFilePickerOpen } = this.state;
const isConfigured = !!url && url.length > 0;
const isConfigured: boolean = !!url && url.length > 0;
return (
<div className={styles.reactImageEditor}>
@ -54,16 +52,16 @@ export default class ReactImageEditor extends React.Component<IReactImageEditorP
{(isFilePickerOpen || isConfigured) && Environment.type !== EnvironmentType.Local &&
<FilePicker
isPanelOpen={isFilePickerOpen}
accepts={[".gif", ".jpg", ".jpeg", ".png"]}
buttonIcon="FileImage"
accepts={['.gif', '.jpg', '.jpeg', '.png']}
buttonIcon='FileImage'
onSave={(filePickerResult: IFilePickerResult) => {
this.setState({ isFilePickerOpen: false }, () => this._onUrlChanged(filePickerResult.fileAbsoluteUrl))
this.setState({ isFilePickerOpen: false }, () => this._onUrlChanged(filePickerResult.fileAbsoluteUrl));
}}
onCancel={() => {
this.setState({ isFilePickerOpen: false })
this.setState({ isFilePickerOpen: false });
}}
onChanged={(filePickerResult: IFilePickerResult) => {
this.setState({ isFilePickerOpen: false }, () => this._onUrlChanged(filePickerResult.fileAbsoluteUrl))
this.setState({ isFilePickerOpen: false }, () => this._onUrlChanged(filePickerResult.fileAbsoluteUrl));
}}
context={this.props.context}
@ -92,7 +90,10 @@ export default class ReactImageEditor extends React.Component<IReactImageEditorP
private _onConfigure = () => {
if (Environment.type === EnvironmentType.Local) {
this.setState({ isFilePickerOpen: false }, () => {
this._onUrlChanged('https://media.gettyimages.com/photos/whitewater-paddlers-descend-vertical-waterfall-in-kayak-picture-id1256321293?s=2048x2048');
this._onUrlChanged(
'https://media.gettyimages.com/photos/'
+ 'whitewater-paddlers-descend-vertical-waterfall-in-kayak-picture-id1256321293?s=2048x2048'
);
});
} else {
this.setState({ isFilePickerOpen: true });

View File

@ -1,30 +1,31 @@
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}
{
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false,
"react-a11y-event-has-role": true
}
}