react-internal-editor
v1.0.2
Published
React.js + ant.design + fabric.js + react-pdf + react-design-editor + material-ui
Downloads
435
Maintainers
Readme
React Internal Editor 1.0.1
React Internal Editor is an assignment editor tool for the internal building operations, which is using React.js + ant.design + fabric.js + react-pdf + react-design-editor + material-ui
Highlighting Features
- Shortcut Layer Control
- Region Comparsion (Dormer Window)
- Export Layer Configurations (full layers)
- Layer Assignments (Individual or Group)
- Vector Drawings (Circle, Triangle, Path, Square)
- Layer Panel (Grouping, Duplication)
- Floor Panel
Screenshots
Floor Panel
Shortcut Link Panel
Region Comparsion
Demo
Please refer to the demo link: http://dev.4d.com.hk/internaleditor/, or download the demo source react-internal-editor-demo
Important Concept Behinds
The project was using react-pdf and fabricjs to edit the pdf file, while the huge performance challenge for the browser loading and loading the pdf in mobile browser, thus we has changed to use image source control for replacing the pdf manipulation in client's browser. Also, you need to reference the followings by doing the stuffs at the server-side before getting the start for this plugin
- Convert all of pages from the PDF document after upload Each of page in the PDF document should be separated to the piece of image and the image should be maintained under a correct relationship in the backend-side
const PDFImage = require("pdf-image").PDFImage;
const pdfImage = new PDFImage('./floors.pdf');
pdfImage.convertFile().then(function (imagePaths) {
// [ /tmp/slide-0.png, /tmp/slide-1.png ]
// save the relationships for those image (e.g. floor relationship)
});
See pdf-image for more info and all of the dependencies for the pdf-image
.
- Version Comparsion Cropping Use your logic for cropping the region from the full image for any pdf, the followings only for the reference
const sharp = require('sharp');
const fs = require('fs');
// read from the local directory or remote server using (http request)
const cacheFilePath = './floors.pdf';
sharp(fs.readFileSync(cacheFilePath))
.extract({
width: Number(width) || 400, // width
height: Number(height) || 300, // height
left: Number(left) || 0, // left
top: Number(top) || 1, // top
})
.toFile(cacheFilePath)
.then(() => {
res.download(cacheFilePath);
req.on('end', () => {
fs.unlinkSync(cacheFilePath);
});
});
See sharp for more info and all of the dependencies for the sharp
.
Getting started
InternalEditor requires Node.js v8.0+ to run.
- Install the dependencies and devDependencies and start the server.
$ cd react-internal-editor
$ npm install
$ npm start
- Install the latest babel7 dependencies for
react-internal-editor
and preapre the static babel configuration file, and prepare the properly loaders in your project
$ npm install @babel/[email protected] @babel/[email protected] @babel/[email protected] @babel/[email protected] --save-dev
.babelrc
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
["@babel/transform-runtime"]
]
}
webpack.config.js
module: {
rules: [
.....
.....
.....
{
// important url loader for the svg fonts in `react-internal-editor`
test: /\.(ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader',
options: {
publicPath: './',
name: 'fonts/[hash].[ext]',
limit: 10000,
},
},
{
test: /\.(css|less)$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
},
{
loader: 'less-loader', // important less loader for the `react-internal-editor`
options: {
javascriptEnabled: true,
}
},
]
}
]
}
- InternalEditor relies on some endpoints for partially operations.
import InternalEditor from 'react-internal-editor';
// On Shortcut View Clicking
onAreaViewClick(selected) {
window.console.warn(`Selected Hyperlink data: ${selected}`);
this.setState({
// change the pdf image url, to fetch the target floor image
// or do other stuffs
sourceUrl: 'http://0.0.0.0:3010/api/viewAreaLink',
});
}
// Save or export layer's config data
onExportConfig(type, data, closeCallback) {
// callback the editor to close the save panel, required
closeCallback();
window.console.warn(type, data);
}
<InternalEditor
source={{ // required
_id: '9e1324686886df234d42', // main pdf id
name: 'Hong Kong-Zhuhai-Macau Bridge',
}}
dataSources={this.state.sources} // optional
preview={false} // optional
language='en-US'
sourceEndpoint={{ // source pdf image endpoint, required
url: this.state.sourceUrl,
// url: 'https://www.deermember.com/api/testpdf',
onError: err => {
alert(err);
},
}}
onSave=this.onExportConfig,
areaListEndpoint={{
url: 'http://0.0.0.0:3010/api/testAreaLink',
onError: err => {
alert(err);
},
}}
onClickHyperLink={this.onAreaViewClick}
groupAssignmentEndpoint={{ // group search list endpoint, required
url: 'http://0.0.0.0:3010/api/testGroup',
onError: err => {
alert(err);
},
singleSelection: false, // multi select
}}
userAssignmentEndpoint={{ // user search list endpoint, required
url: 'http://0.0.0.0:3010/api/testUser',
onError: err => {
alert(err);
},
}}
comparsionListEndpoint={{ // comparsion version list endpoint, required
url: 'http://0.0.0.0:3010/api/testComparsion',
onError: err => {
alert(err);
},
}}
comparsionDetailEndpoint={{ // get the blob pdf image for the comparsion item endpoint, required
url: 'http://0.0.0.0:3010/api/testLoadComparsion',
onError: err => {
alert(err);
},
}}
floorList={[ // suppose not update frequently, options
{
_id: '9e1324686886df234d42',
name: 'Hong Kong-Zhuhai-Macau Bridge (First Floor)', // labe
},
{
_id: '9e142fc86886df234d6d',
name: 'Hong Kong-Zhuhai-Macau Bridge (Second Floor)',
},
{
_id: '9e1324843443df2523d74',
name: 'Hong Kong-Zhuhai-Macau Bridge (Third Floor)',
},
{
_id: '84132484344dk2434d74',
name: 'Hong Kong-Zhuhai-Macau Bridge (Fourth Floor)',
},
{
_id: '6e1324843443df54d74',
name: 'Hong Kong-Zhuhai-Macau Bridge (Fifth Floor)',
},
]}
onFloorSelect={selected => window.console.warn(`Selected Floor Plan data: ${selected}`)} // optional
/>
Component Options
Property | Type | Default | Description
:-----------------------|:--------------|:--------------|:--------------------------------
source | object | undefined | Required. A source infomation including target id and name for the configuation export use, you can also store it at your own component state. e.g. { id: "", name: "" }
dataSources | object | undefined | Required. A layer configurations toward the source pdf. e.g. { workarea: {}, objects: [], "animations": [], "styles": [], "dataSources": [], }
preview | boolean | false | Optional. the editor turns on preview mode, where the panels will be disabled in readonly mode
sourceEndpoint | object | undefined | Required. providing the source endpoint api service for fetching the target PDF Image to the editor, onError callback also provides for any error occurs. e.g. { url: "", onError: () => {}, }
onSave | function | undefined | Required. layer export callback and the type of save will be also returned either save
or 'saveAs' with the source.id
areaListEndpoint | object | undefined | Required. providing the shortcut area endpoint api service for fetching the target area item to be selected, while there is the different area item belonging to the current chosen floor. also, onError callback provides for any error occurs. e.g. { url: "", onError: () => {}, }
onClickHyperLink | function | undefined | Required. area shortcut callback for the view action, should handle the data configuration changing after the callback for the other target area of the floor
groupAssignmentEndpoint | object | undefined | Required. providing the group endpoint api service for fetching the group list to the assignment panel in the editor, onError callback also provides for any error occurs. e.g. { url: "", onError: () => {}, }
userAssignmentEndpoint | object | undefined | Required. providing the user endpoint api service for fetching the individual user list to the assignment panel in the editor, onError callback also provides for any error occurs. e.g. { url: "", onError: () => {}, }
comparsionListEndpoint | object | undefined | Required. providing the version endpoint api service for fetching the version list to the comparsion panel in the editor towards the chosen floor, onError callback also provides for any error occurs. e.g. { url: "", onError: () => {}, }
comparsionDetailEndpoint | object | undefined | Required. providing the version details endpoint api service for cropping the target region towards the chosen version in comparsionListEndpoint
, onError callback also provides for any error occurs. e.g. { url: "", onError: () => {}, }
floorList | array | [] | Optional. the floor list for the current building project, each of building project should has many floors e.g. [ { _id: "", name: "" } ]
onFloorSelect | function | [] | Optional. the floor clicking callback, the other layer configuration data and floor image should be also fetched from sourceEndpoint.url
to dataSources
with source: { _id: "", name: "" }
Layer Configuration Example
const dataSources = {
"workarea": {
"width": 900,
"height": 900
},
"objects": [
// basic workarea object, required
{
"type": "image",
"version": "2.3.6",
"originX": "left",
"originY": "top",
"left": 297.5,
"top": 421,
"width": 0,
"height": 0,
"fill": "rgb(0,0,0)",
"stroke": null,
"strokeWidth": 0,
"strokeDashArray": null,
"strokeLineCap": "butt",
"strokeLineJoin": "miter",
"strokeMiterLimit": 4,
"scaleX": 1,
"scaleY": 1,
"angle": 0,
"flipX": false,
"flipY": false,
"opacity": 1,
"shadow": null,
"visible": true,
"clipTo": null,
"backgroundColor": "rgba(255, 255, 255, 0)",
"fillRule": "nonzero",
"paintFirst": "fill",
"globalCompositeOperation": "source-over",
"transformMatrix": null,
"skewX": 0,
"skewY": 0,
"crossOrigin": "",
"cropX": 0,
"cropY": 0,
"id": "workarea",
"name": "",
"link": {},
"tooltip": {
"enabled": false
},
"layout": "fixed",
"workareaWidth": 595,
"workareaHeight": 842,
"src": "",
"filters": []
},
{
"type": "rect",
"version": "2.3.6",
"originX": "left",
"originY": "top",
"left": 225,
"top": 230.5,
"width": 40,
"height": 40,
"fill": "rgba(0, 0, 0, 1)",
"stroke": "rgba(255, 255, 255, 0)",
"strokeWidth": 1,
"strokeDashArray": null,
"strokeLineCap": "butt",
"strokeLineJoin": "miter",
"strokeMiterLimit": 4,
"scaleX": 1,
"scaleY": 1,
"angle": 0,
"flipX": false,
"flipY": false,
"opacity": 1,
"shadow": null,
"visible": true,
"clipTo": null,
"backgroundColor": "",
"fillRule": "nonzero",
"paintFirst": "fill",
"globalCompositeOperation": "source-over",
"transformMatrix": null,
"skewX": 0,
"skewY": 0,
"rx": 0,
"ry": 0,
"id": "886caf5f-3b8b-474c-9330-264b23f2efaa",
"name": "New shape",
"link": {
"enabled": false,
"type": "resource",
"state": "new",
"dashboard": {}
},
"tooltip": {
"enabled": true,
"type": "resource",
"template": "<div>{{message.name}}</div>"
},
"animation": {
"type": "none",
"loop": true,
"autoplay": true,
"delay": 100,
"duration": 1000
},
"userProperty": {},
"trigger": {
"enabled": false,
"type": "alarm",
"script": "return message.value > 0;",
"effect": "style"
}
},
],
"animations": [],
"styles": [],
"dataSources": [],
};
Development
$ cd react-internal-editor
$ npm install
$ npm start
Open http://0.0.0.0:3030 to view react-internal-editor
Depolyment
$ cd react-internal-editor
$ npm run publish
Todos
- Features
- Job Assignments Control
- Area Separation
- Job Accomplishment
- Environment
- Migrate to React 16.9.0 with context hooks
- Upgrade Material-ui will the latest core set
- Remove useless code for 'react-designer-editor'
Dependencies
react-internal-editor
has some external dependencies, which are the usual react
, react-dom
, ant.design
, material-ui
, i18next
and fabric
, as well as design-editor
.
Changelogs
Verion 1.0.2 (29/08/2019)
- Add Demo Project
- Fix the issue fot the use of react-rangeslider at the Region Comparsion Panel.
Verion 1.0.1 (28/08/2019)
- Downgrade React.js to v16.4.0 from v16.9.0
- Add publishing script for the distribution release using babel-cil
- Remove Slider from @material-ui/code, use react-rangeslider instead.
- Remove pdf-lib and react-pdf dependencies
- Add babel.config.js instead of .babelrc static configuration file
License
react-internal-editor
is available under the MIT License