Merge branch 'petkir-react-image-editor' of github.com:petkir/sp-dev-fx-webparts into petkir-react-image-editor

This commit is contained in:
Peter Paul Kirschner 2021-03-23 23:29:01 +01:00
commit 9a9f3de4ef
18 changed files with 502 additions and 436 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,17 @@
import { DisplayMode } from '@microsoft/sp-core-library';
import { clone } from '@microsoft/sp-lodash-subset';
import { Checkbox, DefaultButton, findIndex, Icon, IconButton, IsFocusVisibleClassName, ISlider, Panel, PanelType, Slider, TextField } from 'office-ui-fabric-react';
import {
Checkbox,
DefaultButton,
findIndex,
Icon,
IconButton,
ISlider,
Panel,
PanelType,
Slider,
TextField
} from 'office-ui-fabric-react';
import * as React from 'react';
import ImageCrop from './components/ImageCrop';
@ -9,12 +20,28 @@ import ItemOrder from './components/ItemOrder';
import { GrayscaleFilter } from './Filter/GrayscaleFilter';
import { SepiaFilter } from './Filter/SepiaFilter';
import { historyItem } from './HistoryItem';
import { historyItem } from './historyItem';
import styles from './ImageManipulation.module.scss';
import { FilterType, filterTypeData, ICrop, ICropSettings, IFilterSettings, IFlipSettings, IImageManipulationSettings, IManipulationTypeDataDetails, IResizeSettings, IRotateSettings, IScaleSettings, ManipulationType, manipulationTypeData, SettingPanelType } from './ImageManipulation.types';
import {
FilterType,
filterTypeData,
ICrop,
ICropSettings,
IFilterSettings,
IFlipSettings,
IImageManipulationSettings,
IManipulationTypeDataDetails,
IResizeSettings,
IRotateSettings,
IScaleSettings,
ManipulationType,
manipulationTypeData,
SettingPanelType
} from './ImageManipulation.types';
// tslint:disable-next-line: no-any
const flipVerticalIcon: any = require('../../svg/flipVertical.svg');
// tslint:disable-next-line: no-any
const flipHorizontalIcon: any = require('../../svg/flipHorizontal.svg');
import * as strings from 'ImageManipulationStrings';
@ -39,16 +66,14 @@ export interface IImageManipulationState {
}
export class ImageManipulation extends React.Component<IImageManipulationProps, IImageManipulationState> {
private img: HTMLImageElement = null;
private wrapperRef: HTMLDivElement = null;
private bufferRef: HTMLCanvasElement = null;
private bufferCtx: CanvasRenderingContext2D = null;
private canvasRef: HTMLCanvasElement = null;
private canvasCtx: CanvasRenderingContext2D = null;
private manipulateRef: HTMLCanvasElement = null;
private manipulateCtx: CanvasRenderingContext2D = null;
private img: HTMLImageElement = undefined;
private wrapperRef: HTMLDivElement = undefined;
private bufferRef: HTMLCanvasElement = undefined;
private bufferCtx: CanvasRenderingContext2D = undefined;
private canvasRef: HTMLCanvasElement = undefined;
private canvasCtx: CanvasRenderingContext2D = undefined;
private manipulateRef: HTMLCanvasElement = undefined;
private manipulateCtx: CanvasRenderingContext2D = undefined;
constructor(props: IImageManipulationProps) {
super(props);
@ -65,7 +90,6 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
this.setManipulateRef = this.setManipulateRef.bind(this);
this.setScale = this.setScale.bind(this);
this.closeFilter = this.closeFilter.bind(this);
}
public componentDidMount(): void {
@ -80,10 +104,36 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
}
}
private imageChanged(url: string) {
public render(): React.ReactElement<IImageManipulationProps> {
return (
<div className={styles.imageEditor} style={{
marginTop: this.props.displyMode === DisplayMode.Edit ? '40px' : '0px'
}} >
{this.props.displyMode === DisplayMode.Edit && this.getCommandBar()}
<div className={styles.imageplaceholder + ' ' + this.getMaxWidth()}
ref={(element: HTMLDivElement) => { this.wrapperRef = element; }}
style={this.canvasRef && { width: '' + this.canvasRef.width + 'px' }}
>
<canvas className={this.getMaxWidth()}
style={{ display: 'none' }}
ref={this.setBufferRef}></canvas>
<canvas className={this.getMaxWidth()}
style={{ display: 'none' }}
ref={this.setManipulateRef}></canvas>
<canvas className={this.getMaxWidth()} ref={this.setCanvasRef} ></canvas>
{this.state.settingPanel === SettingPanelType.Crop && (this.getCropGrid())}
{this.state.settingPanel === SettingPanelType.Resize && (this.getResizeGrid())}
</div>
</div>
);
}
private imageChanged(url: string): void {
this.img = new Image();
this.img.src = url;
this.img.crossOrigin = "Anonymous";
this.img.crossOrigin = 'Anonymous';
this.img.onload = () => {
this.applySettings();
@ -103,14 +153,13 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
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;
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);
@ -121,7 +170,7 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
switch (element.type) {
case ManipulationType.Flip:
const filp = element as IFlipSettings;
const filp: IFlipSettings = element as IFlipSettings;
if (filp.flipY) {
this.manipulateCtx.translate(0, currentheight);
this.manipulateCtx.scale(1, -1);
@ -133,7 +182,7 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
break;
case ManipulationType.Rotate:
const rotate = element as IRotateSettings;
const rotate: IRotateSettings = element as IRotateSettings;
if (!rotate.rotate || rotate.rotate === 0 || isNaN(rotate.rotate)) {
nothingToDo = true;
break;
@ -146,11 +195,11 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
let offsetwidth = 0;
let offsetheight = 0;
var a = oldwidth * Math.abs(Math.cos(angelcalc));
var b = oldheight * Math.abs(Math.sin(angelcalc));
const a: number = oldwidth * Math.abs(Math.cos(angelcalc));
const b: number = oldheight * Math.abs(Math.sin(angelcalc));
var p = oldwidth * Math.abs(Math.sin(angelcalc));
var q = oldheight * Math.abs(Math.cos(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;
@ -165,11 +214,10 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
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;
const scale: IScaleSettings = element as IScaleSettings;
if (scale.scale) {
this.manipulateCtx.translate(currentwidth / 2, currentheight / 2);
this.manipulateCtx.scale(scale.scale, scale.scale);
@ -179,23 +227,32 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
}
break;
case ManipulationType.Crop:
const last = this.props.settings.length === index + 1;
const last: boolean = this.props.settings.length === index + 1;
if (last && this.state.settingPanel === SettingPanelType.Crop) {
//Do nothingis last and current edit
// Do nothing is last and current edit
nothingToDo = true;
} else {
const crop = element as ICropSettings;
const sourceX = crop.sx;
const sourceY = crop.sy;
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);
this.manipulateCtx.drawImage(
this.bufferRef,
sourceX,
sourceY,
newwidth,
newheight,
0,
0,
newwidth,
newheight);
}
break;
case ManipulationType.Resize:
const resize = element as IResizeSettings;
const resize: IResizeSettings = element as IResizeSettings;
newwidth = resize.width;
newheight = resize.height;
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
@ -227,14 +284,10 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
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)
@ -243,7 +296,9 @@ export class ImageManipulation extends React.Component<IImageManipulationProps,
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.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;
@ -251,11 +306,11 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
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';
// 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);
// this.canvasctx.translate(this.canvasRef.width / 2 * -1, this.canvasRef.height / 2 * -1);
}
public render(): React.ReactElement<IImageManipulationProps> {
@ -284,7 +339,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
);
}
private getCropGrid(): JSX.Element {
const lastset = this.getLastManipulation() as ICropSettings;
const lastset: ICropSettings = this.getLastManipulation() as ICropSettings;
let lastdata: ICrop = { sx: 0, sy: 0, width: 0, height: 0 };
if (lastset && lastset.type === ManipulationType.Crop) {
@ -303,7 +358,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getResizeGrid(): JSX.Element {
const lastset = this.getLastManipulation() as IResizeSettings;
const lastset: IResizeSettings = this.getLastManipulation() as IResizeSettings;
if (lastset && lastset.type === ManipulationType.Resize) {
return (<ImageGrid
width={lastset.width} height={lastset.height}
@ -313,7 +368,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
return (<ImageGrid
onChange={(size) => this.setResize(size.width, size.height, undefined)}
//aspect={this.getAspect()}
// aspect={this.getAspect()}
width={this.canvasRef.width} height={this.canvasRef.height} />);
}
@ -326,7 +381,9 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private isFilterActive(type: FilterType): boolean {
return (this.props.settings && this.props.settings.filter((f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type).length > 0);
return (this.props.settings && this.props.settings.filter(
(f) => f.type === ManipulationType.Filter
&& (f as IFilterSettings).filterType === type).length > 0);
}
private closeFilter(): void {
this.setState({
@ -382,7 +439,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}, () => this.toggleEditMode(true));
}
private toggleEditMode(mode: boolean) {
private toggleEditMode(mode: boolean): void {
if (this.props.editMode) {
this.props.editMode(mode);
}
@ -411,8 +468,6 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
this.props.settingsChanged(newhist);
}
}
}}
onRenderItem={historyItem}
/>);
@ -434,10 +489,12 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private toggleFilter(type: FilterType, nvalue: number = undefined, svalue: string = undefined): void {
let tmpsettings = clone(this.props.settings);
let tmpsettings: IImageManipulationSettings[] = clone(this.props.settings);
if (!tmpsettings) { tmpsettings = []; }
if (tmpsettings.filter((f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type).length > 0) {
const removeindex = findIndex(tmpsettings, (f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type);
if (tmpsettings.filter(
(f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type).length > 0) {
const removeindex: number = findIndex(tmpsettings,
(f) => f.type === ManipulationType.Filter && (f as IFilterSettings).filterType === type);
tmpsettings.splice(removeindex, 1);
} else {
tmpsettings.push({
@ -456,12 +513,15 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
return (<div className={styles.buttonHolderPanel} >
<IconButton
iconProps={{ iconName: 'SwitcherStartEnd' }}
onRenderIcon={() => { return (<img className={styles.svgbuttonPanel} src={flipVerticalIcon} />); }}
onRenderIcon={() => {
// tslint:disable-next-line: react-a11y-img-has-alt
return (<img className={styles.svgbuttonPanel} src={flipVerticalIcon} />);
}}
title={strings.FlipHorizontal}
ariaLabel={strings.FlipHorizontal}
onClick={() => {
let last = this.getLastManipulation();
const last: IImageManipulationSettings = this.getLastManipulation();
if (last && last.type === ManipulationType.Flip) {
(last as IFlipSettings).flipX = !(last as IFlipSettings).flipX;
if ((last as IFlipSettings).flipX === false &&
@ -476,11 +536,14 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}}
/>
<IconButton
onRenderIcon={() => { return (<img className={styles.svgbuttonPanel} src={flipHorizontalIcon} />); }}
onRenderIcon={() => {
// tslint:disable-next-line: react-a11y-img-has-alt
return (<img className={styles.svgbuttonPanel} src={flipHorizontalIcon} />);
}}
title={strings.FlipVertical}
ariaLabel={strings.FlipVertical}
onClick={() => {
let last = this.getLastManipulation();
const last: IImageManipulationSettings = this.getLastManipulation();
if (last && last.type === ManipulationType.Flip) {
(last as IFlipSettings).flipY = !(last as IFlipSettings).flipY;
if ((last as IFlipSettings).flipX === false &&
@ -498,8 +561,8 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
</div>);
}
private getRotateSettings(): JSX.Element {
const lastvalue = this.getLastManipulation();
let rotatevalue = 0;
const lastvalue: IImageManipulationSettings = this.getLastManipulation();
let rotatevalue: number = 0;
if (lastvalue && lastvalue.type === ManipulationType.Rotate) {
rotatevalue = (lastvalue as IRotateSettings).rotate ? (lastvalue as IRotateSettings).rotate : 0;
}
@ -509,7 +572,6 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
let icon: string = 'CompassNW';
if (value !== 0) { icon = 'Rotate'; }
return (<DefaultButton
key={'rotate' + index}
onClick={() => {
@ -525,7 +587,6 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
<span className={styles.imgtext} >{'' + value}</span></DefaultButton>);
})}
</div>
<Slider
label=''
@ -536,14 +597,17 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
onChange={this.setRotate}
showValue={true}
componentRef={(component: ISlider | null) => {
//Initial Value has a bug 0 is min value only min value is negative
const correctBugComponent = component as any;
if (correctBugComponent && correctBugComponent.state && correctBugComponent.value != correctBugComponent.props.value) {
// Initial Value has a bug 0 is min value only min value is negative
// tslint:disable-next-line: no-any
const correctBugComponent: any = component as any;
if (correctBugComponent
&& correctBugComponent.state
&& correctBugComponent.value !== correctBugComponent.props.value) {
correctBugComponent.setState({ value: 0, renderedValue: 0 });
}
}}
//originFromZero
// originFromZero
/>
<IconButton
key={'resetrotate'}
@ -557,7 +621,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getCropSettings(): JSX.Element {
let crop: ICropSettings = this.getCropValues();
const crop: ICropSettings = this.getCropValues();
return (<div>
<Checkbox
label={strings.LockAspect}
@ -572,16 +636,31 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}}
/>
<TextField label={strings.SourceX} value={'' + crop.sx} onChanged={(x) => this.setCrop(parseInt(x), undefined, undefined, undefined, crop.aspect)} />
<TextField label={strings.SourceY} value={'' + crop.sy} onChanged={(y) => this.setCrop(undefined, parseInt(y), undefined, undefined, crop.aspect)} />
<TextField label={strings.Width} value={'' + crop.width} onChanged={(w) => this.setCrop(undefined, undefined, parseInt(w), undefined, crop.aspect)} />
<TextField label={strings.Height} value={'' + crop.height} onChanged={(h) => this.setCrop(undefined, undefined, undefined, parseInt(h), crop.aspect)} />
<TextField
label={strings.SourceX}
value={'' + crop.sx}
// tslint:disable-next-line: radix
onChanged={(x) => this.setCrop(parseInt(x), undefined, undefined, undefined, crop.aspect)} />
<TextField
label={strings.SourceY}
value={'' + crop.sy}
// tslint:disable-next-line: radix
onChanged={(y) => this.setCrop(undefined, parseInt(y), undefined, undefined, crop.aspect)} />
<TextField
label={strings.Width}
value={'' + crop.width}
// tslint:disable-next-line: radix
onChanged={(w) => this.setCrop(undefined, undefined, parseInt(w), undefined, crop.aspect)} />
<TextField
label={strings.Height}
value={'' + crop.height}
// tslint:disable-next-line: radix
onChanged={(h) => this.setCrop(undefined, undefined, undefined, parseInt(h), crop.aspect)} />
</div>);
}
private getResizeSettings(): JSX.Element {
let resize: IResizeSettings = this.getResizeValues();
const resize: IResizeSettings = this.getResizeValues();
return (<div>
<Checkbox
@ -597,8 +676,15 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}}
/>
<TextField label={strings.Width} value={'' + resize.width} onChanged={(w) => this.setResize(parseInt(w), undefined, resize.aspect)} />
<TextField label={strings.Height} value={'' + resize.height} onChanged={(h) => this.setResize(undefined, parseInt(h), resize.aspect)} />
<TextField label={strings.Width}
value={'' + resize.width}
// tslint:disable-next-line: radix
onChanged={(w) => this.setResize(parseInt(w), undefined, resize.aspect)}
/>
<TextField label={strings.Height} value={'' + resize.height}
// tslint:disable-next-line: radix
onChanged={(h) => this.setResize(undefined, parseInt(h), resize.aspect)}
/>
</div>);
}
@ -607,10 +693,11 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getScaleSettings(): JSX.Element {
const lastvalue = this.getLastManipulation();
let scalevalue = 1;
const lastvalue: IImageManipulationSettings = this.getLastManipulation();
let scalevalue: number = 1;
if (lastvalue && lastvalue.type === ManipulationType.Scale) {
scalevalue = (lastvalue as IScaleSettings).scale ? (lastvalue as IScaleSettings).scale : 1;
scalevalue = (lastvalue as IScaleSettings).scale ?
(lastvalue as IScaleSettings).scale : 1;
}
return (<div>
@ -636,7 +723,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getResizeValues(): IResizeSettings {
let state: IImageManipulationSettings = this.getLastManipulation();
const state: IImageManipulationSettings = this.getLastManipulation();
let values: IResizeSettings = {
type: ManipulationType.Resize,
height: this.bufferRef.height,
@ -649,7 +736,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private setResize(width: number, height: number, aspect: number): void {
let values: IResizeSettings = this.getResizeValues();
const values: IResizeSettings = this.getResizeValues();
if (width) {
values.width = width;
if (aspect) {
@ -667,7 +754,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private getCropValues(): ICropSettings {
let state: IImageManipulationSettings = this.getLastManipulation();
const state: IImageManipulationSettings = this.getLastManipulation();
let values: ICropSettings = {
type: ManipulationType.Crop,
sx: 0,
@ -682,7 +769,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
private setCrop(sx: number, sy: number, width: number, height: number, aspect: number): void {
let values = this.getCropValues();
const values: ICropSettings = this.getCropValues();
const currentheight: number = this.bufferRef.height;
const currentwidth: number = this.bufferRef.width;
if (!isNaN(sx) && sx >= 0) {
@ -725,22 +812,21 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
}
}
if (isNaN(values.aspect) && !isNaN(aspect)) {
//aspect added
// aspect added
//limit w
// limit w
if ((values.width + values.sx) > currentwidth) {
values.width = currentwidth - values.sx;
}
values.height = values.width / aspect;
//limit h adn recalulate w
// limit h adn recalulate w
if ((values.height + values.sy) > currentheight) {
values.height = currentheight - values.sy;
values.width = values.height * aspect;
}
}
values.aspect = aspect;
if (aspect && (!isNaN(sx) || !isNaN(width))) {
values.height = values.width / aspect;
@ -765,8 +851,8 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
});
}
private calcRotate(value: number): void {
const lastVal = this.getLastManipulation();
let cvalue = 0;
const lastVal: IImageManipulationSettings = this.getLastManipulation();
let cvalue: number = 0;
if (lastVal && lastVal.type === ManipulationType.Rotate) {
cvalue = (lastVal as IRotateSettings).rotate;
}
@ -813,18 +899,17 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
return undefined;
}
private addOrUpdateLastManipulation(changed: IImageManipulationSettings): void {
let state = clone(this.props.settings);
let state: IImageManipulationSettings[] = clone(this.props.settings);
if (!state) {
state = [];
}
if (state.length > 0 && state[state.length - 1].type === changed.type) {
state[state.length - 1] = changed;
} else {
state.push(changed);
}
if (this.state.redosettings && this.state.redosettings.length > 0) {
this.setState({ redosettings: [] }, () => {
if (this.props.settingsChanged) {
@ -840,7 +925,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
private removeLastManipulation(): void {
if (this.props.settings && this.props.settings.length > 0) {
let state = clone(this.props.settings);
const state: IImageManipulationSettings[] = clone(this.props.settings);
state.splice(state.length - 1, 1);
if (this.props.settingsChanged) {
this.props.settingsChanged(clone(state));
@ -853,6 +938,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
iconProps={{ iconName: options.iconName }}
onRenderIcon={(p, defaultrenderer) => {
if (options.svgIcon) {
// tslint:disable-next-line: react-a11y-img-has-alt
return (<img className={styles.svgbutton} src={options.svgIcon} />);
}
return defaultrenderer(p);
@ -878,9 +964,9 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
ariaLabel={strings.CommandBarUndo}
disabled={!this.props.settings || this.props.settings.length < 1}
onClick={() => {
const settings = clone(this.props.settings);
const last = settings.pop();
const redo = clone(this.state.redosettings);
const settings: IImageManipulationSettings[] = clone(this.props.settings);
const last: IImageManipulationSettings = settings.pop();
const redo: IImageManipulationSettings[] = clone(this.state.redosettings);
redo.push(last);
this.setState({ redosettings: redo },
() => {
@ -897,9 +983,9 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
ariaLabel={strings.CommandBarRedo}
disabled={!this.state.redosettings || this.state.redosettings.length < 1}
onClick={() => {
const redosettings = clone(this.state.redosettings);
const redo = redosettings.pop();
const settings = clone(this.props.settings);
const redosettings: IImageManipulationSettings[] = clone(this.state.redosettings);
const redo: IImageManipulationSettings = redosettings.pop();
const settings: IImageManipulationSettings[] = clone(this.props.settings);
settings.push(redo);
this.setState({ redosettings: redosettings },
() => {
@ -933,7 +1019,7 @@ this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHe
onClick={() => this.openPanel(SettingPanelType.History)}
/>
<Panel
isOpen={this.state.settingPanel != SettingPanelType.Closed}
isOpen={this.state.settingPanel !== SettingPanelType.Closed}
type={PanelType.smallFixedFar}
onDismiss={this.closeFilter}
headerText={this.getPanelHeader(this.state.settingPanel)}

View File

@ -1,8 +1,12 @@
import * as React from 'react';
// tslint:disable-next-line: no-any
const colorFilterIcon: any = require('../../svg/colorFilter.svg');
// tslint:disable-next-line: no-any
const cropIcon: any = require('../../svg/crop.svg');
// tslint:disable-next-line: no-any
const flipVerticalIcon: any = require('../../svg/flipVertical.svg');
// tslint:disable-next-line: no-any
const resizeIcon: any = require('../../svg/resize.svg');
import * as strings from 'ImageManipulationStrings';
@ -21,7 +25,7 @@ export enum SettingPanelType {
export enum FilterType {
Grayscale,
Sepia,
Sepia
/*
Blur,
Emboss,
@ -99,6 +103,7 @@ export interface IFilterTypeData {
export interface IManipulationTypeDataBase {
text: string;
iconName?: string;
// tslint:disable-next-line: no-any
svgIcon?: any;
settingPanelType: SettingPanelType;
}
@ -117,7 +122,7 @@ export const manipulationTypeData: IManipulationTypeData = {
svgIcon: cropIcon,
toHTML: (item: ICropSettings) => {
return (<span></span>);
//return (<span>{`X:${item.sx} Y:${item.sy}`}</span>);
// return (<span>{`X:${item.sx} Y:${item.sy}`}</span>);
},
settingPanelType: SettingPanelType.Crop
},
@ -153,8 +158,5 @@ export const manipulationTypeData: IManipulationTypeData = {
svgIcon: resizeIcon,
toHTML: (item: IResizeSettings) => { return (<span></span>); },
settingPanelType: SettingPanelType.Resize
},
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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