Documentation include Source files
|
@ -16,15 +16,34 @@ extensions:
|
|||
|
||||
# React Image Editor
|
||||
|
||||
## Summary
|
||||
TODO
|
||||
|
||||
IMAGE Animated GIF
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
This solution contains an SPFx webpart that shows a HTML Image Editor based on canvas and [Office UI Fabric](https://developer.microsoft.com/fluentui/).
|
||||
|
||||
Key features of the Editor
|
||||
* Resize
|
||||
* Crop
|
||||
* Flip
|
||||
* Rotate
|
||||
* Scale
|
||||
* Filter (Grayscale / Sepia)
|
||||
* Redo / Undo
|
||||
* Histoy of Actions
|
||||
|
||||
The Placeholder and FilePicker is a component from [sp-dev-fx-controls-react ](https://pnp.github.io/sp-dev-fx-controls-react/)
|
||||
|
||||
![react-image-editor in action](assets/react-image-editor.gif)
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![version](https://img.shields.io/badge/version-1.4.0-green.svg)
|
||||
> SharePoint 2019 and SharePoint Online
|
||||
|
||||
References to office-ui-fabric-react version 5.x because of SharePoint 2019 Support
|
||||
|
||||
## Applies to
|
||||
|
||||
|
@ -35,7 +54,7 @@ IMAGE Animated GIF
|
|||
|
||||
## Prerequisites
|
||||
|
||||
> Any special pre-requisites?
|
||||
> SharePoint Online or SharePoint 2019
|
||||
|
||||
## Solution
|
||||
|
||||
|
@ -43,6 +62,8 @@ Solution|Author(s)
|
|||
--------|---------
|
||||
react-image-editor | Peter Paul Kirschner ([@petkir_at](https://twitter.com/petkir_at))
|
||||
|
||||
Thanks to [celum](https://www.celum.com/) and [cubido](https://www.cubido.at/) to allow to share this code.
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
|
@ -61,6 +82,7 @@ Version|Date|Comments
|
|||
- Ensure that you are at the solution folder
|
||||
- in the command-line run:
|
||||
- **npm install**
|
||||
- **edit config\serve.json set "initialPage": "https://{tenant}.sharepoint.com/_layouts/15/workbench.aspx"
|
||||
- **gulp serve**
|
||||
|
||||
> Include any additional steps as needed.
|
||||
|
@ -69,7 +91,6 @@ Version|Date|Comments
|
|||
* 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)
|
||||
* ImageManipulation from [react-image-manipulation-spfx](https://github.com/petkir/react-image-manipulation-spfx)
|
||||
* Office UI Fabric
|
||||
|
||||
|
||||
|
|
After Width: | Height: | Size: 9.0 MiB |
|
@ -3,10 +3,19 @@
|
|||
"name": "pnp-sp-dev-spfx-web-parts-react-image-editor",
|
||||
"source": "pnp",
|
||||
"title": "React Image Editor",
|
||||
"shortDescription": "",
|
||||
"shortDescription": "This solution contains an SPFx webpart that shows a 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 a HTML Image Editor based on canvas and Office UI Fabric ",
|
||||
"Key features of the Editor",
|
||||
"* Resize",
|
||||
"* Crop",
|
||||
"* Flip",
|
||||
"* Rotate",
|
||||
"* Scale",
|
||||
"* Filter (Grayscale / Sepia)",
|
||||
"* Redo / Undo",
|
||||
"* Histoy of Actions"
|
||||
],
|
||||
"created": "2021-03-17",
|
||||
"modified": "2021-03-17",
|
||||
|
@ -32,7 +41,7 @@
|
|||
{
|
||||
"type": "image",
|
||||
"order": 100,
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-image-editor/assets/ReactImageEditorWebPart.gif",
|
||||
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-image-editor/assets/react-image-editor.gif",
|
||||
"alt": "React Image Editor Web part"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
"localizedResources": {
|
||||
"ReactImageEditorWebPartStrings": "lib/webparts/reactImageEditor/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
|
||||
"ImageManipulationStrings": "node_modules/react-image-manipulation-spfx/lib/components/ImageManipulation/loc/{locale}.js"
|
||||
"ImageManipulationStrings": "lib/components/ImageManipulation/loc/{locale}.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"initialPage": "https://contoso.sharepoint.com/_layouts/15/workbench.aspx",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-image-editor",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -534,6 +534,22 @@
|
|||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2",
|
||||
"tslib": "~1.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"office-ui-fabric-react": {
|
||||
"version": "5.21.0",
|
||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.21.0.tgz",
|
||||
"integrity": "sha512-9IBLK6JUWYvdCumKywjkSP7mf/MqTRorP1qXtb2c+w2fQJH4NkdBjanO0zr37c5VPwlqc0KvPf51E9t1NU7oTg==",
|
||||
"requires": {
|
||||
"@microsoft/load-themed-styles": "^1.7.2",
|
||||
"@uifabric/icons": ">=5.2.0 <6.0.0",
|
||||
"@uifabric/merge-styles": ">=5.6.0 <6.0.0",
|
||||
"@uifabric/styling": ">=5.7.0 <6.0.0",
|
||||
"@uifabric/utilities": ">=5.3.3 <6.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"tslib": "^1.7.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@microsoft/package-deps-hash": {
|
||||
|
@ -1066,6 +1082,22 @@
|
|||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2",
|
||||
"requirejs": "2.1.20"
|
||||
},
|
||||
"dependencies": {
|
||||
"office-ui-fabric-react": {
|
||||
"version": "5.21.0",
|
||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.21.0.tgz",
|
||||
"integrity": "sha512-9IBLK6JUWYvdCumKywjkSP7mf/MqTRorP1qXtb2c+w2fQJH4NkdBjanO0zr37c5VPwlqc0KvPf51E9t1NU7oTg==",
|
||||
"requires": {
|
||||
"@microsoft/load-themed-styles": "^1.7.2",
|
||||
"@uifabric/icons": ">=5.2.0 <6.0.0",
|
||||
"@uifabric/merge-styles": ">=5.6.0 <6.0.0",
|
||||
"@uifabric/styling": ">=5.7.0 <6.0.0",
|
||||
"@uifabric/utilities": ">=5.3.3 <6.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"tslib": "^1.7.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@microsoft/sp-lodash-subset": {
|
||||
|
@ -1190,6 +1222,22 @@
|
|||
"react-dom": "15.6.2",
|
||||
"scrollreveal": "3.3.6",
|
||||
"tslib": "~1.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"office-ui-fabric-react": {
|
||||
"version": "5.21.0",
|
||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.21.0.tgz",
|
||||
"integrity": "sha512-9IBLK6JUWYvdCumKywjkSP7mf/MqTRorP1qXtb2c+w2fQJH4NkdBjanO0zr37c5VPwlqc0KvPf51E9t1NU7oTg==",
|
||||
"requires": {
|
||||
"@microsoft/load-themed-styles": "^1.7.2",
|
||||
"@uifabric/icons": ">=5.2.0 <6.0.0",
|
||||
"@uifabric/merge-styles": ">=5.6.0 <6.0.0",
|
||||
"@uifabric/styling": ">=5.7.0 <6.0.0",
|
||||
"@uifabric/utilities": ">=5.3.3 <6.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"tslib": "^1.7.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@microsoft/sp-webpart-workbench": {
|
||||
|
@ -1218,6 +1266,23 @@
|
|||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2",
|
||||
"tslib": "~1.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"office-ui-fabric-react": {
|
||||
"version": "5.21.0",
|
||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.21.0.tgz",
|
||||
"integrity": "sha512-9IBLK6JUWYvdCumKywjkSP7mf/MqTRorP1qXtb2c+w2fQJH4NkdBjanO0zr37c5VPwlqc0KvPf51E9t1NU7oTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@microsoft/load-themed-styles": "^1.7.2",
|
||||
"@uifabric/icons": ">=5.2.0 <6.0.0",
|
||||
"@uifabric/merge-styles": ">=5.6.0 <6.0.0",
|
||||
"@uifabric/styling": ">=5.7.0 <6.0.0",
|
||||
"@uifabric/utilities": ">=5.3.3 <6.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"tslib": "^1.7.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@microsoft/stream-collator": {
|
||||
|
@ -3638,11 +3703,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.2.6",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
|
||||
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
|
||||
},
|
||||
"clean-css": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz",
|
||||
|
@ -12110,17 +12170,33 @@
|
|||
"integrity": "sha512-VwGbHIT89e+9XdGcb2gdPgKgYQGjRG5peyPNO982znlCSB/UIM9g7gF6JmHpaMHb8xvEScY7UGJ8GRz6GQVBRg=="
|
||||
},
|
||||
"office-ui-fabric-react": {
|
||||
"version": "5.21.0",
|
||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.21.0.tgz",
|
||||
"integrity": "sha512-9IBLK6JUWYvdCumKywjkSP7mf/MqTRorP1qXtb2c+w2fQJH4NkdBjanO0zr37c5VPwlqc0KvPf51E9t1NU7oTg==",
|
||||
"version": "5.131.0",
|
||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.131.0.tgz",
|
||||
"integrity": "sha512-QOYu1uf92qhTTIlBAj8teKvRpCmpliRZjynYtgeeUbDm4C4GtXdb/O1rPNFsfT0PNtPC8dCNeQ7/CXjQenUkyw==",
|
||||
"requires": {
|
||||
"@microsoft/load-themed-styles": "^1.7.2",
|
||||
"@uifabric/icons": ">=5.2.0 <6.0.0",
|
||||
"@uifabric/merge-styles": ">=5.6.0 <6.0.0",
|
||||
"@uifabric/styling": ">=5.7.0 <6.0.0",
|
||||
"@uifabric/utilities": ">=5.3.3 <6.0.0",
|
||||
"@microsoft/load-themed-styles": "^1.7.13",
|
||||
"@uifabric/icons": ">=5.8.0 <6.0.0",
|
||||
"@uifabric/merge-styles": ">=5.17.1 <6.0.0",
|
||||
"@uifabric/styling": ">=5.36.0 <6.0.0",
|
||||
"@uifabric/utilities": ">=5.34.2 <6.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"tslib": "^1.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/load-themed-styles": {
|
||||
"version": "1.10.152",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.152.tgz",
|
||||
"integrity": "sha512-NLF5+jkpfr/cCyxqqrPrpuL1YvYGMhPa4fSD8s76eI5/jpIyr1cM3PJ3vk03/XLOjSMuOqYntipB5FD1tKadyQ=="
|
||||
},
|
||||
"@uifabric/icons": {
|
||||
"version": "5.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-5.8.0.tgz",
|
||||
"integrity": "sha512-EUhKxYlIPJshg4fQvCNTYSk0p7RhzEWeEAJBV4sao1SKmN0/pZBnkLbDqWjU5VUfdwZZYiIdaLRpM+pyzhniZw==",
|
||||
"requires": {
|
||||
"@uifabric/styling": ">=5.30.1 <6.0.0",
|
||||
"tslib": "^1.7.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"on-finished": {
|
||||
|
@ -13623,51 +13699,6 @@
|
|||
"resolved": "https://registry.npmjs.org/react-dom-factories/-/react-dom-factories-1.0.2.tgz",
|
||||
"integrity": "sha1-63cFxNs2+1AbOqOP91lhaqD/luA="
|
||||
},
|
||||
"react-image-manipulation-spfx": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-image-manipulation-spfx/-/react-image-manipulation-spfx-0.0.4.tgz",
|
||||
"integrity": "sha512-5VE1Hfn4xg4Q98k4aRFnwxB2hhK/CkSh3TTB/FnEqAyqB04Buz0ZxdspVsVBB41mY57UwehWaP/VBHX5IBvc5w==",
|
||||
"requires": {
|
||||
"@microsoft/sp-core-library": "~1.4.0",
|
||||
"@microsoft/sp-lodash-subset": "~1.4.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "~1.4.0",
|
||||
"@microsoft/sp-webpart-base": "~1.4.0",
|
||||
"classnames": "^2.2.6",
|
||||
"office-ui-fabric-react": "5.131.0",
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/load-themed-styles": {
|
||||
"version": "1.10.151",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.151.tgz",
|
||||
"integrity": "sha512-LCRBzFxmxX94cNNS3ATWu877Y17WJO2/Cg9DQKHrCtrW0tnr7+k6IIGXWAu4L5l9tsa5sQ1YThV6WUS4rWEsFA=="
|
||||
},
|
||||
"@uifabric/icons": {
|
||||
"version": "5.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-5.8.0.tgz",
|
||||
"integrity": "sha512-EUhKxYlIPJshg4fQvCNTYSk0p7RhzEWeEAJBV4sao1SKmN0/pZBnkLbDqWjU5VUfdwZZYiIdaLRpM+pyzhniZw==",
|
||||
"requires": {
|
||||
"@uifabric/styling": ">=5.30.1 <6.0.0",
|
||||
"tslib": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"office-ui-fabric-react": {
|
||||
"version": "5.131.0",
|
||||
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.131.0.tgz",
|
||||
"integrity": "sha512-QOYu1uf92qhTTIlBAj8teKvRpCmpliRZjynYtgeeUbDm4C4GtXdb/O1rPNFsfT0PNtPC8dCNeQ7/CXjQenUkyw==",
|
||||
"requires": {
|
||||
"@microsoft/load-themed-styles": "^1.7.13",
|
||||
"@uifabric/icons": ">=5.8.0 <6.0.0",
|
||||
"@uifabric/merge-styles": ">=5.17.1 <6.0.0",
|
||||
"@uifabric/styling": ">=5.36.0 <6.0.0",
|
||||
"@uifabric/utilities": ">=5.34.2 <6.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"tslib": "^1.7.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-image-editor",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
|
@ -17,9 +17,9 @@
|
|||
"@microsoft/sp-office-ui-fabric-core": "~1.4.0",
|
||||
"@microsoft/sp-webpart-base": "~1.4.0",
|
||||
"@pnp/spfx-controls-react": "^1.21.1",
|
||||
"office-ui-fabric-react": "5.131.0",
|
||||
"react": "15.6.2",
|
||||
"react-dom": "15.6.2",
|
||||
"react-image-manipulation-spfx": "0.0.4"
|
||||
"react-dom": "15.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "15.6.6",
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
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;
|
||||
|
||||
//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];
|
||||
data[i - 3] = gray;
|
||||
data[i - 2] = gray;
|
||||
data[i - 1] = gray;
|
||||
|
||||
}
|
||||
|
||||
return (imageData);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export interface IImageFilter {
|
||||
//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;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
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];
|
||||
private noise: number = 0;
|
||||
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) {
|
||||
// change image colors
|
||||
imageData.data[i] = this.r[imageData.data[i]];
|
||||
imageData.data[i + 1] = this.g[imageData.data[i + 1]];
|
||||
imageData.data[i + 2] = this.b[imageData.data[i + 2]];
|
||||
|
||||
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];
|
||||
imageData.data[i + j] = (iPN > 255) ? 255 : iPN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (imageData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { Icon } from 'office-ui-fabric-react';
|
||||
import * as React from 'react';
|
||||
import styles from './ImageManipulation.module.scss';
|
||||
|
||||
import { IImageManipulationSettings, manipulationTypeData } from './ImageManipulation.types';
|
||||
|
||||
export const historyItem = (item:IImageManipulationSettings, index:number): JSX.Element => {
|
||||
if(!item) {
|
||||
return undefined;
|
||||
}
|
||||
const data=manipulationTypeData[item.type];
|
||||
|
||||
const detailrender = 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.historyItemText}>{data.text}</span>
|
||||
<span className={styles.historyItemDetails}>{detailrender}</span>
|
||||
</span>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,63 @@
|
|||
|
||||
|
||||
.historyItem {
|
||||
.historyItemText,
|
||||
.historyItemIcon{
|
||||
padding-right: 6px;
|
||||
}
|
||||
.historyItemDetails{
|
||||
display: inline;
|
||||
}
|
||||
.historyItemSvg{
|
||||
margin-bottom: -3px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
.svgbuttonPanel {
|
||||
height: 40px;
|
||||
}
|
||||
.buttonHolderPanel {
|
||||
&>button {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.imageEditor{
|
||||
width: 100%;
|
||||
position: relative;
|
||||
.commandBar{
|
||||
position: absolute;
|
||||
top:-32px;
|
||||
button {
|
||||
background-color: lightgray;
|
||||
}
|
||||
.svgbutton {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
.imageplaceholder{
|
||||
position: relative;
|
||||
}
|
||||
.canvasmaxwidth{
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
.iconbtn {
|
||||
min-width: 32px;
|
||||
width: 32px;
|
||||
padding: 10px 2px;
|
||||
margin-right: 3px;
|
||||
height: 50px;
|
||||
&>div{
|
||||
display: inline-block;
|
||||
}
|
||||
.imgtext{
|
||||
display: block;
|
||||
}
|
||||
.imgicon{
|
||||
display: block;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,945 @@
|
|||
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 * as React from 'react';
|
||||
|
||||
import ImageCrop from './components/ImageCrop';
|
||||
import ImageGrid from './components/ImageGrid';
|
||||
import ItemOrder from './components/ItemOrder';
|
||||
|
||||
import { GrayscaleFilter } from './Filter/GrayscaleFilter';
|
||||
import { SepiaFilter } from './Filter/SepiaFilter';
|
||||
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';
|
||||
|
||||
|
||||
const flipVerticalIcon: any = require('../../svg/flipVertical.svg');
|
||||
const flipHorizontalIcon: any = require('../../svg/flipHorizontal.svg');
|
||||
|
||||
import * as strings from 'ImageManipulationStrings';
|
||||
|
||||
export interface IImageManipulationConfig {
|
||||
rotateButtons: number[];
|
||||
}
|
||||
|
||||
export interface IImageManipulationProps {
|
||||
src: string;
|
||||
settings?: IImageManipulationSettings[];
|
||||
settingschanged?: (settings: IImageManipulationSettings[]) => void;
|
||||
imgLoadError?: () => void;
|
||||
editMode?: (mode: boolean) => void;
|
||||
configsettings: IImageManipulationConfig;
|
||||
displyMode: DisplayMode;
|
||||
}
|
||||
|
||||
export interface IImageManipulationState {
|
||||
settingPanel: SettingPanelType;
|
||||
redosettings: IImageManipulationSettings[];
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
|
||||
constructor(props: IImageManipulationProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
settingPanel: SettingPanelType.Closed,
|
||||
redosettings: []
|
||||
};
|
||||
this.openPanel = this.openPanel.bind(this);
|
||||
this.setRotate = this.setRotate.bind(this);
|
||||
this.calcRotate = this.calcRotate.bind(this);
|
||||
this.setCanvasRef = this.setCanvasRef.bind(this);
|
||||
this.setBufferRef = this.setBufferRef.bind(this);
|
||||
this.setManipulateRef = this.setManipulateRef.bind(this);
|
||||
this.setScale = this.setScale.bind(this);
|
||||
this.closeFilter = this.closeFilter.bind(this);
|
||||
|
||||
}
|
||||
public componentDidMount(): void {
|
||||
this.imageChanged(this.props.src);
|
||||
}
|
||||
public componentDidUpdate(prevProps: IImageManipulationProps): void {
|
||||
if (prevProps.src !== this.props.src) {
|
||||
this.imageChanged(this.props.src);
|
||||
} else {
|
||||
this.applySettings();
|
||||
}
|
||||
}
|
||||
|
||||
private imageChanged(url: string) {
|
||||
this.img = new Image();
|
||||
this.img.src = url;
|
||||
this.img.crossOrigin = "Anonymous";
|
||||
this.img.onload = () => {
|
||||
|
||||
this.applySettings();
|
||||
};
|
||||
this.img.onerror = (event: Event | string) => {
|
||||
if (this.props.imgLoadError) {
|
||||
this.props.imgLoadError();
|
||||
}
|
||||
};
|
||||
}
|
||||
private applySettings(): void {
|
||||
this.canvasRef.width = this.img.width;
|
||||
this.canvasRef.height = this.img.height;
|
||||
this.bufferRef.width = this.img.width;
|
||||
this.bufferRef.height = this.img.height;
|
||||
this.manipulateRef.width = this.img.width;
|
||||
this.manipulateRef.height = this.img.height;
|
||||
|
||||
let currentwidth = this.img.width;
|
||||
let currentheight = this.img.height;
|
||||
let newwidth = currentwidth;
|
||||
let newheight = currentheight;
|
||||
this.bufferCtx.clearRect(0, 0, currentwidth, currentheight);
|
||||
this.bufferCtx.drawImage(this.img, 0, 0);
|
||||
|
||||
|
||||
if (this.props.settings) {
|
||||
this.props.settings.forEach((element, index) => {
|
||||
this.manipulateCtx.clearRect(0, 0, currentwidth, currentheight);
|
||||
this.manipulateRef.width = currentwidth;
|
||||
this.manipulateRef.height = currentheight;
|
||||
this.manipulateCtx.save();
|
||||
let nothingToDo: boolean = false;
|
||||
switch (element.type) {
|
||||
case ManipulationType.Flip:
|
||||
const filp = element as IFlipSettings;
|
||||
if (filp.flipY) {
|
||||
this.manipulateCtx.translate(0, currentheight);
|
||||
this.manipulateCtx.scale(1, -1);
|
||||
}
|
||||
if (filp.flipX) {
|
||||
this.manipulateCtx.translate(currentwidth, 0);
|
||||
this.manipulateCtx.scale(-1, 1);
|
||||
}
|
||||
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
|
||||
break;
|
||||
case ManipulationType.Rotate:
|
||||
const rotate = element as IRotateSettings;
|
||||
if (!rotate.rotate || rotate.rotate === 0 || isNaN(rotate.rotate)) {
|
||||
nothingToDo = true;
|
||||
break;
|
||||
}
|
||||
if (rotate.rotate) {
|
||||
const angelcalc = rotate.rotate * Math.PI / 180;
|
||||
const oldwidth = currentwidth;
|
||||
const oldheight = currentheight;
|
||||
let offsetwidth = 0;
|
||||
let offsetheight = 0;
|
||||
|
||||
var a = oldwidth * Math.abs(Math.cos(angelcalc));
|
||||
var b = oldheight * Math.abs(Math.sin(angelcalc));
|
||||
|
||||
var p = oldwidth * Math.abs(Math.sin(angelcalc));
|
||||
var q = oldheight * Math.abs(Math.cos(angelcalc));
|
||||
newwidth = a + b;
|
||||
newheight = p + q;
|
||||
|
||||
offsetwidth = (newwidth - oldwidth) / 2;
|
||||
offsetheight = (newheight - oldheight) / 2;
|
||||
|
||||
this.manipulateRef.width = newwidth;
|
||||
this.manipulateRef.height = newheight;
|
||||
|
||||
this.manipulateCtx.translate(newwidth / 2, newheight / 2);
|
||||
this.manipulateCtx.rotate(angelcalc);
|
||||
this.manipulateCtx.translate(newwidth / 2 * -1, newheight / 2 * -1);
|
||||
|
||||
this.manipulateCtx.drawImage(this.bufferRef, offsetwidth, offsetheight);
|
||||
|
||||
}
|
||||
break;
|
||||
case ManipulationType.Scale:
|
||||
const scale = element as IScaleSettings;
|
||||
if (scale.scale) {
|
||||
this.manipulateCtx.translate(currentwidth / 2, currentheight / 2);
|
||||
this.manipulateCtx.scale(scale.scale, scale.scale);
|
||||
this.manipulateCtx.translate(currentwidth / 2 * -1, currentheight / 2 * -1);
|
||||
|
||||
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
|
||||
}
|
||||
break;
|
||||
case ManipulationType.Filter:
|
||||
nothingToDo = true;
|
||||
const filter = element as IFilterSettings;
|
||||
var imageData = this.bufferCtx.getImageData(0, 0, currentwidth, currentheight);
|
||||
switch (filter.filterType) {
|
||||
case FilterType.Grayscale:
|
||||
imageData = new GrayscaleFilter().process(imageData, currentwidth, currentheight, undefined, undefined);
|
||||
break;
|
||||
case FilterType.Sepia:
|
||||
imageData = new SepiaFilter().process(imageData, currentwidth, currentheight, undefined, undefined);
|
||||
break;
|
||||
}
|
||||
this.bufferCtx.putImageData(imageData, 0, 0);
|
||||
break;
|
||||
case ManipulationType.Crop:
|
||||
const last = this.props.settings.length === index + 1;
|
||||
if (last && this.state.settingPanel === SettingPanelType.Crop) {
|
||||
//Do nothingis last and current edit
|
||||
nothingToDo = true;
|
||||
} else {
|
||||
const crop = element as ICropSettings;
|
||||
const sourceX = crop.sx;
|
||||
const sourceY = crop.sy;
|
||||
newwidth = crop.width;
|
||||
newheight = crop.height;
|
||||
this.manipulateRef.width = newwidth;
|
||||
this.manipulateRef.height = newheight;
|
||||
this.manipulateCtx.drawImage(this.bufferRef, sourceX, sourceY, newwidth, newheight, 0, 0, newwidth, newheight);
|
||||
}
|
||||
break;
|
||||
|
||||
case ManipulationType.Resize:
|
||||
const resize = element as IResizeSettings;
|
||||
newwidth = resize.width;
|
||||
newheight = resize.height;
|
||||
this.manipulateCtx.drawImage(this.bufferRef, 0, 0);
|
||||
}
|
||||
this.manipulateCtx.restore();
|
||||
|
||||
if (!nothingToDo) {
|
||||
this.bufferCtx.clearRect(0, 0, currentwidth, currentheight);
|
||||
|
||||
this.bufferRef.width = newwidth;
|
||||
this.bufferRef.height = newheight;
|
||||
|
||||
this.bufferCtx.clearRect(0, 0, newwidth, newheight);
|
||||
this.bufferCtx.drawImage(this.manipulateRef, 0, 0, newwidth, newheight);
|
||||
|
||||
|
||||
currentwidth = newwidth;
|
||||
currentheight = newheight;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*this.canvasCtx.clearRect(0, 0, this.canvasRef.width, this.canvasRef.height)
|
||||
// this.canvasCtx.drawImage(this.bufferRef, 0, 0);
|
||||
const sourceX = 400;
|
||||
const sourceY = 200;
|
||||
const sourceWidth = 1200;
|
||||
const sourceHeight = 600;
|
||||
this.canvasCtx.drawImage(this.bufferRef, sourceX, sourceY, sourceWidth, sourceHeight, 0, 0, this.canvasRef.width, this.canvasRef.height);
|
||||
*/
|
||||
this.canvasCtx.clearRect(0, 0, currentwidth, currentheight);
|
||||
this.canvasRef.width = currentwidth;
|
||||
this.canvasRef.height = currentheight;
|
||||
this.canvasCtx.drawImage(this.bufferRef, 0, 0);
|
||||
// this.canvasCtx.drawImage(this.bufferRef, 0, 0, currentwidth, currentheight);
|
||||
this.wrapperRef.style.width = currentwidth + 'px';
|
||||
//this.wrapperRef.style.height = currentheight + 'px';
|
||||
// let height = this.canvasRef.height;
|
||||
// let width = this.canvasRef.width;
|
||||
|
||||
//this.canvasctx.translate(this.canvasRef.width / 2 * -1, this.canvasRef.height / 2 * -1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public render(): React.ReactElement<IImageManipulationProps> {
|
||||
return (
|
||||
<div className={styles.imageEditor} style={{
|
||||
marginTop: this.props.displyMode === DisplayMode.Edit ? '40px' : '0px',
|
||||
}} >
|
||||
{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 getCropGrid(): JSX.Element {
|
||||
const lastset = this.getLastManipulation() as ICropSettings;
|
||||
let lastdata: ICrop = { sx: 0, sy: 0, width: 0, height: 0 };
|
||||
|
||||
if (lastset && lastset.type === ManipulationType.Crop) {
|
||||
lastdata = lastset;
|
||||
}
|
||||
return (<ImageCrop
|
||||
crop={lastdata}
|
||||
showRuler
|
||||
sourceHeight={this.img.height}
|
||||
sourceWidth={this.img.width}
|
||||
onChange={(crop) => {
|
||||
this.setCrop(crop.sx, crop.sy, crop.width, crop.height, crop.aspect);
|
||||
}
|
||||
}
|
||||
/>);
|
||||
}
|
||||
|
||||
private getResizeGrid(): JSX.Element {
|
||||
const lastset = this.getLastManipulation() as IResizeSettings;
|
||||
if (lastset && lastset.type === ManipulationType.Resize) {
|
||||
return (<ImageGrid
|
||||
width={lastset.width} height={lastset.height}
|
||||
aspect={lastset.aspect}
|
||||
onChange={(size) => this.setResize(size.width, size.height, lastset.aspect)}
|
||||
/>);
|
||||
}
|
||||
return (<ImageGrid
|
||||
onChange={(size) => this.setResize(size.width, size.height, undefined)}
|
||||
//aspect={this.getAspect()}
|
||||
width={this.canvasRef.width} height={this.canvasRef.height} />);
|
||||
}
|
||||
|
||||
private getMaxWidth(): string {
|
||||
const { settingPanel } = this.state;
|
||||
if (settingPanel === SettingPanelType.Crop || settingPanel === SettingPanelType.Resize) {
|
||||
return '';
|
||||
}
|
||||
return styles.canvasmaxwidth;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
private closeFilter(): void {
|
||||
this.setState({
|
||||
settingPanel: SettingPanelType.Closed
|
||||
}, () => this.toggleEditMode(false));
|
||||
}
|
||||
private getPanelHeader(settingPanel: SettingPanelType): string {
|
||||
switch (settingPanel) {
|
||||
case SettingPanelType.Filter:
|
||||
return strings.ManipulationTypeFilter;
|
||||
case SettingPanelType.Flip:
|
||||
return strings.ManipulationTypeFlip;
|
||||
case SettingPanelType.Rotate:
|
||||
return strings.ManipulationTypeRotate;
|
||||
case SettingPanelType.Scale:
|
||||
return strings.ManipulationTypeScale;
|
||||
case SettingPanelType.Crop:
|
||||
return strings.ManipulationTypeCrop;
|
||||
case SettingPanelType.Resize:
|
||||
return strings.ManipulationTypeResize;
|
||||
case SettingPanelType.History:
|
||||
return strings.SettingPanelHistory;
|
||||
}
|
||||
}
|
||||
private onRenderFooterContent(): JSX.Element {
|
||||
return (
|
||||
<div> </div>
|
||||
);
|
||||
}
|
||||
private renderPanelContent(): JSX.Element {
|
||||
switch (this.state.settingPanel) {
|
||||
case SettingPanelType.Filter:
|
||||
return this.getFilterSettings();
|
||||
case SettingPanelType.Flip:
|
||||
return this.getFlipSettings();
|
||||
case SettingPanelType.Rotate:
|
||||
return this.getRotateSettings();
|
||||
case SettingPanelType.Scale:
|
||||
return this.getScaleSettings();
|
||||
case SettingPanelType.Crop:
|
||||
return this.getCropSettings();
|
||||
case SettingPanelType.Resize:
|
||||
return this.getResizeSettings();
|
||||
|
||||
case SettingPanelType.History:
|
||||
return this.getHistorySettings();
|
||||
}
|
||||
}
|
||||
|
||||
private openPanel(settingPanel: SettingPanelType): void {
|
||||
this.setState({
|
||||
settingPanel: settingPanel
|
||||
}, () => this.toggleEditMode(true));
|
||||
}
|
||||
|
||||
private toggleEditMode(mode: boolean) {
|
||||
if (this.props.editMode) {
|
||||
this.props.editMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
private getHistorySettings(): JSX.Element {
|
||||
return (<ItemOrder
|
||||
label={''}
|
||||
disabled={false}
|
||||
moveUpIconName={'ChevronUpSmall'}
|
||||
moveDownIconName={'ChevronDownSmall'}
|
||||
disableDragAndDrop={false}
|
||||
removeArrows={false}
|
||||
items={this.props.settings}
|
||||
valueChanged={(newhist) => {
|
||||
if (this.state.redosettings && this.state.redosettings.length > 0) {
|
||||
this.setState({ redosettings: [] }, () => {
|
||||
|
||||
if (this.props.settingschanged) {
|
||||
this.props.settingschanged(newhist);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
if (this.props.settingschanged) {
|
||||
this.props.settingschanged(newhist);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}}
|
||||
onRenderItem={historyItem}
|
||||
/>);
|
||||
}
|
||||
|
||||
private getFilterSettings(): JSX.Element {
|
||||
return (<div>
|
||||
{
|
||||
Object.keys(filterTypeData).map((key, index) => {
|
||||
return (<Checkbox
|
||||
key={'Filter' + index}
|
||||
label={filterTypeData[key]}
|
||||
checked={this.isFilterActive((+key) as FilterType)}
|
||||
onChange={() => this.toggleFilter((+key) as FilterType)}
|
||||
/>);
|
||||
})
|
||||
}</div>);
|
||||
|
||||
}
|
||||
|
||||
private toggleFilter(type: FilterType, nvalue: number = undefined, svalue: string = undefined): void {
|
||||
let tmpsettings = 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);
|
||||
tmpsettings.splice(removeindex, 1);
|
||||
} else {
|
||||
tmpsettings.push({
|
||||
type: ManipulationType.Filter,
|
||||
filterType: type,
|
||||
nvalue: nvalue,
|
||||
svalue: svalue
|
||||
});
|
||||
}
|
||||
if (this.props.settingschanged) {
|
||||
this.props.settingschanged(tmpsettings);
|
||||
}
|
||||
}
|
||||
|
||||
private getFlipSettings(): JSX.Element {
|
||||
return (<div className={styles.buttonHolderPanel} >
|
||||
<IconButton
|
||||
iconProps={{ iconName: 'SwitcherStartEnd' }}
|
||||
onRenderIcon={() => { return (<img className={styles.svgbuttonPanel} src={flipVerticalIcon} />); }}
|
||||
title={strings.FlipHorizontal}
|
||||
ariaLabel={strings.FlipHorizontal}
|
||||
onClick={() => {
|
||||
|
||||
let last = this.getLastManipulation();
|
||||
if (last && last.type === ManipulationType.Flip) {
|
||||
(last as IFlipSettings).flipX = !(last as IFlipSettings).flipX;
|
||||
if ((last as IFlipSettings).flipX === false &&
|
||||
(last as IFlipSettings).flipY === false) {
|
||||
this.removeLastManipulation();
|
||||
} else {
|
||||
this.addOrUpdateLastManipulation(last);
|
||||
}
|
||||
} else {
|
||||
this.addOrUpdateLastManipulation({ type: ManipulationType.Flip, flipX: true, flipY: false });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
onRenderIcon={() => { return (<img className={styles.svgbuttonPanel} src={flipHorizontalIcon} />); }}
|
||||
title={strings.FlipVertical}
|
||||
ariaLabel={strings.FlipVertical}
|
||||
onClick={() => {
|
||||
let last = this.getLastManipulation();
|
||||
if (last && last.type === ManipulationType.Flip) {
|
||||
(last as IFlipSettings).flipY = !(last as IFlipSettings).flipY;
|
||||
if ((last as IFlipSettings).flipX === false &&
|
||||
(last as IFlipSettings).flipY === false) {
|
||||
this.removeLastManipulation();
|
||||
} else {
|
||||
this.addOrUpdateLastManipulation(last);
|
||||
}
|
||||
} else {
|
||||
this.addOrUpdateLastManipulation({ type: ManipulationType.Flip, flipX: false, flipY: true });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
</div>);
|
||||
}
|
||||
private getRotateSettings(): JSX.Element {
|
||||
const lastvalue = this.getLastManipulation();
|
||||
let rotatevalue = 0;
|
||||
if (lastvalue && lastvalue.type === ManipulationType.Rotate) {
|
||||
rotatevalue = (lastvalue as IRotateSettings).rotate ? (lastvalue as IRotateSettings).rotate : 0;
|
||||
}
|
||||
return (<div>
|
||||
<div>
|
||||
{this.props.configsettings.rotateButtons.map((value: number, index: number) => {
|
||||
let icon: string = 'CompassNW';
|
||||
if (value !== 0) { icon = 'Rotate'; }
|
||||
|
||||
|
||||
return (<DefaultButton
|
||||
key={'rotate' + index}
|
||||
onClick={() => {
|
||||
if (value === 0) {
|
||||
this.setRotate(value);
|
||||
} else {
|
||||
this.calcRotate(value);
|
||||
}
|
||||
}}
|
||||
className={styles.iconbtn}
|
||||
>
|
||||
<Icon iconName={icon} style={value < 0 ? { transform: 'scaleX(-1)' } : {}} className={styles.imgicon} />
|
||||
<span className={styles.imgtext} >{'' + value}</span></DefaultButton>);
|
||||
})}
|
||||
|
||||
|
||||
</div>
|
||||
<Slider
|
||||
label=''
|
||||
min={-180}
|
||||
max={180}
|
||||
|
||||
value={rotatevalue}
|
||||
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) {
|
||||
correctBugComponent.setState({ value: 0, renderedValue: 0 });
|
||||
}
|
||||
|
||||
}}
|
||||
//originFromZero
|
||||
/>
|
||||
<IconButton
|
||||
key={'resetrotate'}
|
||||
disabled={!(lastvalue && lastvalue.type === ManipulationType.Rotate)}
|
||||
iconProps={{ iconName: 'Undo' }}
|
||||
ariaLabel={strings.CommandBarReset}
|
||||
onClick={() => { this.removeLastManipulation(); }
|
||||
}
|
||||
/>
|
||||
</div >);
|
||||
}
|
||||
|
||||
private getCropSettings(): JSX.Element {
|
||||
let crop: ICropSettings = this.getCropValues();
|
||||
return (<div>
|
||||
<Checkbox
|
||||
label={strings.LockAspect}
|
||||
checked={!isNaN(crop.aspect)}
|
||||
onChange={() => {
|
||||
if (isNaN(crop.aspect)) {
|
||||
this.setCrop(undefined, undefined, undefined, undefined, this.getAspect());
|
||||
} else {
|
||||
this.setCrop(undefined, undefined, undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
/>
|
||||
<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)} />
|
||||
|
||||
</div>);
|
||||
}
|
||||
|
||||
private getResizeSettings(): JSX.Element {
|
||||
let resize: IResizeSettings = this.getResizeValues();
|
||||
return (<div>
|
||||
|
||||
<Checkbox
|
||||
label={strings.LockAspect}
|
||||
checked={!isNaN(resize.aspect)}
|
||||
onChange={() => {
|
||||
if (isNaN(resize.aspect)) {
|
||||
this.setResize(undefined, undefined, this.getAspect());
|
||||
} else {
|
||||
this.setResize(undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
/>
|
||||
<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)} />
|
||||
|
||||
</div>);
|
||||
}
|
||||
private getAspect(): number {
|
||||
return this.canvasRef.width / this.canvasRef.height;
|
||||
}
|
||||
|
||||
private getScaleSettings(): JSX.Element {
|
||||
const lastvalue = this.getLastManipulation();
|
||||
let scalevalue = 1;
|
||||
if (lastvalue && lastvalue.type === ManipulationType.Scale) {
|
||||
scalevalue = (lastvalue as IScaleSettings).scale ? (lastvalue as IScaleSettings).scale : 1;
|
||||
}
|
||||
return (<div>
|
||||
|
||||
<Slider
|
||||
label=''
|
||||
min={0.1}
|
||||
max={5}
|
||||
step={0.1}
|
||||
value={scalevalue}
|
||||
onChange={this.setScale}
|
||||
showValue={true}
|
||||
/>
|
||||
<IconButton
|
||||
key={'resetscale'}
|
||||
disabled={!(lastvalue && lastvalue.type === ManipulationType.Scale)}
|
||||
iconProps={{ iconName: 'Undo' }}
|
||||
title={strings.CommandBarReset}
|
||||
ariaLabel={strings.CommandBarReset}
|
||||
onClick={() => { this.setScale(1); }
|
||||
}
|
||||
/>
|
||||
</div>);
|
||||
}
|
||||
|
||||
private getResizeValues(): IResizeSettings {
|
||||
let state: IImageManipulationSettings = this.getLastManipulation();
|
||||
let values: IResizeSettings = {
|
||||
type: ManipulationType.Resize,
|
||||
height: this.bufferRef.height,
|
||||
width: this.bufferRef.width
|
||||
};
|
||||
if (state && state.type === ManipulationType.Resize) {
|
||||
values = state as IResizeSettings;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private setResize(width: number, height: number, aspect: number): void {
|
||||
let values: IResizeSettings = this.getResizeValues();
|
||||
if (width) {
|
||||
values.width = width;
|
||||
if (aspect) {
|
||||
values.height = values.width / aspect;
|
||||
}
|
||||
}
|
||||
if (height) {
|
||||
values.height = height;
|
||||
if (aspect) {
|
||||
values.width = values.height * aspect;
|
||||
}
|
||||
}
|
||||
values.aspect = aspect;
|
||||
this.addOrUpdateLastManipulation(values);
|
||||
}
|
||||
|
||||
private getCropValues(): ICropSettings {
|
||||
let state: IImageManipulationSettings = this.getLastManipulation();
|
||||
let values: ICropSettings = {
|
||||
type: ManipulationType.Crop,
|
||||
sx: 0,
|
||||
sy: 0,
|
||||
height: this.bufferRef.height,
|
||||
width: this.bufferRef.width
|
||||
};
|
||||
if (state && state.type === ManipulationType.Crop) {
|
||||
values = state as ICropSettings;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private setCrop(sx: number, sy: number, width: number, height: number, aspect: number): void {
|
||||
let values = this.getCropValues();
|
||||
const currentheight: number = this.bufferRef.height;
|
||||
const currentwidth: number = this.bufferRef.width;
|
||||
if (!isNaN(sx) && sx >= 0) {
|
||||
if (sx >= currentwidth) {
|
||||
values.sx = currentwidth - 1;
|
||||
} else {
|
||||
values.sx = sx;
|
||||
}
|
||||
|
||||
// limit max width
|
||||
if ((values.width + values.sx) > currentwidth) {
|
||||
values.width = currentwidth - values.sx;
|
||||
}
|
||||
|
||||
}
|
||||
if (!isNaN(sy) && sy >= 0) {
|
||||
if (sy >= currentheight) {
|
||||
values.sy = currentheight - 1;
|
||||
} else {
|
||||
values.sy = sy;
|
||||
}
|
||||
|
||||
// limit max height
|
||||
if ((values.height + values.sy) > currentheight) {
|
||||
values.height = currentheight - values.sy;
|
||||
}
|
||||
}
|
||||
if (!isNaN(width) && width >= 0) {
|
||||
if ((width + values.sx) > currentwidth) {
|
||||
values.width = currentwidth - values.sx;
|
||||
} else {
|
||||
values.width = width;
|
||||
}
|
||||
}
|
||||
if (!isNaN(height) && height >= 0) {
|
||||
if ((height + values.sy) > currentheight) {
|
||||
values.height = currentheight - values.sy;
|
||||
} else {
|
||||
values.height = height;
|
||||
}
|
||||
}
|
||||
if (isNaN(values.aspect) && !isNaN(aspect)) {
|
||||
//aspect added
|
||||
|
||||
//limit w
|
||||
if ((values.width + values.sx) > currentwidth) {
|
||||
values.width = currentwidth - values.sx;
|
||||
}
|
||||
|
||||
values.height = values.width / aspect;
|
||||
//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;
|
||||
}
|
||||
if (aspect && (!isNaN(sy) || !isNaN(height))) {
|
||||
values.width = values.height * aspect;
|
||||
}
|
||||
this.addOrUpdateLastManipulation(values);
|
||||
}
|
||||
|
||||
private setRotate(value: number): void {
|
||||
this.addOrUpdateLastManipulation({
|
||||
type: ManipulationType.Rotate,
|
||||
rotate: value
|
||||
});
|
||||
|
||||
}
|
||||
private setScale(value: number): void {
|
||||
this.addOrUpdateLastManipulation({
|
||||
type: ManipulationType.Scale,
|
||||
scale: value
|
||||
});
|
||||
}
|
||||
private calcRotate(value: number): void {
|
||||
const lastVal = this.getLastManipulation();
|
||||
let cvalue = 0;
|
||||
if (lastVal && lastVal.type === ManipulationType.Rotate) {
|
||||
cvalue = (lastVal as IRotateSettings).rotate;
|
||||
}
|
||||
cvalue = cvalue + value;
|
||||
if (cvalue < -180) { cvalue = -180; }
|
||||
if (cvalue > 180) { cvalue = 180; }
|
||||
this.addOrUpdateLastManipulation({
|
||||
type: ManipulationType.Rotate,
|
||||
rotate: cvalue
|
||||
});
|
||||
}
|
||||
|
||||
private setCanvasRef(element: HTMLCanvasElement): void {
|
||||
this.canvasRef = element;
|
||||
if (this.canvasRef) {
|
||||
this.canvasCtx = element.getContext('2d');
|
||||
} else {
|
||||
console.log('no canvas context ');
|
||||
}
|
||||
}
|
||||
private setBufferRef(element: HTMLCanvasElement): void {
|
||||
this.bufferRef = element;
|
||||
if (this.bufferRef) {
|
||||
this.bufferCtx = element.getContext('2d');
|
||||
} else {
|
||||
console.log('no buffer context ');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private setManipulateRef(element: HTMLCanvasElement): void {
|
||||
this.manipulateRef = element;
|
||||
if (this.manipulateRef) {
|
||||
this.manipulateCtx = element.getContext('2d');
|
||||
} else {
|
||||
console.log('no manipulation context ');
|
||||
}
|
||||
}
|
||||
|
||||
private getLastManipulation(): IImageManipulationSettings {
|
||||
if (this.props.settings && this.props.settings.length > 0) {
|
||||
return this.props.settings[this.props.settings.length - 1];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
private addOrUpdateLastManipulation(changed: IImageManipulationSettings): void {
|
||||
let state = 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) {
|
||||
this.props.settingschanged(state);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (this.props.settingschanged) {
|
||||
this.props.settingschanged(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeLastManipulation(): void {
|
||||
if (this.props.settings && this.props.settings.length > 0) {
|
||||
let state = clone(this.props.settings);
|
||||
state.splice(state.length - 1, 1);
|
||||
if (this.props.settingschanged) {
|
||||
this.props.settingschanged(clone(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getActionHeaderButton(options: IManipulationTypeDataDetails): JSX.Element {
|
||||
return (<IconButton
|
||||
iconProps={{ iconName: options.iconName }}
|
||||
onRenderIcon={(p, defaultrenderer) => {
|
||||
if (options.svgIcon) {
|
||||
return (<img className={styles.svgbutton} src={options.svgIcon} />);
|
||||
}
|
||||
return defaultrenderer(p);
|
||||
}}
|
||||
title={options.text}
|
||||
ariaLabel={options.text}
|
||||
onClick={() => this.openPanel(options.settingPanelType)}
|
||||
/>);
|
||||
}
|
||||
|
||||
private getCommandBar(): JSX.Element {
|
||||
return (<div className={styles.commandBar}>
|
||||
{this.getActionHeaderButton(manipulationTypeData[ManipulationType.Resize])}
|
||||
{this.getActionHeaderButton(manipulationTypeData[ManipulationType.Crop])}
|
||||
{this.getActionHeaderButton(manipulationTypeData[ManipulationType.Flip])}
|
||||
{this.getActionHeaderButton(manipulationTypeData[ManipulationType.Rotate])}
|
||||
{this.getActionHeaderButton(manipulationTypeData[ManipulationType.Scale])}
|
||||
{this.getActionHeaderButton(manipulationTypeData[ManipulationType.Filter])}
|
||||
|
||||
<IconButton
|
||||
iconProps={{ iconName: 'Undo' }}
|
||||
title={strings.CommandBarUndo}
|
||||
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);
|
||||
redo.push(last);
|
||||
this.setState({ redosettings: redo },
|
||||
() => {
|
||||
if (this.props.settingschanged) {
|
||||
this.props.settingschanged(settings);
|
||||
}
|
||||
});
|
||||
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{ iconName: 'Redo' }}
|
||||
title={strings.CommandBarRedo}
|
||||
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);
|
||||
settings.push(redo);
|
||||
this.setState({ redosettings: redosettings },
|
||||
() => {
|
||||
if (this.props.settingschanged) {
|
||||
this.props.settingschanged(settings);
|
||||
}
|
||||
});
|
||||
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{ iconName: 'Delete' }}
|
||||
title={strings.CommandBarReset}
|
||||
ariaLabel={strings.CommandBarReset}
|
||||
disabled={!this.props.settings || this.props.settings.length < 1}
|
||||
onClick={() => {
|
||||
this.setState({ redosettings: [] },
|
||||
() => {
|
||||
if (this.props.settingschanged) {
|
||||
this.props.settingschanged([]);
|
||||
}
|
||||
});
|
||||
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{ iconName: 'History' }}
|
||||
title={strings.SettingPanelHistory}
|
||||
ariaLabel={strings.SettingPanelHistory}
|
||||
disabled={!this.props.settings || this.props.settings.length < 1}
|
||||
onClick={() => this.openPanel(SettingPanelType.History)}
|
||||
/>
|
||||
<Panel
|
||||
isOpen={this.state.settingPanel != SettingPanelType.Closed}
|
||||
type={PanelType.smallFixedFar}
|
||||
onDismiss={this.closeFilter}
|
||||
headerText={this.getPanelHeader(this.state.settingPanel)}
|
||||
closeButtonAriaLabel={strings.SettingPanelClose}
|
||||
isBlocking={false}
|
||||
onRenderFooterContent={this.onRenderFooterContent}
|
||||
>
|
||||
{this.renderPanelContent()}
|
||||
</Panel>
|
||||
</div>);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
import * as React from 'react';
|
||||
|
||||
const colorFilterIcon: any = require('../../svg/colorFilter.svg');
|
||||
const cropIcon: any = require('../../svg/crop.svg');
|
||||
const flipVerticalIcon: any = require('../../svg/flipVertical.svg');
|
||||
const resizeIcon: any = require('../../svg/resize.svg');
|
||||
import * as strings from 'ImageManipulationStrings';
|
||||
|
||||
export enum ManipulationType {
|
||||
Crop,
|
||||
Scale,
|
||||
Rotate,
|
||||
Flip,
|
||||
Filter,
|
||||
Resize
|
||||
}
|
||||
|
||||
export enum SettingPanelType {
|
||||
Closed = 1,
|
||||
Filter = 2,
|
||||
Flip = 3,
|
||||
Rotate = 4,
|
||||
Scale = 5,
|
||||
Crop = 6,
|
||||
Resize = 7,
|
||||
History = 99
|
||||
}
|
||||
|
||||
export enum FilterType {
|
||||
Grayscale,
|
||||
Sepia,
|
||||
/*
|
||||
Blur,
|
||||
Emboss,
|
||||
Sepia2,
|
||||
Invert,
|
||||
Sharpen,
|
||||
RemoteWhite,
|
||||
Brightness,
|
||||
Noise,
|
||||
Pixelate,
|
||||
ColorOverLay*/
|
||||
}
|
||||
|
||||
|
||||
export interface IManipulationBase {
|
||||
type: ManipulationType;
|
||||
}
|
||||
|
||||
export interface ICrop {
|
||||
sx: number;
|
||||
sy: number;
|
||||
width: number;
|
||||
height: number;
|
||||
aspect?: number;
|
||||
}
|
||||
|
||||
export interface IResize {
|
||||
width: number;
|
||||
height: number;
|
||||
aspect?: number;
|
||||
}
|
||||
|
||||
export interface ICropSettings extends IManipulationBase, ICrop {
|
||||
|
||||
}
|
||||
export interface IFlipSettings extends IManipulationBase {
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
}
|
||||
export interface IScaleSettings extends IManipulationBase {
|
||||
scale: number;
|
||||
}
|
||||
export interface IRotateSettings extends IManipulationBase {
|
||||
rotate: number;
|
||||
}
|
||||
|
||||
export interface IFilterSettings extends IManipulationBase {
|
||||
filterType: FilterType;
|
||||
nvalue?: number;
|
||||
svalue?: string;
|
||||
}
|
||||
export interface IResizeSettings extends IManipulationBase, IResize {
|
||||
|
||||
}
|
||||
|
||||
export type IImageManipulationSettings = IFilterSettings | IRotateSettings | IScaleSettings | IFlipSettings | ICropSettings | IResizeSettings;
|
||||
|
||||
|
||||
export const filterTypeData: IFilterTypeData = {
|
||||
0: strings.FilterTypeGrayscale,
|
||||
1: strings.FilterTypeSepia
|
||||
};
|
||||
|
||||
export interface IFilterTypeData {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface IManipulationTypeDataBase {
|
||||
text: string;
|
||||
iconName?: string;
|
||||
svgIcon?: any;
|
||||
settingPanelType: SettingPanelType;
|
||||
}
|
||||
|
||||
export interface IManipulationTypeData {
|
||||
[key: string]: IManipulationTypeDataDetails;
|
||||
}
|
||||
|
||||
export interface IManipulationTypeDataDetails extends IManipulationTypeDataBase {
|
||||
toHTML: (item: IImageManipulationSettings) => JSX.Element;
|
||||
}
|
||||
|
||||
export const manipulationTypeData: IManipulationTypeData = {
|
||||
0: {
|
||||
text: strings.ManipulationTypeCrop,
|
||||
svgIcon: cropIcon,
|
||||
toHTML: (item: ICropSettings) => {
|
||||
return (<span></span>);
|
||||
//return (<span>{`X:${item.sx} Y:${item.sy}`}</span>);
|
||||
},
|
||||
settingPanelType: SettingPanelType.Crop
|
||||
},
|
||||
1: {
|
||||
text: strings.ManipulationTypeScale,
|
||||
iconName: 'Zoom',
|
||||
toHTML: (item: IScaleSettings) => { return (<span></span>); },
|
||||
settingPanelType: SettingPanelType.Scale
|
||||
},
|
||||
2: {
|
||||
text: strings.ManipulationTypeRotate,
|
||||
iconName: 'Rotate',
|
||||
toHTML: (item: IRotateSettings) => { return (<span></span>); },
|
||||
settingPanelType: SettingPanelType.Rotate
|
||||
},
|
||||
3: {
|
||||
text: strings.ManipulationTypeFlip,
|
||||
svgIcon: flipVerticalIcon,
|
||||
toHTML: (item: IFlipSettings) => { return (<span></span>); },
|
||||
settingPanelType: SettingPanelType.Flip
|
||||
},
|
||||
4: {
|
||||
text: strings.ManipulationTypeFilter,
|
||||
svgIcon: colorFilterIcon,
|
||||
toHTML: (item: IFilterSettings) => {
|
||||
return (<span>{filterTypeData[item.filterType]}</span>);
|
||||
},
|
||||
settingPanelType: SettingPanelType.Filter
|
||||
},
|
||||
5: {
|
||||
text: strings.ManipulationTypeResize,
|
||||
iconName: 'SizeLegacy',
|
||||
svgIcon: resizeIcon,
|
||||
toHTML: (item: IResizeSettings) => { return (<span></span>); },
|
||||
settingPanelType: SettingPanelType.Resize
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
export enum nodePoition {
|
||||
NW,
|
||||
N,
|
||||
NE,
|
||||
E,
|
||||
SE,
|
||||
S,
|
||||
SW,
|
||||
W
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
$drag-handle-width: 10px !default;
|
||||
$drag-handle-height: 10px !default;
|
||||
$drag-bar-size: 6px !default;
|
||||
|
||||
// Query to kick us into "mobile" mode with larger drag handles/bars.
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer
|
||||
$mobile-media-query: '(pointer: coarse)' !default;
|
||||
|
||||
// Mobile handle/bar sizes. Override as above.
|
||||
$drag-handle-mobile-width: 24px !default;
|
||||
$drag-handle-mobile-height: 24px !default;
|
||||
|
||||
// Handle color/border.
|
||||
$drag-handle-background-colour: rgba(0, 0, 0, 0.2) !default;
|
||||
$drag-handle-border: 1px solid rgba(255, 255, 255, 0.7) !default;
|
||||
|
||||
.ImgGridShadowOverlay{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ImgGridVisible {
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
right: 0;
|
||||
bottom:0;
|
||||
cursor: crosshair;
|
||||
touch-action: manipulation;
|
||||
|
||||
.CropContrainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translate3d(0, 0, 0);
|
||||
box-sizing: border-box;
|
||||
cursor: move;
|
||||
box-shadow: 0 0 0 9999em rgba(0, 0, 0, 0.5);
|
||||
touch-action: manipulation;
|
||||
border: 1px dashed white;
|
||||
|
||||
.ruleOfThirdsVT,
|
||||
.ruleOfThirdsHZ{
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
.ruleOfThirdsVT{
|
||||
&::before,
|
||||
&::after {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 33.3333%;
|
||||
left: calc(100% / 3);
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: 66.6666%;
|
||||
left: calc(100% / 3 * 2);
|
||||
}
|
||||
}
|
||||
.ruleOfThirdsHZ{
|
||||
&::before,
|
||||
&::after {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: 33.3333%;
|
||||
top: calc(100% / 3);
|
||||
}
|
||||
|
||||
&::after {
|
||||
top: 66.6666%;
|
||||
top: calc(100% / 3 * 2);
|
||||
}
|
||||
}
|
||||
|
||||
.dragHandle{
|
||||
position: absolute;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
width: $drag-handle-width;
|
||||
height: $drag-handle-height;
|
||||
background-color: $drag-handle-background-colour;
|
||||
border: $drag-handle-border;
|
||||
box-sizing: border-box;
|
||||
|
||||
// This stops the borders disappearing when keyboard
|
||||
// nudging.
|
||||
outline: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.nw{
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin-top: -(ceil($drag-handle-height / 2));
|
||||
margin-left: -(ceil($drag-handle-width / 2));
|
||||
cursor: nwse-resize;
|
||||
|
||||
&::after {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
.n {
|
||||
top: 0;
|
||||
left: 50%;
|
||||
margin-top: -(ceil($drag-handle-height / 2));
|
||||
margin-left: -(ceil($drag-handle-width / 2));
|
||||
cursor: ns-resize;
|
||||
|
||||
&::after {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.ne {
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin-top: -(ceil($drag-handle-height / 2));
|
||||
margin-right: -(ceil($drag-handle-width / 2));
|
||||
cursor: nesw-resize;
|
||||
|
||||
&::after {
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.e {
|
||||
top: 50%;
|
||||
right: 0;
|
||||
margin-top: -(ceil($drag-handle-height / 2));
|
||||
margin-right: -(ceil($drag-handle-width / 2));
|
||||
cursor: ew-resize;
|
||||
|
||||
&::after {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.se {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin-bottom: -(ceil($drag-handle-height / 2));
|
||||
margin-right: -(ceil($drag-handle-width / 2));
|
||||
cursor: nwse-resize;
|
||||
|
||||
&::after {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.s {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
margin-bottom: -(ceil($drag-handle-height / 2));
|
||||
margin-left: -(ceil($drag-handle-width / 2));
|
||||
cursor: ns-resize;
|
||||
|
||||
&::after {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
.sw {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin-bottom: -(ceil($drag-handle-height / 2));
|
||||
margin-left: -(ceil($drag-handle-width / 2));
|
||||
cursor: nesw-resize;
|
||||
|
||||
&::after {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
.w {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
margin-top: -(ceil($drag-handle-height / 2));
|
||||
margin-left: -(ceil($drag-handle-width / 2));
|
||||
cursor: ew-resize;
|
||||
|
||||
&::after {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
.dragBar_n {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $drag-bar-size;
|
||||
margin-top: -($drag-bar-size / 2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.dragBar_e {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: $drag-bar-size;
|
||||
height: 100%;
|
||||
margin-right: -($drag-bar-size / 2);
|
||||
}
|
||||
.dragBar_s {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: $drag-bar-size;
|
||||
margin-bottom: -($drag-bar-size / 2);
|
||||
}
|
||||
.dragBar_w {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: $drag-bar-size;
|
||||
height: 100%;
|
||||
margin-left: -($drag-bar-size / 2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,492 @@
|
|||
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';
|
||||
|
||||
import { nodePoition } from './Enums';
|
||||
import styles from './ImageCrop.module.scss';
|
||||
import { ICropData, IMousePosition } from './Interfaces';
|
||||
|
||||
function clamp(num, min, max) {
|
||||
return Math.min(Math.max(num, min), max);
|
||||
}
|
||||
|
||||
|
||||
export interface IImageCropProps {
|
||||
crop: ICrop;
|
||||
|
||||
sourceHeight: number;
|
||||
sourceWidth: number;
|
||||
showRuler?: boolean;
|
||||
onDragStart?: (e: MouseEvent) => void;
|
||||
onComplete?: (crop: ICrop) => void;
|
||||
onChange?: (crop: ICrop) => void;
|
||||
onDragEnd?: (e) => void;
|
||||
}
|
||||
|
||||
export interface IImageCropState {
|
||||
cropIsActive: boolean;
|
||||
newCropIsBeingDrawn: boolean;
|
||||
reloadtimestamp: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Feature detection
|
||||
// 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> {
|
||||
|
||||
private controlRef: HTMLDivElement = null;
|
||||
|
||||
private dragStarted: boolean = false;
|
||||
private mouseDownOnCrop: boolean = false;
|
||||
private evData: ICropData;
|
||||
|
||||
constructor(props: IImageCropProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
cropIsActive: false,
|
||||
newCropIsBeingDrawn: false,
|
||||
reloadtimestamp: ''
|
||||
};
|
||||
this.onDocMouseTouchMove = this.onDocMouseTouchMove.bind(this);
|
||||
this.onDocMouseTouchEnd = this.onDocMouseTouchEnd.bind(this);
|
||||
this.onCropMouseTouchDown = this.onCropMouseTouchDown.bind(this);
|
||||
this.setControlRef = this.setControlRef.bind(this);
|
||||
this.onMouseTouchDown = this.onMouseTouchDown.bind(this);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
const { crop, sourceHeight, sourceWidth } = 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
|
||||
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;
|
||||
|
||||
return (
|
||||
<div ref={this.setControlRef}
|
||||
className={styles.ImgGridShadowOverlay}
|
||||
onMouseMove={this.onDocMouseTouchMove}
|
||||
onTouchMove={this.onDocMouseTouchMove}
|
||||
onMouseUp={this.onDocMouseTouchEnd}
|
||||
onTouchCancel={this.onDocMouseTouchEnd}
|
||||
onTouchEnd={this.onDocMouseTouchEnd}
|
||||
onMouseDown={this.onMouseTouchDown}
|
||||
onTouchStart={this.onMouseTouchDown}
|
||||
>
|
||||
<div className={styles.ImgGridVisible}
|
||||
style={
|
||||
{
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
}
|
||||
}>
|
||||
{cropSelection}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private createSelectionGrid(): JSX.Element {
|
||||
const { showRuler } = this.props;
|
||||
const style = this.getCropStyle();
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
className={styles.CropContrainer}
|
||||
onMouseDown={this.onCropMouseTouchDown}
|
||||
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} />
|
||||
<div className={styles.dragBar_w} data-ord={nodePoition.W} />
|
||||
|
||||
<div className={[styles.dragHandle, styles.nw].join(' ')} data-ord={nodePoition.NW} />
|
||||
<div className={[styles.dragHandle, styles.n].join(' ')} data-ord={nodePoition.N} />
|
||||
<div className={[styles.dragHandle, styles.ne].join(' ')} data-ord={nodePoition.NE} />
|
||||
<div className={[styles.dragHandle, styles.e].join(' ')} data-ord={nodePoition.E} />
|
||||
<div className={[styles.dragHandle, styles.se].join(' ')} data-ord={nodePoition.SE} />
|
||||
<div className={[styles.dragHandle, styles.s].join(' ')} data-ord={nodePoition.S} />
|
||||
<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} />
|
||||
<div className={styles.ruleOfThirdsVT} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private makeNewCrop(): ICrop {
|
||||
const crop: ICrop = { ...{ sx: 0, sy: 0, height: 0, width: 0 }, ...this.props.crop };
|
||||
return crop;
|
||||
}
|
||||
|
||||
private getCropStyle() {
|
||||
const crop = this.makeNewCrop();
|
||||
const unit = 'px';
|
||||
return {
|
||||
top: `${crop.sy}${unit}`,
|
||||
left: `${crop.sx}${unit}`,
|
||||
width: `${crop.width}${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
|
||||
});
|
||||
}
|
||||
|
||||
private onDocMouseTouchMove(e: React.MouseEvent<HTMLDivElement> | any): void {
|
||||
const { crop, onChange, onDragStart } = this.props;
|
||||
if (!this.mouseDownOnCrop) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
if (!this.dragStarted) {
|
||||
this.dragStarted = true;
|
||||
if (onDragStart) {
|
||||
onDragStart(e as any);
|
||||
}
|
||||
}
|
||||
const pos = this.getCurrentPosition(e);
|
||||
|
||||
|
||||
|
||||
|
||||
const clientPos = this.getClientPos(e);
|
||||
/*
|
||||
if (this.evData.isResize && this.props.aspect && this.evData.cropOffset) {
|
||||
clientPos.y = this.straightenYPath(clientPos.x);
|
||||
}
|
||||
*/
|
||||
|
||||
this.evData.xDiff = clientPos.x - this.evData.clientStartX;
|
||||
this.evData.yDiff = clientPos.y - this.evData.clientStartY;
|
||||
|
||||
let nextCrop;
|
||||
|
||||
if (this.evData.isResize) {
|
||||
nextCrop = this.resizeCrop();
|
||||
} else {
|
||||
nextCrop = this.dragCrop();
|
||||
}
|
||||
|
||||
if (nextCrop !== crop) {
|
||||
if (onChange) {
|
||||
onChange(nextCrop);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private dragCrop() {
|
||||
|
||||
const { evData } = this;
|
||||
let 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);
|
||||
nextCrop.sy = clamp(evData.cropStartY + evData.yDiff, 0, height - nextCrop.height);
|
||||
|
||||
return nextCrop;
|
||||
}
|
||||
|
||||
private resizeCrop() {
|
||||
const { evData } = this;
|
||||
let nextCrop = this.makeNewCrop();
|
||||
const { pos } = evData;
|
||||
|
||||
if (evData.xInversed) {
|
||||
evData.xDiff -= evData.cropStartWidth * 2;
|
||||
|
||||
}
|
||||
if (evData.yInversed) {
|
||||
evData.yDiff -= evData.cropStartHeight * 2;
|
||||
|
||||
}
|
||||
const newSize = this.getNewSize();
|
||||
|
||||
|
||||
let newX = evData.cropStartX;
|
||||
let newY = evData.cropStartY;
|
||||
|
||||
if (evData.xInversed) {
|
||||
newX = nextCrop.sx + (nextCrop.width - newSize.width);
|
||||
}
|
||||
|
||||
if (evData.yInversed) {
|
||||
newY = nextCrop.sy + (nextCrop.height - newSize.height);
|
||||
}
|
||||
|
||||
const containedCrop: ICrop = {
|
||||
sx: newX,
|
||||
sy: newY,
|
||||
width: newSize.width,
|
||||
height: newSize.height,
|
||||
aspect: this.props.crop.aspect
|
||||
};
|
||||
|
||||
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;
|
||||
nextCrop.height = containedCrop.height;
|
||||
} else if (pos === nodePoition.E || pos === nodePoition.W) {
|
||||
nextCrop.sx = containedCrop.sx;
|
||||
nextCrop.width = containedCrop.width;
|
||||
} else if (pos === nodePoition.N || pos === nodePoition.S) {
|
||||
nextCrop.sy = containedCrop.sy;
|
||||
nextCrop.height = containedCrop.height;
|
||||
}
|
||||
return nextCrop;
|
||||
}
|
||||
|
||||
|
||||
private getNewSize(): { width: number, height: number } {
|
||||
const { crop, sourceWidth, sourceHeight } = this.props;
|
||||
const { evData } = this;
|
||||
|
||||
let newWidth = evData.cropStartWidth + evData.xDiff;
|
||||
|
||||
if (evData.xInversed) {
|
||||
newWidth = Math.abs(newWidth);
|
||||
}
|
||||
|
||||
newWidth = clamp(newWidth, 0, sourceWidth);
|
||||
|
||||
// New height.
|
||||
let newHeight;
|
||||
|
||||
if (crop.aspect) {
|
||||
newHeight = newWidth / crop.aspect;
|
||||
} else {
|
||||
newHeight = evData.cropStartHeight + evData.yDiff;
|
||||
}
|
||||
|
||||
if (evData.yInversed) {
|
||||
// Cap if polarity is inversed and the height fills the y space.
|
||||
newHeight = Math.min(Math.abs(newHeight), evData.cropStartY);
|
||||
}
|
||||
|
||||
newHeight = clamp(newHeight, 0, sourceHeight);
|
||||
|
||||
if (crop.aspect) {
|
||||
newWidth = clamp(newHeight * crop.aspect, 0, sourceWidth);
|
||||
}
|
||||
|
||||
return {
|
||||
width: newWidth,
|
||||
height: newHeight,
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
if (onDragEnd) {
|
||||
onDragEnd(e);
|
||||
}
|
||||
if (onComplete) {
|
||||
onComplete(crop);
|
||||
}
|
||||
this.setState({ cropIsActive: false, newCropIsBeingDrawn: false });
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private onCropMouseTouchDown(e: MouseEvent | any): void {
|
||||
const { crop } = this.props;
|
||||
|
||||
e.preventDefault(); // Stop drag selection.
|
||||
const mousepos = this.getClientPos(e);
|
||||
const { ord } = e.target.dataset;
|
||||
|
||||
let xInversed: boolean = false;
|
||||
let yInversed: boolean = false;
|
||||
let pos: nodePoition = undefined;
|
||||
if (ord && !isNaN(+ord)) {
|
||||
pos = +ord;
|
||||
xInversed = pos === nodePoition.NW || pos === nodePoition.W || pos === nodePoition.SW;
|
||||
yInversed = pos === nodePoition.NW || pos === nodePoition.N || pos === nodePoition.NE;
|
||||
}
|
||||
|
||||
this.evData = {
|
||||
clientStartX: mousepos.x,
|
||||
clientStartY: mousepos.y,
|
||||
cropStartWidth: crop.width,
|
||||
cropStartHeight: crop.height,
|
||||
cropStartX: xInversed ? crop.sx + crop.width : crop.sx,
|
||||
cropStartY: yInversed ? crop.sy + crop.height : crop.sy,
|
||||
xInversed: xInversed,
|
||||
yInversed: yInversed,
|
||||
isResize: (ord && !isNaN(ord)),
|
||||
pos: pos,
|
||||
xDiff: 0,
|
||||
yDiff: 0
|
||||
};
|
||||
|
||||
this.mouseDownOnCrop = true;
|
||||
this.setState({ cropIsActive: true });
|
||||
}
|
||||
|
||||
|
||||
private setControlRef(element: HTMLDivElement): void {
|
||||
this.controlRef = element;
|
||||
}
|
||||
|
||||
private getClientPos(e: MouseEvent | any): IMousePosition {
|
||||
let pageX;
|
||||
let pageY;
|
||||
|
||||
if (e.touches) {
|
||||
[{ pageX, pageY }] = e.touches;
|
||||
} else {
|
||||
({ pageX, pageY } = e);
|
||||
}
|
||||
|
||||
return {
|
||||
x: pageX,
|
||||
y: pageY,
|
||||
};
|
||||
}
|
||||
|
||||
private isValid(crop: ICrop) {
|
||||
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;
|
||||
}
|
||||
|
||||
private onMouseTouchDown(e: MouseEvent | any): void {
|
||||
const { crop, onChange } = this.props;
|
||||
e.preventDefault(); // Stop drag selection.
|
||||
const mousepos = 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
|
||||
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
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const nextCrop: ICrop = {
|
||||
sx: startx,
|
||||
sy: starty,
|
||||
width: 0,
|
||||
height: 0,
|
||||
aspect: crop.aspect
|
||||
};
|
||||
|
||||
this.evData = {
|
||||
clientStartX: mousepos.x,
|
||||
clientStartY: mousepos.y,
|
||||
cropStartWidth: nextCrop.width,
|
||||
cropStartHeight: nextCrop.height,
|
||||
cropStartX: nextCrop.sx,
|
||||
cropStartY: nextCrop.sy,
|
||||
xInversed: false,
|
||||
yInversed: false,
|
||||
isResize: true,
|
||||
xDiff: 0,
|
||||
yDiff: 0,
|
||||
pos: nodePoition.NW,
|
||||
};
|
||||
|
||||
this.mouseDownOnCrop = true;
|
||||
|
||||
onChange(nextCrop);
|
||||
|
||||
this.setState({ cropIsActive: true, newCropIsBeingDrawn: true });
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
.ImgGridShadowOverlay{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
//background-color: rgba(0,0,0,0.4);
|
||||
overflow: hidden;
|
||||
}
|
||||
.ImgGridVisible {
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
right: 0;
|
||||
bottom:0;
|
||||
// box-sizing: border-box;
|
||||
// box-shadow:0 0 0 9999em;
|
||||
box-shadow: 0 0 0 9999em rgba(0, 0, 0, 0.5);
|
||||
|
||||
.ImgGridTabel {
|
||||
border:2px solid white;
|
||||
display: table;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.ImgGridRow {
|
||||
display: table-row;
|
||||
height: 33.33%;
|
||||
|
||||
.ImgGridCell {
|
||||
display: table-cell;
|
||||
width: 33.33%;
|
||||
}
|
||||
.ImgLeftTop{
|
||||
border-right: 1px solid white;
|
||||
border-bottom: 1px solid white;
|
||||
.bubble{
|
||||
cursor:nwse-resize;
|
||||
left: 0;
|
||||
top:0;
|
||||
}
|
||||
}
|
||||
.ImgRightTop{
|
||||
border-left: 1px solid white;
|
||||
border-bottom: 1px solid white;
|
||||
.bubble{
|
||||
cursor:nesw-resize;
|
||||
right: 0;
|
||||
top:0;
|
||||
}
|
||||
}
|
||||
.ImgLeftBottom {
|
||||
border-right: 1px solid white;
|
||||
border-top: 1px solid white;
|
||||
.bubble{
|
||||
cursor:nesw-resize;
|
||||
left: 0;
|
||||
bottom:0;
|
||||
}
|
||||
}
|
||||
.ImgRightBottom {
|
||||
border-left: 1px solid white;
|
||||
border-top: 1px solid white;
|
||||
.bubble{
|
||||
cursor:nwse-resize;
|
||||
right: 0;
|
||||
bottom:0;
|
||||
}
|
||||
}
|
||||
.ImgCenterTop {
|
||||
border-bottom: 1px dashed white;
|
||||
.bubble{
|
||||
cursor:ns-resize;
|
||||
left: 50%;
|
||||
top:0;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
.ImgCenterBottom{
|
||||
border-top: 1px dashed white;
|
||||
.bubble{
|
||||
cursor:ns-resize;
|
||||
left: 50%;
|
||||
bottom:0;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
.ImgLeftCenter{
|
||||
border-right: 1px dashed white;
|
||||
.bubble{
|
||||
cursor:ew-resize;
|
||||
left: 0;
|
||||
top:50%;
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
.ImgRightCenter{
|
||||
border-left: 1px dashed white;
|
||||
.bubble{
|
||||
cursor:ew-resize;
|
||||
right: 0;
|
||||
top:50%;
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bubble{
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-color: white;
|
||||
//border-radius: 50%;
|
||||
border:1px solid black;
|
||||
display: block;
|
||||
position:absolute;
|
||||
// margin: -8px 0 0 -8px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
.ImgGridRowTop {
|
||||
.ImgGridCellLeft {}
|
||||
.ImgGridCellCenter {}
|
||||
.ImgGridCellRight {}
|
||||
}
|
||||
.ImgGridRowMiddle {}
|
||||
.ImgGridRowBottom {}
|
||||
*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
import { Overlay } from 'office-ui-fabric-react';
|
||||
import * as React from 'react';
|
||||
import { IResize } from '../ImageManipulation.types';
|
||||
|
||||
import { nodePoition } from './Enums';
|
||||
import styles from './ImageGrid.module.scss';
|
||||
import { IMousePosition } from './Interfaces';
|
||||
|
||||
export interface IImageGridProps {
|
||||
width: number;
|
||||
height: number;
|
||||
aspect?: number;
|
||||
onChange: (size: IResize) => void;
|
||||
onComplete?: (size: IResize) => void;
|
||||
onDragEnd?: (e: MouseEvent | any) => void;
|
||||
onDragStart?: (e: MouseEvent | any) => void;
|
||||
}
|
||||
|
||||
export interface IImageGridState { }
|
||||
|
||||
export interface IResizeData {
|
||||
pos: nodePoition;
|
||||
width: number;
|
||||
height: number;
|
||||
xInverse: boolean;
|
||||
yInverse: boolean;
|
||||
clientStartX: number;
|
||||
clientStartY: number;
|
||||
}
|
||||
|
||||
export default class ImageGrid extends React.Component<IImageGridProps, IImageGridState> {
|
||||
|
||||
private evData: IResizeData = null;
|
||||
private dragStarted: boolean = false;
|
||||
constructor(props: IImageGridProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
|
||||
this.onStartResizing = this.onStartResizing.bind(this);
|
||||
this.onDocMouseTouchMove = this.onDocMouseTouchMove.bind(this);
|
||||
this.onDocMouseTouchEnd = this.onDocMouseTouchEnd.bind(this);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
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('mouseup', this.onDocMouseTouchEnd);
|
||||
window.document.removeEventListener('touchend', this.onDocMouseTouchEnd);
|
||||
window.document.removeEventListener('touchcancel', this.onDocMouseTouchEnd);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IImageGridProps> {
|
||||
return (
|
||||
<div className={styles.ImgGridShadowOverlay}>
|
||||
<div className={styles.ImgGridVisible}
|
||||
style={
|
||||
{
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
}
|
||||
}>
|
||||
<div className={styles.ImgGridTabel}>
|
||||
|
||||
<div className={styles.ImgGridRow}>
|
||||
<div className={styles.ImgLeftTop + ' ' + styles.ImgGridCell}>
|
||||
<div className={styles.bubble}
|
||||
onMouseDown={this.onStartResizing}
|
||||
onTouchStart={this.onStartResizing}
|
||||
data-ord={nodePoition.NW} />
|
||||
</div>
|
||||
<div className={styles.ImgCenterTop + ' ' + styles.ImgGridCell}>
|
||||
<div className={styles.bubble} data-ord={nodePoition.N} onMouseDown={this.onStartResizing} />
|
||||
</div>
|
||||
<div className={styles.ImgRightTop + ' ' + styles.ImgGridCell}>
|
||||
<div className={styles.bubble} data-ord={nodePoition.NE} onMouseDown={this.onStartResizing} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.ImgGridRow}>
|
||||
<div className={styles.ImgLeftCenter + ' ' + styles.ImgGridCell}>
|
||||
<div className={styles.bubble} data-ord={nodePoition.W} onMouseDown={this.onStartResizing} />
|
||||
</div>
|
||||
<div className={styles.ImgGridCell}></div>
|
||||
<div className={styles.ImgRightCenter + ' ' + styles.ImgGridCell}>
|
||||
<div className={styles.bubble} data-ord={nodePoition.E} onMouseDown={this.onStartResizing} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.ImgGridRow}>
|
||||
<div className={styles.ImgLeftBottom + ' ' + styles.ImgGridCell}>
|
||||
<div className={styles.bubble} data-ord={nodePoition.SW} onMouseDown={this.onStartResizing} />
|
||||
</div>
|
||||
<div className={styles.ImgCenterBottom + ' ' + styles.ImgGridCell}>
|
||||
<div className={styles.bubble} data-ord={nodePoition.S} onMouseDown={this.onStartResizing} />
|
||||
</div>
|
||||
<div className={styles.ImgRightBottom + ' ' + styles.ImgGridCell}>
|
||||
<div className={styles.bubble} data-ord={nodePoition.SE} onMouseDown={this.onStartResizing} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onStartResizing(e: MouseEvent | any): void {
|
||||
const mousePos = this.getClientPos(e);
|
||||
let xInversed: boolean = false;
|
||||
let yInversed: boolean = false;
|
||||
const { ord } = e.target.dataset;
|
||||
let pos: nodePoition = undefined;
|
||||
if (ord && !isNaN(+ord)) {
|
||||
pos = +ord;
|
||||
xInversed = pos === nodePoition.NW || pos === nodePoition.W || pos === nodePoition.SW;
|
||||
yInversed = pos === nodePoition.NW || pos === nodePoition.N || pos === nodePoition.NE;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
this.dragStarted = true;
|
||||
if (this.props.onDragStart) {
|
||||
this.props.onDragStart(e);
|
||||
}
|
||||
this.evData = {
|
||||
clientStartX: mousePos.x,
|
||||
clientStartY: mousePos.y,
|
||||
xInverse: xInversed,
|
||||
yInverse: yInversed,
|
||||
pos: pos,
|
||||
width: this.props.width,
|
||||
height: this.props.height
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private onDocMouseTouchMove(e: React.MouseEvent<HTMLDivElement> | any): void {
|
||||
const { aspect ,onChange } = this.props;
|
||||
if (!this.dragStarted) {
|
||||
return;
|
||||
}
|
||||
if (!this.evData) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
const mousePos = 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) {
|
||||
xDiff = mousePos.x - this.evData.clientStartX;
|
||||
} 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) {
|
||||
yDiff = this.evData.clientStartY - mousePos.y;
|
||||
} 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 = {
|
||||
width: this.evData.width + xDiff,
|
||||
height: this.evData.height + yDiff
|
||||
};
|
||||
if(aspect) {
|
||||
if(this.evData.pos !== nodePoition.N && this.evData.pos !== nodePoition.S) {
|
||||
nextsize.height = nextsize.width / aspect;
|
||||
} else {
|
||||
nextsize.width = nextsize.height * aspect;
|
||||
}
|
||||
}
|
||||
if (onChange) {
|
||||
onChange(nextsize);
|
||||
}
|
||||
}
|
||||
|
||||
private onDocMouseTouchEnd(e: MouseEvent | any): void {
|
||||
const { width, height, onDragEnd, onComplete } = this.props;
|
||||
if (this.dragStarted) {
|
||||
this.dragStarted = false;
|
||||
if (onDragEnd) {
|
||||
onDragEnd(e);
|
||||
}
|
||||
this.evData = undefined;
|
||||
if (onComplete) {
|
||||
onComplete({ width: width, height: height });
|
||||
this.setState({ cropIsActive: false, newCropIsBeingDrawn: false });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private getClientPos(e: MouseEvent | any): IMousePosition {
|
||||
let pageX;
|
||||
let pageY;
|
||||
|
||||
if (e.touches) {
|
||||
[{ pageX, pageY }] = e.touches;
|
||||
} else {
|
||||
({ pageX, pageY } = e);
|
||||
}
|
||||
|
||||
return {
|
||||
x: pageX,
|
||||
y: pageY,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { nodePoition } from "./Enums";
|
||||
|
||||
export interface IMousePosition {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export interface ICropData {
|
||||
clientStartX: number;
|
||||
clientStartY: number;
|
||||
cropStartWidth: number;
|
||||
cropStartHeight: number;
|
||||
cropStartX: number;
|
||||
cropStartY: number;
|
||||
xInversed: boolean;
|
||||
yInversed: boolean;
|
||||
isResize: boolean;
|
||||
pos?: nodePoition;
|
||||
xDiff: number;
|
||||
yDiff: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
$ms-color-themePrimary: '[theme:themePrimary, default:#0078d7]';
|
||||
$ms-color-neutralLight: '[theme:neutralLight, default:#eaeaea]';
|
||||
$ms-color-neutralLighter: '[theme:neutralLighter, default:#f4f4f4]';
|
||||
$ms-color-neutralTertiary: '[theme:neutralTertiary, default:#a6a6a6]';
|
||||
$ms-color-white: '[theme:white, default:#ffffff]';
|
||||
|
||||
|
||||
.propertyFieldOrder {
|
||||
margin-bottom: 2px;
|
||||
|
||||
ul {
|
||||
padding: 0.5px;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
|
||||
li {
|
||||
color: $ms-color-neutralTertiary;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
background-color: $ms-color-white;
|
||||
border: 0.5px solid;
|
||||
border-color: $ms-color-neutralLight;
|
||||
outline: 0.5px solid;
|
||||
outline-color: $ms-color-neutralLight;
|
||||
|
||||
.enabled & :hover {
|
||||
background-color: $ms-color-neutralLighter;
|
||||
}
|
||||
|
||||
& > div {
|
||||
padding: 3px 6px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.itemBox {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.dragEnter {
|
||||
background-color: $ms-color-neutralLight;
|
||||
border-top: 2px dashed;
|
||||
border-top-color: $ms-color-themePrimary;
|
||||
}
|
||||
|
||||
.dragLast {
|
||||
background-color: $ms-color-neutralLight;
|
||||
border-bottom: 2px dashed;
|
||||
border-bottom-color: $ms-color-themePrimary;
|
||||
}
|
||||
|
||||
.lastBox {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
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 * as React from 'react';
|
||||
import styles from './ItemOrder.module.scss';
|
||||
|
||||
export interface IItemOrderProps {
|
||||
label: string;
|
||||
disabled: boolean;
|
||||
items: Array<any>;
|
||||
textProperty?: string;
|
||||
moveUpIconName: string;
|
||||
moveDownIconName: string;
|
||||
disableDragAndDrop: boolean;
|
||||
removeArrows: boolean;
|
||||
maxHeight?: number;
|
||||
valueChanged: (newValue: Array<any>) => void;
|
||||
onRenderItem?: (item: any, index: number) => JSX.Element;
|
||||
}
|
||||
|
||||
export interface IItemOrderState {
|
||||
items: Array<any>;
|
||||
}
|
||||
|
||||
|
||||
export default class ItemOrder extends React.Component<IItemOrderProps, IItemOrderState> {
|
||||
|
||||
private _draggedItem: any;
|
||||
private _selection: ISelection;
|
||||
private _ddHelper: DragDropHelper;
|
||||
private _refs: Array<HTMLElement>;
|
||||
private _ddSubs: Array<any>;
|
||||
private _lastBox: HTMLElement;
|
||||
|
||||
|
||||
constructor(props: IItemOrderProps) {
|
||||
super(props);
|
||||
|
||||
this._selection = null;
|
||||
this._ddHelper = new DragDropHelper({
|
||||
selection: this._selection
|
||||
});
|
||||
|
||||
this._refs = new Array<HTMLElement>();
|
||||
this._ddSubs = new Array<any>();
|
||||
|
||||
this._draggedItem = null;
|
||||
|
||||
this.state = {
|
||||
items: []
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public render(): JSX.Element {
|
||||
const {
|
||||
items
|
||||
} = this.state;
|
||||
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}>
|
||||
{
|
||||
(items && items.length > 0) && (
|
||||
items.map((value: any, index: number) => {
|
||||
return (
|
||||
<li
|
||||
ref={this.registerRef}
|
||||
key={index}
|
||||
draggable={!this.props.disableDragAndDrop && !this.props.disabled}
|
||||
style={{ cursor: !this.props.disableDragAndDrop && !this.props.disabled ? 'pointer' : 'default' }}
|
||||
>{this.renderItem(value, index)}</li>
|
||||
);
|
||||
})
|
||||
)
|
||||
}
|
||||
{
|
||||
(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 || []
|
||||
});
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.setupSubscriptions();
|
||||
}
|
||||
|
||||
public componentWillUpdate(nextProps: IItemOrderProps): void {
|
||||
// Check if the provided items are still the same
|
||||
if (!isEqual(nextProps.items, this.state.items)) {
|
||||
this.setState({
|
||||
items: this.props.items || []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(): void {
|
||||
this.cleanupSubscriptions();
|
||||
this.setupSubscriptions();
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.cleanupSubscriptions();
|
||||
}
|
||||
|
||||
private registerRef = (ref: HTMLElement): void => {
|
||||
this._refs.push(ref);
|
||||
}
|
||||
|
||||
private setupSubscriptions = (): void => {
|
||||
if (!this.props.disableDragAndDrop && !this.props.disabled) {
|
||||
this._refs.forEach((value: HTMLElement, index: number) => {
|
||||
this._ddSubs.push(this._ddHelper.subscribe(value, new EventGroup(value), {
|
||||
eventMap: [
|
||||
{
|
||||
callback: (context: IDragDropContext, event?: any) => {
|
||||
this._draggedItem = context.data;
|
||||
},
|
||||
eventName: 'dragstart'
|
||||
}
|
||||
],
|
||||
selectionIndex: index,
|
||||
context: { data: this.state.items[index], index: index },
|
||||
updateDropState: (isDropping: boolean, event: DragEvent) => {
|
||||
if (isDropping) {
|
||||
value.classList.add(styles.dragEnter);
|
||||
} else {
|
||||
value.classList.remove(styles.dragEnter);
|
||||
}
|
||||
},
|
||||
canDrop: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => {
|
||||
return true;
|
||||
},
|
||||
canDrag: (item?: any) => {
|
||||
return true;
|
||||
},
|
||||
onDrop: (item?: any, event?: DragEvent) => {
|
||||
if (this._draggedItem) {
|
||||
this.insertBeforeItem(item);
|
||||
}
|
||||
},
|
||||
/*onDragStart: (item?: any, itemIndex?: number, selectedItems?: any[], event?: MouseEvent) => {
|
||||
//Never called for some reason, so using eventMap above
|
||||
this._draggedItem = item;
|
||||
},*/
|
||||
onDragEnd: (item?: any, event?: DragEvent) => {
|
||||
this._draggedItem = null;
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
//Create dropable 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 },
|
||||
updateDropState: (isDropping: boolean, event: DragEvent) => {
|
||||
if (isDropping) {
|
||||
this._refs[this._refs.length - 1].classList.add(styles.dragLast);
|
||||
} else {
|
||||
this._refs[this._refs.length - 1].classList.remove(styles.dragLast);
|
||||
}
|
||||
},
|
||||
canDrop: (dropContext?: IDragDropContext, dragContext?: IDragDropContext) => {
|
||||
return true;
|
||||
},
|
||||
onDrop: (item?: any, event?: DragEvent) => {
|
||||
if (this._draggedItem) {
|
||||
let itemIndex: number = this.state.items.indexOf(this._draggedItem);
|
||||
this.moveItemAtIndexToTargetIndex(itemIndex, this.state.items.length - 1);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupSubscriptions = (): void => {
|
||||
while (this._ddSubs.length) {
|
||||
let sub: any = this._ddSubs.pop();
|
||||
sub.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private insertBeforeItem = (item: any) => {
|
||||
let itemIndex: number = this.state.items.indexOf(this._draggedItem);
|
||||
let targetIndex: number = this.state.items.indexOf(item);
|
||||
if (itemIndex < targetIndex) {
|
||||
targetIndex -= 1;
|
||||
}
|
||||
this.moveItemAtIndexToTargetIndex(itemIndex, targetIndex);
|
||||
}
|
||||
|
||||
|
||||
private onMoveUpClick = (itemIndex: number): void => {
|
||||
if (itemIndex > 0) {
|
||||
this.moveItemAtIndexToTargetIndex(itemIndex, itemIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private onMoveDownClick = (itemIndex: number): void => {
|
||||
if (itemIndex < this.state.items.length - 1) {
|
||||
this.moveItemAtIndexToTargetIndex(itemIndex, itemIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
items.splice(targetIndex, 0, ...items.splice(itemIndex, 1)[0]);
|
||||
|
||||
this.setState({
|
||||
items: items
|
||||
});
|
||||
|
||||
this.props.valueChanged(items);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*ImageManipulation
|
||||
|
||||
FilterType
|
||||
*/
|
||||
|
||||
export { ImageManipulation, IImageManipulationConfig } from './ImageManipulation';
|
||||
|
||||
export {
|
||||
IImageManipulationSettings, IManipulationBase,
|
||||
IFilterSettings, IRotateSettings, IScaleSettings, IFlipSettings, ICropSettings, IResizeSettings,
|
||||
FilterType
|
||||
} from './ImageManipulation.types';
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
define([], function () {
|
||||
return {
|
||||
"ManipulationTypeFilter": "Filter",
|
||||
"ManipulationTypeFlip": "Spiegeln",
|
||||
"ManipulationTypeRotate": "Drehen",
|
||||
"ManipulationTypeScale": "Skalieren",
|
||||
"ManipulationTypeCrop": "Zuschneiden",
|
||||
"ManipulationTypeResize": "Größe ändern",
|
||||
"FilterTypeGrayscale": "Graustufen",
|
||||
"FilterTypeSepia": "Sepia",
|
||||
"SettingPanelClose": "Schließen",
|
||||
"SettingPanelHistory": "Verlauf",
|
||||
"CommandBarRedo": "Erneut ausführen",
|
||||
"CommandBarUndo": "Rückgängig machen",
|
||||
"CommandBarReset": "Zurücksetzen",
|
||||
"FlipVertical": "Vertikal",
|
||||
"FlipHorizontal": "Horizontal",
|
||||
"LockAspect": "Verhältnis sperren",
|
||||
"Width": "Breite",
|
||||
"Height": "Höhe",
|
||||
"SourceX": "SourceX",
|
||||
"SourceY": "SourceY",
|
||||
}
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
define([], function () {
|
||||
return {
|
||||
"ManipulationTypeFilter": "Filter",
|
||||
"ManipulationTypeFlip": "Flip",
|
||||
"ManipulationTypeRotate": "Rotate",
|
||||
"ManipulationTypeScale": "Scale",
|
||||
"ManipulationTypeCrop": "Crop",
|
||||
"ManipulationTypeResize": "Resize",
|
||||
"FilterTypeGrayscale": "Grayscale",
|
||||
"FilterTypeSepia": "Sepia",
|
||||
"SettingPanelClose": "Close",
|
||||
"SettingPanelHistory": "History",
|
||||
"CommandBarRedo": "Redo",
|
||||
"CommandBarUndo": "Undo",
|
||||
"CommandBarReset": "Reset",
|
||||
"FlipVertical": "Vertical",
|
||||
"FlipHorizontal": "Horizontal",
|
||||
"LockAspect": "Lock aspect",
|
||||
"Width": "Width",
|
||||
"Height": "Height",
|
||||
"SourceX": "SourceX",
|
||||
"SourceY": "SourceY",
|
||||
}
|
||||
});
|
35
samples/react-image-editor/src/components/ImageManipulation/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
declare interface IImageManipulationStrings {
|
||||
|
||||
ManipulationTypeFilter: string;
|
||||
ManipulationTypeFlip: string;
|
||||
ManipulationTypeRotate: string;
|
||||
ManipulationTypeScale: string;
|
||||
ManipulationTypeCrop: string;
|
||||
ManipulationTypeResize: string;
|
||||
|
||||
|
||||
FilterTypeGrayscale: string;
|
||||
FilterTypeSepia: string;
|
||||
|
||||
SettingPanelClose: string;
|
||||
SettingPanelHistory: string;
|
||||
|
||||
CommandBarRedo: string;
|
||||
CommandBarUndo: string;
|
||||
CommandBarReset: string;
|
||||
|
||||
FlipVertical: string;
|
||||
FlipHorizontal: string;
|
||||
|
||||
LockAspect: string;
|
||||
Width: string;
|
||||
Height: string;
|
||||
SourceX: string;
|
||||
SourceY: string;
|
||||
|
||||
}
|
||||
|
||||
declare module 'ImageManipulationStrings' {
|
||||
const strings: IImageManipulationStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './ImageManipulation';
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M 16 4 C 11.593564 4 8 7.5935677 8 12 C 8 12.195683 8.0153666 12.387845 8.0292969 12.580078 C 5.0876831 13.764072 3 16.643186 3 20 C 3 24.406432 6.5935644 28 11 28 C 12.890388 28 14.628379 27.335631 16 26.232422 C 17.371621 27.335631 19.109612 28 21 28 C 25.406436 28 29 24.406432 29 20 C 29 16.643186 26.912317 13.764072 23.970703 12.580078 C 23.984633 12.387845 24 12.195683 24 12 C 24 7.5935677 20.406436 4 16 4 z M 16 6 C 19.325556 6 22 8.674446 22 12 C 22 12.023102 21.996351 12.045321 21.996094 12.068359 C 21.669097 12.027374 21.337804 12 21 12 C 19.109612 12 17.371621 12.664369 16 13.767578 C 14.628379 12.664369 12.890388 12 11 12 C 10.662196 12 10.330903 12.027374 10.003906 12.068359 C 10.003649 12.045321 10 12.023102 10 12 C 10 8.674446 12.674444 6 16 6 z M 11 14 C 12.360072 14 13.607475 14.45273 14.611328 15.208984 C 14.110052 15.875191 13.710721 16.618398 13.435547 17.421875 C 12.008277 16.748871 10.896895 15.533946 10.359375 14.035156 C 10.569969 14.013008 10.783318 14 11 14 z M 21 14 C 21.216682 14 21.430031 14.013008 21.640625 14.035156 C 21.103105 15.533946 19.991723 16.748871 18.564453 17.421875 C 18.289279 16.618398 17.889948 15.875191 17.388672 15.208984 C 18.392525 14.45273 19.639928 14 21 14 z M 8.4355469 14.578125 C 9.1903993 16.782207 10.878414 18.554196 13.029297 19.419922 C 13.015367 19.612155 13 19.804317 13 20 C 13 21.797096 13.604527 23.452954 14.611328 24.791016 C 13.607475 25.54727 12.360072 26 11 26 C 7.6744439 26 5 23.325554 5 20 C 5 17.594646 6.4028703 15.536599 8.4355469 14.578125 z M 23.564453 14.578125 C 25.59713 15.536599 27 17.594646 27 20 C 27 23.325554 24.325556 26 21 26 C 19.639928 26 18.392525 25.54727 17.388672 24.791016 C 18.395473 23.452954 19 21.797096 19 20 C 19 19.804317 18.984633 19.612155 18.970703 19.419922 C 21.121586 18.554196 22.809601 16.782207 23.564453 14.578125 z M 16 16.675781 C 16.264346 17.073691 16.476452 17.507074 16.640625 17.964844 C 16.430031 17.986992 16.216682 18 16 18 C 15.783318 18 15.569969 17.986992 15.359375 17.964844 C 15.523548 17.507074 15.735654 17.073691 16 16.675781 z M 15.003906 19.931641 C 15.330903 19.972626 15.662196 20 16 20 C 16.337804 20 16.669097 19.972626 16.996094 19.931641 C 16.996351 19.954679 17 19.976898 17 20 C 17 21.232254 16.631467 22.373698 16 23.324219 C 15.368533 22.373698 15 21.232254 15 20 C 15 19.976898 15.003649 19.954679 15.003906 19.931641 z"></path></svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M 8 4 L 8 8 L 4 8 L 4 10 L 8 10 L 8 24 L 22 24 L 22 28 L 24 28 L 24 24 L 28 24 L 28 22 L 11.4375 22 L 22 11.4375 L 22 21 L 24 21 L 24 9.4375 L 27.71875 5.71875 L 26.28125 4.28125 L 22.5625 8 L 11 8 L 11 10 L 20.5625 10 L 10 20.5625 L 10 4 Z"></path></svg>
|
After Width: | Height: | Size: 325 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M 26 3.3125 L 4.5 15 L 26 15 Z M 4.46875 17 L 7.9375 18.875 L 24.53125 27.875 L 26 28.6875 L 26 17 Z M 12.34375 19 L 24 19 L 24 25.34375 Z"></path></svg>
|
After Width: | Height: | Size: 223 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M 15 4.46875 L 13.125 7.9375 L 4.125 24.53125 L 3.3125 26 L 15 26 Z M 17 4.5 L 17 26 L 28.6875 26 Z M 13 12.34375 L 13 24 L 6.65625 24 Z"></path></svg>
|
After Width: | Height: | Size: 221 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><path d="M 46.0625 1.9375 C 45.976563 1.949219 45.894531 1.96875 45.8125 2 C 45.726563 2.019531 45.640625 2.050781 45.5625 2.09375 L 1.5625 24.09375 C 1.210938 24.257813 0.984375 24.613281 0.984375 25 C 0.984375 25.386719 1.210938 25.742188 1.5625 25.90625 L 45.5625 47.90625 C 46.0625 48.148438 46.664063 47.9375 46.90625 47.4375 C 47.148438 46.9375 46.9375 46.335938 46.4375 46.09375 L 39.40625 42.5625 C 41.109375 39.992188 45 33.238281 45 25 C 45 16.761719 41.109375 10.007813 39.40625 7.4375 L 46.4375 3.90625 C 46.960938 3.78125 47.292969 3.269531 47.191406 2.742188 C 47.089844 2.214844 46.59375 1.859375 46.0625 1.9375 Z M 35.75 9.25 C 36.667969 10.550781 41 17.03125 41 25 C 41 32.96875 36.667969 39.449219 35.75 40.75 L 26.59375 36.1875 C 28.097656 34.328125 31 30.078125 31 25 C 31 19.921875 28.097656 15.671875 26.59375 13.8125 Z M 21.6875 16.28125 C 20.347656 18.484375 19 21.542969 19 25 C 19 28.457031 20.347656 31.515625 21.6875 33.71875 L 4.25 25 Z"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M 5 5 L 5 7 L 7 7 L 7 5 Z M 9 5 L 9 7 L 11 7 L 11 5 Z M 13 5 L 13 7 L 15 7 L 15 5 Z M 17 5 L 17 7 L 19 7 L 19 5 Z M 21 5 L 21 7 L 23 7 L 23 5 Z M 25 5 L 25 7 L 27 7 L 27 5 Z M 5 9 L 5 11 L 7 11 L 7 9 Z M 25 9 L 25 11 L 27 11 L 27 9 Z M 14 11 L 14 13 L 17.5625 13 L 13.5625 17 L 5 17 L 5 27 L 15 27 L 15 18.4375 L 19 14.4375 L 19 18 L 21 18 L 21 11 Z M 5 13 L 5 15 L 7 15 L 7 13 Z M 25 13 L 25 15 L 27 15 L 27 13 Z M 25 17 L 25 19 L 27 19 L 27 17 Z M 7 19 L 13 19 L 13 25 L 7 25 Z M 25 21 L 25 23 L 27 23 L 27 21 Z M 17 25 L 17 27 L 19 27 L 19 25 Z M 21 25 L 21 27 L 23 27 L 23 25 Z M 25 25 L 25 27 L 27 27 L 27 25 Z"></path></svg>
|
After Width: | Height: | Size: 700 B |
|
@ -10,8 +10,9 @@ import {
|
|||
|
||||
import * as strings from 'ReactImageEditorWebPartStrings';
|
||||
import ReactImageEditor, { IReactImageEditorBaseProps, IReactImageEditorProps } from './components/ReactImageEditor';
|
||||
import { IImageManipulationSettings } from '../../components';
|
||||
|
||||
|
||||
import { IImageManipulationSettings } from 'react-image-manipulation-spfx';
|
||||
|
||||
export interface IReactImageEditorWebPartProps extends IReactImageEditorBaseProps {
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ 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 'react-image-manipulation-spfx'
|
||||
import { ImageManipulation, IImageManipulationSettings } from '../../../components/ImageManipulation'
|
||||
|
||||
|
||||
export interface IReactImageEditorProps extends IReactImageEditorBaseProps {
|
||||
|
@ -60,7 +60,7 @@ export default class ReactImageEditor extends React.Component<IReactImageEditorP
|
|||
this.setState({ isFilePickerOpen: false }, () => this._onUrlChanged(filePickerResult.fileAbsoluteUrl))
|
||||
}}
|
||||
onCancel={() => {
|
||||
this.setState({ isFilePickerOpen: false }, () => this._onUrlChanged(''))
|
||||
this.setState({ isFilePickerOpen: false })
|
||||
}}
|
||||
onChanged={(filePickerResult: IFilePickerResult) => {
|
||||
this.setState({ isFilePickerOpen: false }, () => this._onUrlChanged(filePickerResult.fileAbsoluteUrl))
|
||||
|
|