Skip to content

Commit d34f1df

Browse files
author
Francis Cote
committed
Initial commit 🚀
1 parent b421b16 commit d34f1df

11 files changed

+387
-1
lines changed

.babelrc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"presets": [
3+
["env", { "modules": false }]
4+
]
5+
}

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.DS_Store
2+
node_modules/
3+
dist/
4+
npm-debug.log
5+
yarn-error.log

README.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
11
# vue-polygon-editor
2-
Simple Vue.js line-by-line svg polygon editor
2+
3+
> Simple Vue.js line-by-line svg polygon editor
4+
5+
## Demo
6+
7+
## Installation
8+
9+
## Usage
10+
11+
## License
12+
[MIT](https://github.com/thatfrankdev/vue-polygon-editor/blob/master/LICENSE)

index.html

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>vue-polygon-editor</title>
6+
</head>
7+
<body>
8+
<div id="app"></div>
9+
<script src="/dist/demo.js"></script>
10+
</body>
11+
</html>

package.json

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "vue-polygon-editor",
3+
"description": "Simple Vue.js line-by-line svg polygon editor",
4+
"version": "1.0.0",
5+
"author": "Francis Cote <[email protected]>",
6+
"main" : "dist/vue-polygon-editor.js",
7+
"scripts": {
8+
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.config.js --open --hot",
9+
"build": "cross-env NODE_ENV=production webpack --progress --config webpack.dev.config.js --hide-modules",
10+
"release": "cross-env NODE_ENV=production webpack --progress --config webpack.release.config.js --hide-modules"
11+
},
12+
"files": [
13+
"dist/*",
14+
"README.md",
15+
"LICENSE"
16+
],
17+
"dependencies": {
18+
"vue": "^2.3.3"
19+
},
20+
"devDependencies": {
21+
"babel-core": "^6.0.0",
22+
"babel-loader": "^6.0.0",
23+
"babel-preset-env": "^1.5.1",
24+
"cross-env": "^3.0.0",
25+
"css-loader": "^0.25.0",
26+
"file-loader": "^0.9.0",
27+
"node-sass": "^4.5.0",
28+
"sass-loader": "^5.0.1",
29+
"vue-loader": "^12.1.0",
30+
"vue-template-compiler": "^2.3.3",
31+
"webpack": "^2.6.1",
32+
"webpack-dev-server": "^2.4.5"
33+
}
34+
}

src/PolygonEditor.vue

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<template>
2+
<svg
3+
:height="height"
4+
:width="width"
5+
@click="addPoint"
6+
@contextmenu="clearPoints"
7+
:style="svgStyle">
8+
9+
<polygon
10+
:points="pointsString"
11+
:fill="fill"
12+
:fill-opacity="fillOpacity"
13+
:stroke="stroke"
14+
:stroke-width="strokeWidth" />
15+
16+
<circle
17+
v-for="point in points"
18+
:cx="point.x"
19+
:cy="point.y"
20+
:r="pointsRadius"
21+
:fill="getPointFill(point)"
22+
:fill-opacity="getPointFillOpacity(point)"
23+
@click="removePointHoveringOn"
24+
@mouseenter="hoveringOn(point)"
25+
@mouseleave="hoveringOn(null)" />
26+
27+
</svg>
28+
</template>
29+
30+
<script>
31+
export default {
32+
33+
name: 'polygon-editor',
34+
35+
props: {
36+
value: { type: String, default: null },
37+
height: { type: String, default: "100%" },
38+
width: { type: String, default: "100%" },
39+
pointsRadius: { type: Number, default: 5 },
40+
pointsHoverColor: { type: String, default: "red" },
41+
pointsColor: { type: String, default: "teal" },
42+
fill: { type: String, default: "lightgray" },
43+
fillOpacity: { type: Number, default: 0.5 },
44+
stroke: { type: String, default: "gray" },
45+
strokeWidth: { type: Number, default: 1 },
46+
backgroundImage: { type: String, default: null }
47+
},
48+
49+
data() {
50+
return {
51+
points: this.extractPoints(this.value),
52+
pointHoveringOn: null
53+
}
54+
},
55+
56+
computed: {
57+
hasPoints() {
58+
return this.points && this.points.length > 0;
59+
},
60+
lastPoint() {
61+
if (this.hasPoints) {
62+
return this.points[this.points.length - 1];
63+
}
64+
return null;
65+
},
66+
pointsString() {
67+
let pointStrings = this.points.map(function (point) {
68+
return point.x + ',' + point.y;
69+
}, this);
70+
return pointStrings.join(' ');
71+
},
72+
svgStyle() {
73+
let style = '';
74+
if (this.backgroundImage) {
75+
style += 'background:url("' + this.backgroundImage + '") no-repeat left top'
76+
}
77+
return style;
78+
}
79+
},
80+
81+
watch: {
82+
pointsString() {
83+
this.$emit('input', this.pointsString)
84+
}
85+
},
86+
87+
methods: {
88+
extractPoints() {
89+
const valuePattern = /^(\d+,\d+ )*\d+,\d+$/;
90+
let points = [];
91+
if (!this.value) { return points; }
92+
if (!valuePattern.test(this.value)) {
93+
console.warn('Value provided is not in the correct format. Value must bew in the format of polygon "points" attribute (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/points)');
94+
return points;
95+
}
96+
this.value.split(' ').forEach(point => {
97+
points.push(
98+
{
99+
x: parseInt(point.split(',')[0], 10),
100+
y: parseInt(point.split(',')[1], 10),
101+
}
102+
);
103+
}, this);
104+
return points;
105+
},
106+
addPoint(evt) {
107+
this.points.push(
108+
{
109+
x: evt.offsetX,
110+
y: evt.offsetY
111+
}
112+
)
113+
},
114+
removePointHoveringOn(evt) {
115+
if (!this.pointHoveringOn) {
116+
return;
117+
}
118+
evt.stopImmediatePropagation();
119+
this.points.splice(this.points.indexOf(this.pointHoveringOn), 1);
120+
},
121+
clearPoints(evt) {
122+
this.points = [];
123+
evt.preventDefault();
124+
},
125+
getPointFill(point) {
126+
if (point === this.pointHoveringOn) {
127+
return this.pointsHoverColor;
128+
}
129+
return this.pointsColor;
130+
},
131+
getPointFillOpacity(point) {
132+
if (point === this.pointHoveringOn || point === this.lastPoint) {
133+
return 1;
134+
}
135+
return 0;
136+
},
137+
hoveringOn(point) {
138+
this.pointHoveringOn = point;
139+
},
140+
}
141+
142+
}
143+
144+
</script>
145+
146+
<style lang="scss">
147+
148+
</style>

src/demo/App.vue

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<template>
2+
<div id="app">
3+
<textarea type="text" v-model="points" />
4+
<polygon-editor
5+
v-model="points"
6+
height="300px"
7+
width="300px"
8+
stroke="black"
9+
fill="blue"
10+
background-image="https://unsplash.it/300/300/?random">
11+
</polygon-editor>
12+
</div>
13+
</template>
14+
15+
<script>
16+
17+
import PolygonEditor from '../PolygonEditor.vue'
18+
19+
export default {
20+
name: 'app',
21+
components:{
22+
PolygonEditor
23+
},
24+
data () {
25+
return {
26+
points : "200,10 250,190 160,210"
27+
}
28+
}
29+
}
30+
31+
</script>
32+
33+
<style lang="scss">
34+
35+
#app {
36+
font-family: 'Avenir', Helvetica, Arial, sans-serif;
37+
-webkit-font-smoothing: antialiased;
38+
-moz-osx-font-smoothing: grayscale;
39+
text-align: center;
40+
color: #2c3e50;
41+
margin-top: 60px;
42+
}
43+
44+
</style>

src/demo/main.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import Vue from 'vue'
2+
import App from './App.vue'
3+
4+
new Vue({
5+
el: '#app',
6+
render: h => h(App)
7+
})

src/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import PolygonEditor from './PolygonEditor.vue'
2+
3+
export default PolygonEditor;

webpack.dev.config.js

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
var path = require('path')
2+
var webpack = require('webpack')
3+
4+
module.exports = {
5+
entry: './src/demo/main.js',
6+
output: {
7+
path: path.resolve(__dirname, './dist/demo'),
8+
publicPath: '/dist',
9+
filename: 'demo.js'
10+
},
11+
module: {
12+
rules: [
13+
{
14+
test: /\.vue$/,
15+
loader: 'vue-loader',
16+
options: {
17+
loaders: {
18+
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
19+
// the "scss" and "sass" values for the lang attribute to the right configs here.
20+
// other preprocessors should work out of the box, no loader config like this necessary.
21+
'scss': 'vue-style-loader!css-loader!sass-loader',
22+
'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax'
23+
}
24+
// other vue-loader options go here
25+
}
26+
},
27+
{
28+
test: /\.js$/,
29+
loader: 'babel-loader',
30+
exclude: /node_modules/
31+
},
32+
{
33+
test: /\.(png|jpg|gif|svg)$/,
34+
loader: 'file-loader',
35+
options: {
36+
name: '[name].[ext]?[hash]'
37+
}
38+
}
39+
]
40+
},
41+
resolve: {
42+
alias: {
43+
'vue$': 'vue/dist/vue.esm.js'
44+
}
45+
},
46+
devServer: {
47+
historyApiFallback: true,
48+
noInfo: true
49+
},
50+
performance: {
51+
hints: false
52+
},
53+
devtool: '#eval-source-map'
54+
}
55+
56+
if (process.env.NODE_ENV === 'production') {
57+
module.exports.devtool = '#source-map'
58+
// http://vue-loader.vuejs.org/en/workflow/production.html
59+
module.exports.plugins = (module.exports.plugins || []).concat([
60+
new webpack.DefinePlugin({
61+
'process.env': {
62+
NODE_ENV: '"production"'
63+
}
64+
}),
65+
new webpack.optimize.UglifyJsPlugin({
66+
sourceMap: true,
67+
compress: {
68+
warnings: false
69+
}
70+
}),
71+
new webpack.LoaderOptionsPlugin({
72+
minimize: true
73+
})
74+
])
75+
}

0 commit comments

Comments
 (0)