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

@ -22,6 +22,7 @@ extensions:
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,7 +2,7 @@
{
"name": "pnp-sp-dev-spfx-web-parts-react-image-editor",
"source": "pnp",
"title": "React Image Editor",
"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": [

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;
// 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 (var i = arraylength - 1; i > 0; i -= 4) {
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
var gray = 0.3 * data[i - 3] + 0.59 * data[i - 2] + 0.11 * data[i - 1];
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,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 {
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 => {
// 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}
@ -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=''
@ -534,8 +570,11 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
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) {
// 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 });
}
@ -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) {
@ -735,9 +797,8 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
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;
}
@ -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,7 +56,7 @@ 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)
) {
@ -74,18 +69,11 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
}
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,56 +372,24 @@ 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;
// 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 &&
@ -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,6 +145,7 @@ 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;
if (!this.dragStarted) {
@ -151,24 +156,30 @@ 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
};
@ -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 {
}
@ -34,6 +31,7 @@ export default class ReactImageEditorWebPart extends BaseClientSideWebPart<IReac
updateTitleProperty: (value: string) => { this.properties.title = value; },
updateUrlProperty: (value: string) => {
// tslint:disable-next-line: curly
if (this.properties.url !== value)
this.properties.url = value;
this.properties.settings = [];
@ -58,7 +56,6 @@ export default class ReactImageEditorWebPart extends BaseClientSideWebPart<IReac
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
@ -73,7 +70,6 @@ export default class ReactImageEditorWebPart extends BaseClientSideWebPart<IReac
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

@ -25,6 +25,7 @@
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
"whitespace": false,
"react-a11y-event-has-role": true
}
}