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 ## 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 Key features of the Editor
* Resize * Resize
* Crop * Crop
* Flip * 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) ![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) ![SPFx 1.4.0](https://img.shields.io/badge/SPFx-1.4.0-green.svg)
> SharePoint 2019 and SharePoint Online ![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 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. > Include any additional steps as needed.
## Usage ## Usage
* PNP Placeholder control if not Configured * PNP Placeholder control if not Configured
* PNP WebpartTitle control (toggle Show/Hide in property pane) * PNP WebpartTitle control (toggle Show/Hide in property pane)
* PNP FilePicker control to pick Images (is mocked on localworkbench) * 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", "name": "pnp-sp-dev-spfx-web-parts-react-image-editor",
"source": "pnp", "source": "pnp",
"title": "React Image Editor", "title": "Image Editor",
"shortDescription": "This solution contains an SPFx webpart that shows an HTML Image Editor based on canvas and Office UI Fabric", "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", "url": "https://github.com/pnp/sp-dev-fx-webparts/tree/master/samples/react-image-editor",
"longDescription": [ "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", "Key features of the Editor",
"* Resize", "* Resize",
"* Crop", "* Crop",

View File

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

View File

@ -1,6 +1,6 @@
export interface IImageFilter { export interface IImageFilter {
//describing function, // describing function,
//parameters are in left side parenthesis, // parameters are in left side parenthesis,
//right side 'string' is return type // right side 'string' is return type
process(imageData: ImageData, width: number, height: number, nvalue?: number, svalue?: string): ImageData; 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 { 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]; // 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]; private r: number[] = [
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]; 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; 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; // var data: Uint8ClampedArray = imageData.data;
this.noise = nvalue; 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 // change image colors
imageData.data[i] = this.r[imageData.data[i]]; imageData.data[i] = this.r[imageData.data[i]];
imageData.data[i + 1] = this.g[imageData.data[i + 1]]; imageData.data[i + 1] = this.g[imageData.data[i + 1]];
@ -16,14 +27,13 @@ export class SepiaFilter implements IImageFilter {
if (this.noise > 0) { if (this.noise > 0) {
this.noise = Math.round(this.noise - Math.random() * this.noise); this.noise = Math.round(this.noise - Math.random() * this.noise);
for (var j = 0; j < 3; j++) { for (let j: number = 0; j < 3; j++) {
var iPN = this.noise + imageData.data[i + j]; const iPN: number = this.noise + imageData.data[i + j];
imageData.data[i + j] = (iPN > 255) ? 255 : iPN; imageData.data[i + j] = (iPN > 255) ? 255 : iPN;
} }
} }
} }
return (imageData); return (imageData);
} }
} }

View File

@ -2,18 +2,25 @@ import { Icon } from 'office-ui-fabric-react';
import * as React from 'react'; import * as React from 'react';
import styles from './ImageManipulation.module.scss'; 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
if(!item) { export const historyItem = (item: IImageManipulationSettings, _index: number): JSX.Element => {
if (!item) {
return undefined; 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 ( return (
<span className={styles.historyItem}> <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.historyItemText}>{data.text}</span>
<span className={styles.historyItemDetails}>{detailrender}</span> <span className={styles.historyItemDetails}>{detailrender}</span>
</span> </span>

View File

@ -1,6 +1,17 @@
import { DisplayMode } from '@microsoft/sp-core-library'; import { DisplayMode } from '@microsoft/sp-core-library';
import { clone } from '@microsoft/sp-lodash-subset'; 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 * as React from 'react';
import ImageCrop from './components/ImageCrop'; import ImageCrop from './components/ImageCrop';
@ -9,12 +20,28 @@ import ItemOrder from './components/ItemOrder';
import { GrayscaleFilter } from './Filter/GrayscaleFilter'; import { GrayscaleFilter } from './Filter/GrayscaleFilter';
import { SepiaFilter } from './Filter/SepiaFilter'; import { SepiaFilter } from './Filter/SepiaFilter';
import { historyItem } from './HistoryItem'; import { historyItem } from './historyItem';
import styles from './ImageManipulation.module.scss'; 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'); const flipVerticalIcon: any = require('../../svg/flipVertical.svg');
// tslint:disable-next-line: no-any
const flipHorizontalIcon: any = require('../../svg/flipHorizontal.svg'); const flipHorizontalIcon: any = require('../../svg/flipHorizontal.svg');
import * as strings from 'ImageManipulationStrings'; import * as strings from 'ImageManipulationStrings';
@ -39,16 +66,14 @@ export interface IImageManipulationState {
} }
export class ImageManipulation extends React.Component<IImageManipulationProps, IImageManipulationState> { export class ImageManipulation extends React.Component<IImageManipulationProps, IImageManipulationState> {
private img: HTMLImageElement = null; private img: HTMLImageElement = undefined;
private wrapperRef: HTMLDivElement = null; private wrapperRef: HTMLDivElement = undefined;
private bufferRef: HTMLCanvasElement = null; private bufferRef: HTMLCanvasElement = undefined;
private bufferCtx: CanvasRenderingContext2D = null; private bufferCtx: CanvasRenderingContext2D = undefined;
private canvasRef: HTMLCanvasElement = null; private canvasRef: HTMLCanvasElement = undefined;
private canvasCtx: CanvasRenderingContext2D = null; private canvasCtx: CanvasRenderingContext2D = undefined;
private manipulateRef: HTMLCanvasElement = null; private manipulateRef: HTMLCanvasElement = undefined;
private manipulateCtx: CanvasRenderingContext2D = null; private manipulateCtx: CanvasRenderingContext2D = undefined;
constructor(props: IImageManipulationProps) { constructor(props: IImageManipulationProps) {
super(props); super(props);
@ -65,11 +90,12 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
this.setManipulateRef = this.setManipulateRef.bind(this); this.setManipulateRef = this.setManipulateRef.bind(this);
this.setScale = this.setScale.bind(this); this.setScale = this.setScale.bind(this);
this.closeFilter = this.closeFilter.bind(this); this.closeFilter = this.closeFilter.bind(this);
} }
public componentDidMount(): void { public componentDidMount(): void {
this.imageChanged(this.props.src); this.imageChanged(this.props.src);
} }
public componentDidUpdate(prevProps: IImageManipulationProps): void { public componentDidUpdate(prevProps: IImageManipulationProps): void {
if (prevProps.src !== this.props.src) { if (prevProps.src !== this.props.src) {
this.imageChanged(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> { public render(): React.ReactElement<IImageManipulationProps> {
return ( return (
<div className={styles.imageEditor} style={{ <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()} {this.props.displyMode === DisplayMode.Edit && this.getCommandBar()}
<div className={styles.imageplaceholder + ' ' + this.getMaxWidth()} <div className={styles.imageplaceholder + ' ' + this.getMaxWidth()}
@ -280,8 +129,189 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
</div> </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 { 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 }; let lastdata: ICrop = { sx: 0, sy: 0, width: 0, height: 0 };
if (lastset && lastset.type === ManipulationType.Crop) { if (lastset && lastset.type === ManipulationType.Crop) {
@ -300,7 +330,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
} }
private getResizeGrid(): JSX.Element { private getResizeGrid(): JSX.Element {
const lastset = this.getLastManipulation() as IResizeSettings; const lastset: IResizeSettings = this.getLastManipulation() as IResizeSettings;
if (lastset && lastset.type === ManipulationType.Resize) { if (lastset && lastset.type === ManipulationType.Resize) {
return (<ImageGrid return (<ImageGrid
width={lastset.width} height={lastset.height} width={lastset.width} height={lastset.height}
@ -310,7 +340,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
} }
return (<ImageGrid return (<ImageGrid
onChange={(size) => this.setResize(size.width, size.height, undefined)} onChange={(size) => this.setResize(size.width, size.height, undefined)}
//aspect={this.getAspect()} // aspect={this.getAspect()}
width={this.canvasRef.width} height={this.canvasRef.height} />); 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 { 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 { private closeFilter(): void {
this.setState({ this.setState({
@ -379,7 +411,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}, () => this.toggleEditMode(true)); }, () => this.toggleEditMode(true));
} }
private toggleEditMode(mode: boolean) { private toggleEditMode(mode: boolean): void {
if (this.props.editMode) { if (this.props.editMode) {
this.props.editMode(mode); this.props.editMode(mode);
} }
@ -408,8 +440,6 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
this.props.settingschanged(newhist); this.props.settingschanged(newhist);
} }
} }
}} }}
onRenderItem={historyItem} 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 { 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) { tmpsettings = []; }
if (tmpsettings.filter((f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type).length > 0) { if (tmpsettings.filter(
const removeindex = findIndex(tmpsettings, (f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type); (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); tmpsettings.splice(removeindex, 1);
} else { } else {
tmpsettings.push({ tmpsettings.push({
@ -453,12 +485,15 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
return (<div className={styles.buttonHolderPanel} > return (<div className={styles.buttonHolderPanel} >
<IconButton <IconButton
iconProps={{ iconName: 'SwitcherStartEnd' }} 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} title={strings.FlipHorizontal}
ariaLabel={strings.FlipHorizontal} ariaLabel={strings.FlipHorizontal}
onClick={() => { onClick={() => {
let last = this.getLastManipulation(); const last: IImageManipulationSettings = this.getLastManipulation();
if (last && last.type === ManipulationType.Flip) { if (last && last.type === ManipulationType.Flip) {
(last as IFlipSettings).flipX = !(last as IFlipSettings).flipX; (last as IFlipSettings).flipX = !(last as IFlipSettings).flipX;
if ((last as IFlipSettings).flipX === false && if ((last as IFlipSettings).flipX === false &&
@ -473,11 +508,14 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}} }}
/> />
<IconButton <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} title={strings.FlipVertical}
ariaLabel={strings.FlipVertical} ariaLabel={strings.FlipVertical}
onClick={() => { onClick={() => {
let last = this.getLastManipulation(); const last: IImageManipulationSettings = this.getLastManipulation();
if (last && last.type === ManipulationType.Flip) { if (last && last.type === ManipulationType.Flip) {
(last as IFlipSettings).flipY = !(last as IFlipSettings).flipY; (last as IFlipSettings).flipY = !(last as IFlipSettings).flipY;
if ((last as IFlipSettings).flipX === false && if ((last as IFlipSettings).flipX === false &&
@ -495,8 +533,8 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
</div>); </div>);
} }
private getRotateSettings(): JSX.Element { private getRotateSettings(): JSX.Element {
const lastvalue = this.getLastManipulation(); const lastvalue: IImageManipulationSettings = this.getLastManipulation();
let rotatevalue = 0; let rotatevalue: number = 0;
if (lastvalue && lastvalue.type === ManipulationType.Rotate) { if (lastvalue && lastvalue.type === ManipulationType.Rotate) {
rotatevalue = (lastvalue as IRotateSettings).rotate ? (lastvalue as IRotateSettings).rotate : 0; 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'; let icon: string = 'CompassNW';
if (value !== 0) { icon = 'Rotate'; } if (value !== 0) { icon = 'Rotate'; }
return (<DefaultButton return (<DefaultButton
key={'rotate' + index} key={'rotate' + index}
onClick={() => { onClick={() => {
@ -522,7 +559,6 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
<span className={styles.imgtext} >{'' + value}</span></DefaultButton>); <span className={styles.imgtext} >{'' + value}</span></DefaultButton>);
})} })}
</div> </div>
<Slider <Slider
label='' label=''
@ -533,14 +569,17 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
onChange={this.setRotate} onChange={this.setRotate}
showValue={true} showValue={true}
componentRef={(component: ISlider | null) => { componentRef={(component: ISlider | null) => {
//Initial Value has a bug 0 is min value only min value is negative // Initial Value has a bug 0 is min value only min value is negative
const correctBugComponent = component as any; // tslint:disable-next-line: no-any
if (correctBugComponent && correctBugComponent.state && correctBugComponent.value != correctBugComponent.props.value) { const correctBugComponent: any = component as any;
if (correctBugComponent
&& correctBugComponent.state
&& correctBugComponent.value !== correctBugComponent.props.value) {
correctBugComponent.setState({ value: 0, renderedValue: 0 }); correctBugComponent.setState({ value: 0, renderedValue: 0 });
} }
}} }}
//originFromZero // originFromZero
/> />
<IconButton <IconButton
key={'resetrotate'} key={'resetrotate'}
@ -554,7 +593,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
} }
private getCropSettings(): JSX.Element { private getCropSettings(): JSX.Element {
let crop: ICropSettings = this.getCropValues(); const crop: ICropSettings = this.getCropValues();
return (<div> return (<div>
<Checkbox <Checkbox
label={strings.LockAspect} 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
<TextField label={strings.SourceY} value={'' + crop.sy} onChanged={(y) => this.setCrop(undefined, parseInt(y), undefined, undefined, crop.aspect)} /> label={strings.SourceX}
<TextField label={strings.Width} value={'' + crop.width} onChanged={(w) => this.setCrop(undefined, undefined, parseInt(w), undefined, crop.aspect)} /> value={'' + crop.sx}
<TextField label={strings.Height} value={'' + crop.height} onChanged={(h) => this.setCrop(undefined, undefined, undefined, parseInt(h), crop.aspect)} /> // 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>); </div>);
} }
private getResizeSettings(): JSX.Element { private getResizeSettings(): JSX.Element {
let resize: IResizeSettings = this.getResizeValues(); const resize: IResizeSettings = this.getResizeValues();
return (<div> return (<div>
<Checkbox <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.Width}
<TextField label={strings.Height} value={'' + resize.height} onChanged={(h) => this.setResize(undefined, parseInt(h), resize.aspect)} /> 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>); </div>);
} }
@ -604,10 +665,11 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
} }
private getScaleSettings(): JSX.Element { private getScaleSettings(): JSX.Element {
const lastvalue = this.getLastManipulation(); const lastvalue: IImageManipulationSettings = this.getLastManipulation();
let scalevalue = 1; let scalevalue: number = 1;
if (lastvalue && lastvalue.type === ManipulationType.Scale) { 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> return (<div>
@ -633,7 +695,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
} }
private getResizeValues(): IResizeSettings { private getResizeValues(): IResizeSettings {
let state: IImageManipulationSettings = this.getLastManipulation(); const state: IImageManipulationSettings = this.getLastManipulation();
let values: IResizeSettings = { let values: IResizeSettings = {
type: ManipulationType.Resize, type: ManipulationType.Resize,
height: this.bufferRef.height, 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 { private setResize(width: number, height: number, aspect: number): void {
let values: IResizeSettings = this.getResizeValues(); const values: IResizeSettings = this.getResizeValues();
if (width) { if (width) {
values.width = width; values.width = width;
if (aspect) { if (aspect) {
@ -664,7 +726,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
} }
private getCropValues(): ICropSettings { private getCropValues(): ICropSettings {
let state: IImageManipulationSettings = this.getLastManipulation(); const state: IImageManipulationSettings = this.getLastManipulation();
let values: ICropSettings = { let values: ICropSettings = {
type: ManipulationType.Crop, type: ManipulationType.Crop,
sx: 0, 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 { 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 currentheight: number = this.bufferRef.height;
const currentwidth: number = this.bufferRef.width; const currentwidth: number = this.bufferRef.width;
if (!isNaN(sx) && sx >= 0) { if (!isNaN(sx) && sx >= 0) {
@ -722,22 +784,21 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
} }
} }
if (isNaN(values.aspect) && !isNaN(aspect)) { if (isNaN(values.aspect) && !isNaN(aspect)) {
//aspect added // aspect added
//limit w // limit w
if ((values.width + values.sx) > currentwidth) { if ((values.width + values.sx) > currentwidth) {
values.width = currentwidth - values.sx; values.width = currentwidth - values.sx;
} }
values.height = values.width / aspect; values.height = values.width / aspect;
//limit h adn recalulate w // limit h adn recalulate w
if ((values.height + values.sy) > currentheight) { if ((values.height + values.sy) > currentheight) {
values.height = currentheight - values.sy; values.height = currentheight - values.sy;
values.width = values.height * aspect; values.width = values.height * aspect;
} }
} }
values.aspect = aspect; values.aspect = aspect;
if (aspect && (!isNaN(sx) || !isNaN(width))) { if (aspect && (!isNaN(sx) || !isNaN(width))) {
values.height = values.width / aspect; values.height = values.width / aspect;
@ -762,8 +823,8 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}); });
} }
private calcRotate(value: number): void { private calcRotate(value: number): void {
const lastVal = this.getLastManipulation(); const lastVal: IImageManipulationSettings = this.getLastManipulation();
let cvalue = 0; let cvalue: number = 0;
if (lastVal && lastVal.type === ManipulationType.Rotate) { if (lastVal && lastVal.type === ManipulationType.Rotate) {
cvalue = (lastVal as IRotateSettings).rotate; cvalue = (lastVal as IRotateSettings).rotate;
} }
@ -810,18 +871,17 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
return undefined; return undefined;
} }
private addOrUpdateLastManipulation(changed: IImageManipulationSettings): void { private addOrUpdateLastManipulation(changed: IImageManipulationSettings): void {
let state = clone(this.props.settings); let state: IImageManipulationSettings[] = clone(this.props.settings);
if (!state) { if (!state) {
state = []; state = [];
} }
if (state.length > 0 && state[state.length - 1].type === changed.type) { if (state.length > 0 && state[state.length - 1].type === changed.type) {
state[state.length - 1] = changed; state[state.length - 1] = changed;
} else { } else {
state.push(changed); state.push(changed);
} }
if (this.state.redosettings && this.state.redosettings.length > 0) { if (this.state.redosettings && this.state.redosettings.length > 0) {
this.setState({ redosettings: [] }, () => { this.setState({ redosettings: [] }, () => {
if (this.props.settingschanged) { if (this.props.settingschanged) {
@ -837,7 +897,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
private removeLastManipulation(): void { private removeLastManipulation(): void {
if (this.props.settings && this.props.settings.length > 0) { 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); state.splice(state.length - 1, 1);
if (this.props.settingschanged) { if (this.props.settingschanged) {
this.props.settingschanged(clone(state)); this.props.settingschanged(clone(state));
@ -850,6 +910,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
iconProps={{ iconName: options.iconName }} iconProps={{ iconName: options.iconName }}
onRenderIcon={(p, defaultrenderer) => { onRenderIcon={(p, defaultrenderer) => {
if (options.svgIcon) { if (options.svgIcon) {
// tslint:disable-next-line: react-a11y-img-has-alt
return (<img className={styles.svgbutton} src={options.svgIcon} />); return (<img className={styles.svgbutton} src={options.svgIcon} />);
} }
return defaultrenderer(p); return defaultrenderer(p);
@ -875,9 +936,9 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
ariaLabel={strings.CommandBarUndo} ariaLabel={strings.CommandBarUndo}
disabled={!this.props.settings || this.props.settings.length < 1} disabled={!this.props.settings || this.props.settings.length < 1}
onClick={() => { onClick={() => {
const settings = clone(this.props.settings); const settings: IImageManipulationSettings[] = clone(this.props.settings);
const last = settings.pop(); const last: IImageManipulationSettings = settings.pop();
const redo = clone(this.state.redosettings); const redo: IImageManipulationSettings[] = clone(this.state.redosettings);
redo.push(last); redo.push(last);
this.setState({ redosettings: redo }, this.setState({ redosettings: redo },
() => { () => {
@ -894,9 +955,9 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
ariaLabel={strings.CommandBarRedo} ariaLabel={strings.CommandBarRedo}
disabled={!this.state.redosettings || this.state.redosettings.length < 1} disabled={!this.state.redosettings || this.state.redosettings.length < 1}
onClick={() => { onClick={() => {
const redosettings = clone(this.state.redosettings); const redosettings: IImageManipulationSettings[] = clone(this.state.redosettings);
const redo = redosettings.pop(); const redo: IImageManipulationSettings = redosettings.pop();
const settings = clone(this.props.settings); const settings: IImageManipulationSettings[] = clone(this.props.settings);
settings.push(redo); settings.push(redo);
this.setState({ redosettings: redosettings }, this.setState({ redosettings: redosettings },
() => { () => {
@ -930,7 +991,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
onClick={() => this.openPanel(SettingPanelType.History)} onClick={() => this.openPanel(SettingPanelType.History)}
/> />
<Panel <Panel
isOpen={this.state.settingPanel != SettingPanelType.Closed} isOpen={this.state.settingPanel !== SettingPanelType.Closed}
type={PanelType.smallFixedFar} type={PanelType.smallFixedFar}
onDismiss={this.closeFilter} onDismiss={this.closeFilter}
headerText={this.getPanelHeader(this.state.settingPanel)} headerText={this.getPanelHeader(this.state.settingPanel)}

View File

@ -1,8 +1,12 @@
import * as React from 'react'; import * as React from 'react';
// tslint:disable-next-line: no-any
const colorFilterIcon: any = require('../../svg/colorFilter.svg'); const colorFilterIcon: any = require('../../svg/colorFilter.svg');
// tslint:disable-next-line: no-any
const cropIcon: any = require('../../svg/crop.svg'); const cropIcon: any = require('../../svg/crop.svg');
// tslint:disable-next-line: no-any
const flipVerticalIcon: any = require('../../svg/flipVertical.svg'); const flipVerticalIcon: any = require('../../svg/flipVertical.svg');
// tslint:disable-next-line: no-any
const resizeIcon: any = require('../../svg/resize.svg'); const resizeIcon: any = require('../../svg/resize.svg');
import * as strings from 'ImageManipulationStrings'; import * as strings from 'ImageManipulationStrings';
@ -28,7 +32,7 @@ export enum SettingPanelType {
export enum FilterType { export enum FilterType {
Grayscale, Grayscale,
Sepia, Sepia
/* /*
Blur, Blur,
Emboss, Emboss,
@ -42,7 +46,6 @@ export enum FilterType {
ColorOverLay*/ ColorOverLay*/
} }
export interface IManipulationBase { export interface IManipulationBase {
type: ManipulationType; 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 = { export const filterTypeData: IFilterTypeData = {
0: strings.FilterTypeGrayscale, 0: strings.FilterTypeGrayscale,
@ -99,6 +107,7 @@ export interface IFilterTypeData {
export interface IManipulationTypeDataBase { export interface IManipulationTypeDataBase {
text: string; text: string;
iconName?: string; iconName?: string;
// tslint:disable-next-line: no-any
svgIcon?: any; svgIcon?: any;
settingPanelType: SettingPanelType; settingPanelType: SettingPanelType;
} }
@ -117,7 +126,7 @@ export const manipulationTypeData: IManipulationTypeData = {
svgIcon: cropIcon, svgIcon: cropIcon,
toHTML: (item: ICropSettings) => { toHTML: (item: ICropSettings) => {
return (<span></span>); return (<span></span>);
//return (<span>{`X:${item.sx} Y:${item.sy}`}</span>); // return (<span>{`X:${item.sx} Y:${item.sy}`}</span>);
}, },
settingPanelType: SettingPanelType.Crop settingPanelType: SettingPanelType.Crop
}, },
@ -153,8 +162,5 @@ export const manipulationTypeData: IManipulationTypeData = {
svgIcon: resizeIcon, svgIcon: resizeIcon,
toHTML: (item: IResizeSettings) => { return (<span></span>); }, toHTML: (item: IResizeSettings) => { return (<span></span>); },
settingPanelType: SettingPanelType.Resize 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 * as React from 'react';
import { ICrop } from '../ImageManipulation.types'; import { ICrop } from '../ImageManipulation.types';
@ -7,11 +5,10 @@ import { nodePoition } from './Enums';
import styles from './ImageCrop.module.scss'; import styles from './ImageCrop.module.scss';
import { ICropData, IMousePosition } from './Interfaces'; 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); return Math.min(Math.max(num, min), max);
} }
export interface IImageCropProps { export interface IImageCropProps {
crop: ICrop; crop: ICrop;
@ -21,7 +18,8 @@ export interface IImageCropProps {
onDragStart?: (e: MouseEvent) => void; onDragStart?: (e: MouseEvent) => void;
onComplete?: (crop: ICrop) => void; onComplete?: (crop: ICrop) => void;
onChange?: (crop: ICrop) => void; onChange?: (crop: ICrop) => void;
onDragEnd?: (e) => void; // tslint:disable-next-line: no-any
onDragEnd?: (e: any) => void;
} }
export interface IImageCropState { export interface IImageCropState {
@ -30,17 +28,14 @@ export interface IImageCropState {
reloadtimestamp: string; reloadtimestamp: string;
} }
// Feature detection // 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 // 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 dragStarted: boolean = false;
private mouseDownOnCrop: boolean = false; private mouseDownOnCrop: boolean = false;
@ -61,31 +56,24 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
} }
public componentDidMount(): void { public componentDidMount(): void {
const { crop, sourceHeight, sourceWidth } = this.props; const { crop } = this.props;
if (crop && this.isValid(crop) && if (crop && this.isValid(crop) &&
(crop.sx !== 0 || crop.sy !== 0 || crop.width !== 0 && crop.height !== 0) (crop.sx !== 0 || crop.sy !== 0 || crop.width !== 0 && crop.height !== 0)
) { ) {
this.setState({ cropIsActive: true }); this.setState({ cropIsActive: true });
} else { } else {
//Requireed because first renderer has no ref // Requireed because first renderer has no ref
this.setState({ reloadtimestamp: new Date().getTime().toString() }); this.setState({ reloadtimestamp: new Date().getTime().toString() });
} }
} }
public componentWillUnmount(): void {
}
public render(): React.ReactElement<IImageCropProps> { public render(): React.ReactElement<IImageCropProps> {
const { crop } = this.props; const { crop } = this.props;
const { cropIsActive, newCropIsBeingDrawn } = this.state; const cropSelection: JSX.Element = this.isValid(crop) && this.controlRef ? this.createSelectionGrid() : undefined;
const cropSelection = this.isValid(crop) && this.controlRef ? this.createSelectionGrid() : null;
// tslint:disable:react-a11y-event-has-role
return ( return (
<div ref={this.setControlRef} <div ref={this.setControlRef}
className={styles.ImgGridShadowOverlay} className={styles.ImgGridShadowOverlay}
@ -110,12 +98,14 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
</div> </div>
</div> </div>
); );
// tslint:
} }
private createSelectionGrid(): JSX.Element { private createSelectionGrid(): JSX.Element {
const { showRuler } = this.props; 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 ( return (
<div <div
style={style} style={style}
@ -124,7 +114,6 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
onTouchStart={this.onCropMouseTouchDown} onTouchStart={this.onCropMouseTouchDown}
> >
<div className={styles.dragBar_n} data-ord={nodePoition.N} /> <div className={styles.dragBar_n} data-ord={nodePoition.N} />
<div className={styles.dragBar_e} data-ord={nodePoition.E} /> <div className={styles.dragBar_e} data-ord={nodePoition.E} />
<div className={styles.dragBar_s} data-ord={nodePoition.S} /> <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.sw].join(' ')} data-ord={nodePoition.SW} />
<div className={[styles.dragHandle, styles.w].join(' ')} data-ord={nodePoition.W} /> <div className={[styles.dragHandle, styles.w].join(' ')} data-ord={nodePoition.W} />
{showRuler && ( {showRuler && (
<div> <div>
<div className={styles.ruleOfThirdsHZ} /> <div className={styles.ruleOfThirdsHZ} />
@ -148,6 +136,7 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
)} )}
</div> </div>
); );
// tslint:enable
} }
private makeNewCrop(): ICrop { private makeNewCrop(): ICrop {
@ -155,32 +144,18 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
return crop; return crop;
} }
private getCropStyle() { private getCropStyle(): { top: string, left: string, width: string, height: string } {
const crop = this.makeNewCrop(); const crop: ICrop = this.makeNewCrop();
const unit = 'px'; const unit: string = 'px';
return { return {
top: `${crop.sy}${unit}`, top: `${crop.sy}${unit}`,
left: `${crop.sx}${unit}`, left: `${crop.sx}${unit}`,
width: `${crop.width}${unit}`, width: `${crop.width}${unit}`,
height: `${crop.height}${unit}`, height: `${crop.height}${unit}`
}; };
} }
private getCurrentPosition(e: MouseEvent | any): IMousePosition { // tslint:disable-next-line: no-any
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
});
}
private onDocMouseTouchMove(e: React.MouseEvent<HTMLDivElement> | any): void { private onDocMouseTouchMove(e: React.MouseEvent<HTMLDivElement> | any): void {
const { crop, onChange, onDragStart } = this.props; const { crop, onChange, onDragStart } = this.props;
if (!this.mouseDownOnCrop) { if (!this.mouseDownOnCrop) {
@ -191,15 +166,12 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
if (!this.dragStarted) { if (!this.dragStarted) {
this.dragStarted = true; this.dragStarted = true;
if (onDragStart) { if (onDragStart) {
// tslint:disable-next-line: no-any
onDragStart(e as any); onDragStart(e as any);
} }
} }
const pos = this.getCurrentPosition(e);
const clientPos: IMousePosition = this.getClientPos(e);
const clientPos = this.getClientPos(e);
/* /*
if (this.evData.isResize && this.props.aspect && this.evData.cropOffset) { if (this.evData.isResize && this.props.aspect && this.evData.cropOffset) {
clientPos.y = this.straightenYPath(clientPos.x); 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.xDiff = clientPos.x - this.evData.clientStartX;
this.evData.yDiff = clientPos.y - this.evData.clientStartY; this.evData.yDiff = clientPos.y - this.evData.clientStartY;
let nextCrop; let nextCrop: ICrop;
if (this.evData.isResize) { if (this.evData.isResize) {
nextCrop = this.resizeCrop(); nextCrop = this.resizeCrop();
@ -226,10 +198,10 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
} }
private dragCrop() { private dragCrop(): ICrop {
const { evData } = this; const { evData } = this;
let nextCrop: ICrop = this.makeNewCrop(); const nextCrop: ICrop = this.makeNewCrop();
const width: number = this.controlRef.clientWidth; const width: number = this.controlRef.clientWidth;
const height: number = this.controlRef.clientHeight; const height: number = this.controlRef.clientHeight;
nextCrop.sx = clamp(evData.cropStartX + evData.xDiff, 0, width - nextCrop.width); 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; return nextCrop;
} }
private resizeCrop() { private resizeCrop(): ICrop {
const { evData } = this; const { evData } = this;
let nextCrop = this.makeNewCrop(); const nextCrop: ICrop = this.makeNewCrop();
const { pos } = evData; const { pos } = evData;
if (evData.xInversed) { if (evData.xInversed) {
@ -251,11 +223,10 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
evData.yDiff -= evData.cropStartHeight * 2; evData.yDiff -= evData.cropStartHeight * 2;
} }
const newSize = this.getNewSize(); const newSize: { width: number, height: number } = this.getNewSize();
let newX: number = evData.cropStartX;
let newX = evData.cropStartX; let newY: number = evData.cropStartY;
let newY = evData.cropStartY;
if (evData.xInversed) { if (evData.xInversed) {
newX = nextCrop.sx + (nextCrop.width - newSize.width); 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 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.sx = containedCrop.sx;
nextCrop.sy = containedCrop.sy; nextCrop.sy = containedCrop.sy;
nextCrop.width = containedCrop.width; nextCrop.width = containedCrop.width;
@ -288,12 +263,11 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
return nextCrop; return nextCrop;
} }
private getNewSize(): { width: number, height: number } { private getNewSize(): { width: number, height: number } {
const { crop, sourceWidth, sourceHeight } = this.props; const { crop, sourceWidth, sourceHeight } = this.props;
const { evData } = this; const { evData } = this;
let newWidth = evData.cropStartWidth + evData.xDiff; let newWidth: number = evData.cropStartWidth + evData.xDiff;
if (evData.xInversed) { if (evData.xInversed) {
newWidth = Math.abs(newWidth); newWidth = Math.abs(newWidth);
@ -302,7 +276,7 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
newWidth = clamp(newWidth, 0, sourceWidth); newWidth = clamp(newWidth, 0, sourceWidth);
// New height. // New height.
let newHeight; let newHeight: number;
if (crop.aspect) { if (crop.aspect) {
newHeight = newWidth / crop.aspect; newHeight = newWidth / crop.aspect;
@ -323,16 +297,14 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
return { return {
width: newWidth, width: newWidth,
height: newHeight, height: newHeight
}; };
} }
// tslint:disable-next-line: no-any
private onDocMouseTouchEnd(e: MouseEvent | any): void { private onDocMouseTouchEnd(e: MouseEvent | any): void {
const { crop, onDragEnd, onComplete } = this.props; const { crop, onDragEnd, onComplete } = this.props;
let elecord = this.controlRef.getBoundingClientRect();
if (this.mouseDownOnCrop) { if (this.mouseDownOnCrop) {
this.mouseDownOnCrop = false; this.mouseDownOnCrop = false;
this.dragStarted = 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 { private onCropMouseTouchDown(e: MouseEvent | any): void {
const { crop } = this.props; const { crop } = this.props;
e.preventDefault(); // Stop drag selection. e.preventDefault(); // Stop drag selection.
const mousepos = this.getClientPos(e); const mousepos: IMousePosition = this.getClientPos(e);
const { ord } = e.target.dataset; const { ord } = e.target.dataset;
let xInversed: boolean = false; let xInversed: boolean = false;
@ -382,14 +355,14 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
this.setState({ cropIsActive: true }); this.setState({ cropIsActive: true });
} }
private setControlRef(element: HTMLDivElement): void { private setControlRef(element: HTMLDivElement): void {
this.controlRef = element; this.controlRef = element;
} }
// tslint:disable-next-line: no-any
private getClientPos(e: MouseEvent | any): IMousePosition { private getClientPos(e: MouseEvent | any): IMousePosition {
let pageX; let pageX: number;
let pageY; let pageY: number;
if (e.touches) { if (e.touches) {
[{ pageX, pageY }] = e.touches; [{ pageX, pageY }] = e.touches;
@ -399,62 +372,30 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
return { return {
x: pageX, x: pageX,
y: pageY, y: pageY
}; };
} }
private isValid(crop: ICrop) { private isValid(crop: ICrop): boolean {
return crop && !isNaN(crop.width) && !isNaN(crop.height); return crop && !isNaN(crop.width) && !isNaN(crop.height);
} }
private makeAspectCrop(crop: ICrop) { // tslint:disable-next-line: no-any
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;
}
private onMouseTouchDown(e: MouseEvent | any): void { private onMouseTouchDown(e: MouseEvent | any): void {
const { crop, onChange } = this.props; const { crop, onChange } = this.props;
e.preventDefault(); // Stop drag selection. e.preventDefault(); // Stop drag selection.
const mousepos = this.getClientPos(e); const mousepos: IMousePosition = this.getClientPos(e);
let refpos = this.controlRef.getBoundingClientRect(); // tslint:disable-next-line: no-any
let startx: number = mousepos.x - refpos.left; const refpos: any = this.controlRef.getBoundingClientRect();
let starty: number = mousepos.y - refpos.top; const startx: number = mousepos.x - refpos.left;
//is mousePos in current pos const starty: number = mousepos.y - refpos.top;
// is mousePos in current pos
if (crop) { if (crop) {
if (crop.sx - 5 <= startx && crop.sx + crop.width + 5 >= startx && if (crop.sx - 5 <= startx && crop.sx + crop.width + 5 >= startx &&
crop.sy - 5 <= starty && crop.sy + crop.height + 5 >= starty crop.sy - 5 <= starty && crop.sy + crop.height + 5 >= starty
) { ) {
//Position in current crop do Nothing // Position in current crop do Nothing
return; return;
} }
} }
@ -479,7 +420,7 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
isResize: true, isResize: true,
xDiff: 0, xDiff: 0,
yDiff: 0, yDiff: 0,
pos: nodePoition.NW, pos: nodePoition.NW
}; };
this.mouseDownOnCrop = true; this.mouseDownOnCrop = true;
@ -488,5 +429,4 @@ export default class ImageCrop extends React.Component<IImageCropProps, IImageCr
this.setState({ cropIsActive: true, newCropIsBeingDrawn: true }); 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 * as React from 'react';
import { IResize } from '../ImageManipulation.types'; import { IResize } from '../ImageManipulation.types';
@ -12,7 +11,9 @@ export interface IImageGridProps {
aspect?: number; aspect?: number;
onChange: (size: IResize) => void; onChange: (size: IResize) => void;
onComplete?: (size: IResize) => void; onComplete?: (size: IResize) => void;
// tslint:disable-next-line: no-any
onDragEnd?: (e: MouseEvent | any) => void; onDragEnd?: (e: MouseEvent | any) => void;
// tslint:disable-next-line: no-any
onDragStart?: (e: MouseEvent | any) => void; onDragStart?: (e: MouseEvent | any) => void;
} }
@ -30,7 +31,7 @@ export interface IResizeData {
export default class ImageGrid extends React.Component<IImageGridProps, IImageGridState> { export default class ImageGrid extends React.Component<IImageGridProps, IImageGridState> {
private evData: IResizeData = null; private evData: IResizeData = undefined;
private dragStarted: boolean = false; private dragStarted: boolean = false;
constructor(props: IImageGridProps) { constructor(props: IImageGridProps) {
super(props); super(props);
@ -43,22 +44,23 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
} }
public componentDidMount(): void { public componentDidMount(): void {
window.document.addEventListener("mousemove", this.onDocMouseTouchMove); window.document.addEventListener('mousemove', this.onDocMouseTouchMove);
window.document.addEventListener("touchmove", this.onDocMouseTouchMove); window.document.addEventListener('touchmove', this.onDocMouseTouchMove);
window.document.addEventListener('mouseup', this.onDocMouseTouchEnd); window.document.addEventListener('mouseup', this.onDocMouseTouchEnd);
window.document.addEventListener('touchend', this.onDocMouseTouchEnd); window.document.addEventListener('touchend', this.onDocMouseTouchEnd);
window.document.addEventListener('touchcancel', this.onDocMouseTouchEnd); window.document.addEventListener('touchcancel', this.onDocMouseTouchEnd);
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {
window.document.removeEventListener("mousemove", this.onDocMouseTouchMove); window.document.removeEventListener('mousemove', this.onDocMouseTouchMove);
window.document.removeEventListener("touchmove", this.onDocMouseTouchMove); window.document.removeEventListener('touchmove', this.onDocMouseTouchMove);
window.document.removeEventListener('mouseup', this.onDocMouseTouchEnd); window.document.removeEventListener('mouseup', this.onDocMouseTouchEnd);
window.document.removeEventListener('touchend', this.onDocMouseTouchEnd); window.document.removeEventListener('touchend', this.onDocMouseTouchEnd);
window.document.removeEventListener('touchcancel', this.onDocMouseTouchEnd); window.document.removeEventListener('touchcancel', this.onDocMouseTouchEnd);
} }
public render(): React.ReactElement<IImageGridProps> { public render(): React.ReactElement<IImageGridProps> {
// tslint:disable:react-a11y-event-has-role
return ( return (
<div className={styles.ImgGridShadowOverlay}> <div className={styles.ImgGridShadowOverlay}>
<div className={styles.ImgGridVisible} <div className={styles.ImgGridVisible}
@ -110,10 +112,12 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
</div> </div>
</div> </div>
); );
// tslint:enable
} }
// tslint:disable-next-line: no-any
private onStartResizing(e: MouseEvent | any): void { private onStartResizing(e: MouseEvent | any): void {
const mousePos = this.getClientPos(e); const mousePos: IMousePosition = this.getClientPos(e);
let xInversed: boolean = false; let xInversed: boolean = false;
let yInversed: boolean = false; let yInversed: boolean = false;
const { ord } = e.target.dataset; 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 { private onDocMouseTouchMove(e: React.MouseEvent<HTMLDivElement> | any): void {
const { aspect ,onChange } = this.props; const { aspect, onChange } = this.props;
if (!this.dragStarted) { if (!this.dragStarted) {
return; return;
} }
@ -151,29 +156,35 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
} }
e.preventDefault(); e.preventDefault();
const mousePos = this.getClientPos(e); const mousePos: IMousePosition = this.getClientPos(e);
let xDiff: number = 0; let xDiff: number = 0;
let yDiff: 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; 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; 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; 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; yDiff = mousePos.y - this.evData.clientStartY;
} }
let nextsize: IResize = { const nextsize: IResize = {
width: this.evData.width + xDiff, width: this.evData.width + xDiff,
height: this.evData.height + yDiff height: this.evData.height + yDiff
}; };
if(aspect) { if (aspect) {
if(this.evData.pos !== nodePoition.N && this.evData.pos !== nodePoition.S) { if (this.evData.pos !== nodePoition.N && this.evData.pos !== nodePoition.S) {
nextsize.height = nextsize.width / aspect; nextsize.height = nextsize.width / aspect;
} else { } else {
nextsize.width = nextsize.height * aspect; 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 { private onDocMouseTouchEnd(e: MouseEvent | any): void {
const { width, height, onDragEnd, onComplete } = this.props; const { width, height, onDragEnd, onComplete } = this.props;
if (this.dragStarted) { if (this.dragStarted) {
@ -196,14 +208,13 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
onComplete({ width: width, height: height }); onComplete({ width: width, height: height });
this.setState({ cropIsActive: false, newCropIsBeingDrawn: false }); this.setState({ cropIsActive: false, newCropIsBeingDrawn: false });
} }
} }
} }
// tslint:disable-next-line: no-any
private getClientPos(e: MouseEvent | any): IMousePosition { private getClientPos(e: MouseEvent | any): IMousePosition {
let pageX; let pageX: number;
let pageY; let pageY: number;
if (e.touches) { if (e.touches) {
[{ pageX, pageY }] = e.touches; [{ pageX, pageY }] = e.touches;
@ -213,8 +224,7 @@ export default class ImageGrid extends React.Component<IImageGridProps, IImageGr
return { return {
x: pageX, x: pageX,
y: pageY, y: pageY
}; };
} }
} }

View File

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

View File

@ -1,12 +1,13 @@
import { isEqual } from '@microsoft/sp-lodash-subset'; import { isEqual } from '@microsoft/sp-lodash-subset';
import { EventGroup, IButtonStyles, IconButton, ISelection, Label } from 'office-ui-fabric-react'; 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 * as React from 'react';
import styles from './ItemOrder.module.scss'; import styles from './ItemOrder.module.scss';
export interface IItemOrderProps { export interface IItemOrderProps {
label: string; label: string;
disabled: boolean; disabled: boolean;
// tslint:disable-next-line: no-any
items: Array<any>; items: Array<any>;
textProperty?: string; textProperty?: string;
moveUpIconName: string; moveUpIconName: string;
@ -14,45 +15,47 @@ export interface IItemOrderProps {
disableDragAndDrop: boolean; disableDragAndDrop: boolean;
removeArrows: boolean; removeArrows: boolean;
maxHeight?: number; maxHeight?: number;
// tslint:disable-next-line: no-any
valueChanged: (newValue: Array<any>) => void; valueChanged: (newValue: Array<any>) => void;
// tslint:disable-next-line: no-any
onRenderItem?: (item: any, index: number) => JSX.Element; onRenderItem?: (item: any, index: number) => JSX.Element;
} }
export interface IItemOrderState { export interface IItemOrderState {
// tslint:disable-next-line: no-any
items: Array<any>; items: Array<any>;
} }
export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrderState> { export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrderState> {
// tslint:disable-next-line: no-any
private _draggedItem: any; private _draggedItem: any;
private _selection: ISelection; private _selection: ISelection;
private _ddHelper: DragDropHelper; private _ddHelper: DragDropHelper;
private _refs: Array<HTMLElement>; private _refs: Array<HTMLElement>;
// tslint:disable-next-line: no-any
private _ddSubs: Array<any>; private _ddSubs: Array<any>;
private _lastBox: HTMLElement; private _lastBox: HTMLElement;
constructor(props: IItemOrderProps) { constructor(props: IItemOrderProps) {
super(props); super(props);
this._selection = null; this._selection = undefined;
this._ddHelper = new DragDropHelper({ this._ddHelper = new DragDropHelper({
selection: this._selection selection: this._selection
}); });
this._refs = new Array<HTMLElement>(); this._refs = new Array<HTMLElement>();
// tslint:disable-next-line: no-any
this._ddSubs = new Array<any>(); this._ddSubs = new Array<any>();
this._draggedItem = null; this._draggedItem = undefined;
this.state = { this.state = {
items: [] items: []
}; };
} }
public render(): JSX.Element { public render(): JSX.Element {
const { const {
items items
@ -60,9 +63,12 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
return ( return (
<div className={styles.propertyFieldOrder}> <div className={styles.propertyFieldOrder}>
{this.props.label && <Label>{this.props.label}</Label>} {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) && ( (items && items.length > 0) && (
// tslint:disable-next-line: no-any
items.map((value: any, index: number) => { items.map((value: any, index: number) => {
return ( return (
<li <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> </ul>
</div> </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 { public componentWillMount(): void {
this.setState({ this.setState({
items: this.props.items || [] items: this.props.items || []
@ -167,6 +119,64 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
this.cleanupSubscriptions(); 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 => { private registerRef = (ref: HTMLElement): void => {
this._refs.push(ref); 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), { this._ddSubs.push(this._ddHelper.subscribe(value, new EventGroup(value), {
eventMap: [ eventMap: [
{ {
callback: (context: IDragDropContext, event?: any) => { // tslint:disable-next-line: no-any
callback: (context: IDragDropContext, _event?: any) => {
this._draggedItem = context.data; this._draggedItem = context.data;
}, },
eventName: 'dragstart' eventName: 'dragstart'
@ -185,20 +196,22 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
], ],
selectionIndex: index, selectionIndex: index,
context: { data: this.state.items[index], index: index }, context: { data: this.state.items[index], index: index },
updateDropState: (isDropping: boolean, event: DragEvent) => { updateDropState: (isDropping: boolean, _event: DragEvent) => {
if (isDropping) { if (isDropping) {
value.classList.add(styles.dragEnter); value.classList.add(styles.dragEnter);
} else { } else {
value.classList.remove(styles.dragEnter); value.classList.remove(styles.dragEnter);
} }
}, },
canDrop: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => { canDrop: (_dropContext?: IDragDropContext, _dragContext?: IDragDropContext) => {
return true; return true;
}, },
canDrag: (item?: any) => { // tslint:disable-next-line: no-any
canDrag: (_item?: any) => {
return true; return true;
}, },
onDrop: (item?: any, event?: DragEvent) => { // tslint:disable-next-line: no-any
onDrop: (item?: any, _event?: DragEvent) => {
if (this._draggedItem) { if (this._draggedItem) {
this.insertBeforeItem(item); 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 //Never called for some reason, so using eventMap above
this._draggedItem = item; this._draggedItem = item;
},*/ },*/
onDragEnd: (item?: any, event?: DragEvent) => { // tslint:disable-next-line: no-any
this._draggedItem = null; onDragEnd: (_item?: any, _event?: DragEvent) => {
this._draggedItem = undefined;
} }
})); }));
}); });
//Create dropable area below list to allow items to be dragged to the bottom // Create droppable area below list to allow items to be dragged to the bottom
if (this._refs.length && typeof this._lastBox !== "undefined") { if (this._refs.length && typeof this._lastBox !== 'undefined') {
this._ddSubs.push(this._ddHelper.subscribe(this._lastBox, new EventGroup(this._lastBox), { this._ddSubs.push(this._ddHelper.subscribe(this._lastBox, new EventGroup(this._lastBox), {
selectionIndex: this._refs.length, selectionIndex: this._refs.length,
context: { data: {}, index: 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); this._refs[this._refs.length - 1].classList.remove(styles.dragLast);
} }
}, },
canDrop: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => { canDrop: (_dropContext?: IDragDropContext, _dragContext?: IDragDropContext) => {
return true; return true;
}, },
onDrop: (item?: any, event?: DragEvent) => { // tslint:disable-next-line: no-any
onDrop: (_item?: any, _event?: DragEvent) => {
if (this._draggedItem) { 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); this.moveItemAtIndexToTargetIndex(itemIndex, this.state.items.length - 1);
} }
} }
@ -241,13 +256,15 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
private cleanupSubscriptions = (): void => { private cleanupSubscriptions = (): void => {
while (this._ddSubs.length) { while (this._ddSubs.length) {
let sub: any = this._ddSubs.pop(); // tslint:disable-next-line: no-any
const sub: any = this._ddSubs.pop();
sub.dispose(); sub.dispose();
} }
} }
// tslint:disable-next-line: no-any
private insertBeforeItem = (item: 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); let targetIndex: number = this.state.items.indexOf(item);
if (itemIndex < targetIndex) { if (itemIndex < targetIndex) {
targetIndex -= 1; targetIndex -= 1;
@ -255,7 +272,6 @@ export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrd
this.moveItemAtIndexToTargetIndex(itemIndex, targetIndex); this.moveItemAtIndexToTargetIndex(itemIndex, targetIndex);
} }
private onMoveUpClick = (itemIndex: number): void => { private onMoveUpClick = (itemIndex: number): void => {
if (itemIndex > 0) { if (itemIndex > 0) {
this.moveItemAtIndexToTargetIndex(itemIndex, itemIndex - 1); 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 => { private moveItemAtIndexToTargetIndex = (itemIndex: number, targetIndex: number): void => {
if (itemIndex !== targetIndex && itemIndex > -1 && targetIndex > -1 && itemIndex < this.state.items.length && targetIndex < this.state.items.length) { if (itemIndex !== targetIndex
let items: Array<any> = this.state.items; && 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]); items.splice(targetIndex, 0, ...items.splice(itemIndex, 1)[0]);
this.setState({ this.setState({

View File

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

View File

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

View File

@ -4,17 +4,13 @@
"alias": "ReactImageEditorWebPart", "alias": "ReactImageEditorWebPart",
"componentType": "WebPart", "componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*", "version": "*",
"manifestVersion": 2, "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, "requiresCustomScript": false,
"preconfiguredEntries": [{ "preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other "groupId": "5c03119e-3074-46fd-976b-c60198311f70",
"group": { "default": "Other" }, "group": { "default": "Other" },
"title": { "default": "react-image-editor" }, "title": { "default": "react-image-editor" },
"description": { "default": "react-image-editor description" }, "description": { "default": "react-image-editor description" },

View File

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

View File

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

View File

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