mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-25 11:06:40 +07:00
stickers: drawing arrow + text
This commit is contained in:
parent
f183c8593d
commit
9729a9e117
11 changed files with 120 additions and 25 deletions
|
@ -14,3 +14,5 @@ const database = mongoose.connection;
|
||||||
|
|
||||||
database.on('error', (err) => { console.error(`Database Connection Error: ${err}`); process.exit(2); });
|
database.on('error', (err) => { console.error(`Database Connection Error: ${err}`); process.exit(2); });
|
||||||
database.on('connected', () => { console.info('Succesfully connected to MongoDB Database'); });
|
database.on('connected', () => { console.info('Succesfully connected to MongoDB Database'); });
|
||||||
|
|
||||||
|
console.log(`DB: mongodb://${USER}:${PASSWORD}@${HOSTNAME}:${PORT}/${DATABASE}`);
|
||||||
|
|
|
@ -8,8 +8,8 @@ const RouteSchema = new Schema(
|
||||||
title: { type: String, default: '' },
|
title: { type: String, default: '' },
|
||||||
// address: { type: String, required: true },
|
// address: { type: String, required: true },
|
||||||
version: { type: Number, default: 2 },
|
version: { type: Number, default: 2 },
|
||||||
route: { type: Array },
|
route: { type: Array, default: [] },
|
||||||
stickers: { type: Array },
|
stickers: { type: Array, default: [] },
|
||||||
owner: { type: Schema.Types.ObjectId, ref: 'User' },
|
owner: { type: Schema.Types.ObjectId, ref: 'User' },
|
||||||
logo: { type: String, default: 'DEFAULT' },
|
logo: { type: String, default: 'DEFAULT' },
|
||||||
distance: { type: Number, default: 0 },
|
distance: { type: Number, default: 0 },
|
||||||
|
|
|
@ -8,7 +8,7 @@ module.exports = async (req, res) => {
|
||||||
path: 'routes',
|
path: 'routes',
|
||||||
options: {
|
options: {
|
||||||
limit: 100,
|
limit: 100,
|
||||||
sort: { updated: 1 },
|
sort: { updated_at: -1 },
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const random_url = await generateRandomUrl();
|
const random_url = await generateRandomUrl();
|
||||||
|
|
|
@ -9,7 +9,7 @@ module.exports.parseRoute = route => route.filter(el => (
|
||||||
));
|
));
|
||||||
|
|
||||||
module.exports.parseStickers = stickers => stickers.filter(el => (
|
module.exports.parseStickers = stickers => stickers.filter(el => (
|
||||||
Object.keys(el).length === 4
|
Object.keys(el).length === 5
|
||||||
&& el.latlng
|
&& el.latlng
|
||||||
&& Object.keys(el.latlng).length === 2
|
&& Object.keys(el.latlng).length === 2
|
||||||
&& el.latlng.lat
|
&& el.latlng.lat
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const RouteRow = ({
|
||||||
<div className="route-description">
|
<div className="route-description">
|
||||||
<span>
|
<span>
|
||||||
<Icon icon="icon-cycle-1" />
|
<Icon icon="icon-cycle-1" />
|
||||||
{(distance && `${distance} km`) || 'N/A'}
|
{(distance && `${distance} km`) || '0 km'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
setTitle,
|
setTitle,
|
||||||
} from '$redux/user/actions';
|
} from '$redux/user/actions';
|
||||||
import { DEFAULT_PROVIDER, PROVIDERS } from '$constants/providers';
|
import { DEFAULT_PROVIDER, PROVIDERS } from '$constants/providers';
|
||||||
|
import { STICKERS } from '$constants/stickers';
|
||||||
|
|
||||||
export class Editor {
|
export class Editor {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -136,7 +137,7 @@ export class Editor {
|
||||||
if (!e || !e.latlng || !this.activeSticker) return;
|
if (!e || !e.latlng || !this.activeSticker) return;
|
||||||
const { latlng } = e;
|
const { latlng } = e;
|
||||||
|
|
||||||
this.stickers.createSticker({ latlng, sticker: this.activeSticker });
|
this.stickers.createSticker({ latlng, sticker: this.activeSticker.sticker, set: this.activeSticker.set });
|
||||||
this.setActiveSticker(null);
|
this.setActiveSticker(null);
|
||||||
this.setChanged(true);
|
this.setChanged(true);
|
||||||
};
|
};
|
||||||
|
@ -232,12 +233,18 @@ export class Editor {
|
||||||
|
|
||||||
this.stickers.clearAll();
|
this.stickers.clearAll();
|
||||||
if (stickers) {
|
if (stickers) {
|
||||||
stickers.map(sticker => this.stickers.createSticker({
|
stickers.map(sticker =>
|
||||||
latlng: sticker.latlng,
|
sticker.set && STICKERS[sticker.set].url &&
|
||||||
angle: parseStickerAngle({ sticker, version }),
|
this.stickers.createSticker({
|
||||||
sticker: parseStickerStyle({ sticker, version }),
|
latlng: sticker.latlng,
|
||||||
text: sticker.text,
|
// angle: parseStickerAngle({ sticker, version }),
|
||||||
}));
|
// sticker: parseStickerStyle({ sticker, version }),
|
||||||
|
angle: sticker.angle,
|
||||||
|
sticker: sticker.sticker,
|
||||||
|
set: sticker.set,
|
||||||
|
text: sticker.text,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (owner) this.owner = owner;
|
if (owner) this.owner = owner;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import classnames from 'classnames';
|
||||||
|
|
||||||
export class Sticker {
|
export class Sticker {
|
||||||
constructor({
|
constructor({
|
||||||
latlng, deleteSticker, map, lockMapClicks, sticker, triggerOnChange, angle = 2.2, text = '',
|
latlng, deleteSticker, map, lockMapClicks, sticker, set, triggerOnChange, angle = 2.2, text = '',
|
||||||
}) {
|
}) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.latlng = latlng;
|
this.latlng = latlng;
|
||||||
|
@ -18,6 +18,7 @@ export class Sticker {
|
||||||
this.isDragging = false;
|
this.isDragging = false;
|
||||||
this.map = map;
|
this.map = map;
|
||||||
this.sticker = sticker;
|
this.sticker = sticker;
|
||||||
|
this.set = set;
|
||||||
this.editable = true;
|
this.editable = true;
|
||||||
this.triggerOnChange = triggerOnChange;
|
this.triggerOnChange = triggerOnChange;
|
||||||
this.direction = 'right';
|
this.direction = 'right';
|
||||||
|
@ -38,7 +39,7 @@ export class Sticker {
|
||||||
onMouseUp={this.onDragStop}
|
onMouseUp={this.onDragStop}
|
||||||
>
|
>
|
||||||
<StickerDesc value={this.text} onChange={this.setText} />
|
<StickerDesc value={this.text} onChange={this.setText} />
|
||||||
{this.generateStickerSVG(sticker)}
|
{this.generateStickerSVG(set, sticker)}
|
||||||
<div
|
<div
|
||||||
className="sticker-delete"
|
className="sticker-delete"
|
||||||
onMouseDown={this.onDelete}
|
onMouseDown={this.onDelete}
|
||||||
|
@ -163,20 +164,23 @@ export class Sticker {
|
||||||
this.stickerArrow.style.transform = `rotate(${angle + Math.PI}rad)`;
|
this.stickerArrow.style.transform = `rotate(${angle + Math.PI}rad)`;
|
||||||
};
|
};
|
||||||
|
|
||||||
generateStickerSVG = ({ set, sticker }) => (
|
generateStickerSVG = (set, sticker) => {
|
||||||
<div
|
return (
|
||||||
className="sticker-image"
|
<div
|
||||||
style={{
|
className="sticker-image"
|
||||||
|
style={{
|
||||||
backgroundImage: `url('${STICKERS[set].url}`,
|
backgroundImage: `url('${STICKERS[set].url}`,
|
||||||
backgroundPosition: `${-STICKERS[set].layers[sticker].off * 72} 50%`,
|
backgroundPosition: `${-STICKERS[set].layers[sticker].off * 72} 50%`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
dumpData = () => ({
|
dumpData = () => ({
|
||||||
angle: this.angle,
|
angle: this.angle,
|
||||||
latlng: { ...this.marker.getLatLng() },
|
latlng: { ...this.marker.getLatLng() },
|
||||||
sticker: this.sticker,
|
sticker: this.sticker,
|
||||||
|
set: this.set,
|
||||||
text: this.text,
|
text: this.text,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class Stickers {
|
||||||
// this.createSticker({ latlng });
|
// this.createSticker({ latlng });
|
||||||
// };
|
// };
|
||||||
|
|
||||||
createSticker = ({ latlng, sticker, angle = 2.2, text = '' }) => {
|
createSticker = ({ latlng, sticker, angle = 2.2, text = '', set }) => {
|
||||||
const marker = new Sticker({
|
const marker = new Sticker({
|
||||||
latlng,
|
latlng,
|
||||||
angle,
|
angle,
|
||||||
|
@ -28,6 +28,7 @@ export class Stickers {
|
||||||
map: this.map,
|
map: this.map,
|
||||||
lockMapClicks: this.lockMapClicks,
|
lockMapClicks: this.lockMapClicks,
|
||||||
sticker,
|
sticker,
|
||||||
|
set,
|
||||||
triggerOnChange: this.triggerOnChange,
|
triggerOnChange: this.triggerOnChange,
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,9 +20,9 @@ import { DEFAULT_USER } from '$constants/auth';
|
||||||
import { TIPS } from '$constants/tips';
|
import { TIPS } from '$constants/tips';
|
||||||
import {
|
import {
|
||||||
composeImages,
|
composeImages,
|
||||||
composePoly, downloadCanvas,
|
composePoly, composeStickers, downloadCanvas,
|
||||||
fetchImages,
|
fetchImages,
|
||||||
getPolyPlacement,
|
getPolyPlacement, getStickersPlacement,
|
||||||
getTilePlacement,
|
getTilePlacement,
|
||||||
imageFetcher
|
imageFetcher
|
||||||
} from '$utils/renderer';
|
} from '$utils/renderer';
|
||||||
|
@ -259,12 +259,15 @@ function* getRenderData() {
|
||||||
|
|
||||||
const geometry = getTilePlacement();
|
const geometry = getTilePlacement();
|
||||||
const points = getPolyPlacement();
|
const points = getPolyPlacement();
|
||||||
|
const stickers = getStickersPlacement();
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
const images = yield fetchImages(ctx, geometry);
|
const images = yield fetchImages(ctx, geometry);
|
||||||
|
|
||||||
yield composeImages({ geometry, images, ctx });
|
yield composeImages({ geometry, images, ctx });
|
||||||
yield composePoly({ points, ctx });
|
yield composePoly({ points, ctx });
|
||||||
|
yield composeStickers({ stickers, ctx });
|
||||||
|
|
||||||
return yield canvas.toDataURL('image/jpeg');
|
return yield canvas.toDataURL('image/jpeg');
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,7 +162,7 @@
|
||||||
|
|
||||||
.sticker-desc {
|
.sticker-desc {
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
|
z-index: -1;
|
||||||
height: auto;
|
height: auto;
|
||||||
background: #222222;
|
background: #222222;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -65,6 +65,17 @@ export const getPolyPlacement = () => (
|
||||||
: editor.poly.poly.getLatLngs().map((latlng) => ({ ...editor.map.map.latLngToContainerPoint(latlng) }))
|
: editor.poly.poly.getLatLngs().map((latlng) => ({ ...editor.map.map.latLngToContainerPoint(latlng) }))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getStickersPlacement = () => (
|
||||||
|
(!editor.stickers || editor.stickers.dumpData().length <= 0)
|
||||||
|
? []
|
||||||
|
: editor.stickers.dumpData().map(({ latlng: { lat, lng }, text, angle, sticker }) => ({
|
||||||
|
...editor.map.map.latLngToContainerPoint({ lat, lng }),
|
||||||
|
text,
|
||||||
|
sticker,
|
||||||
|
angle,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const getImageSource = coords => replaceProviderUrl(editor.provider, coords);
|
const getImageSource = coords => replaceProviderUrl(editor.provider, coords);
|
||||||
|
|
||||||
export const imageFetcher = source => new Promise((resolve, reject) => {
|
export const imageFetcher = source => new Promise((resolve, reject) => {
|
||||||
|
@ -142,7 +153,74 @@ export const composePoly = ({ points, ctx }) => {
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
|
|
||||||
return true;
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const measureText = (ctx, text, lineHeight) => (
|
||||||
|
text.split('\n').reduce((obj, line) => (
|
||||||
|
{
|
||||||
|
width: Math.max(ctx.measureText(line).width, obj.width),
|
||||||
|
height: obj.height + lineHeight,
|
||||||
|
}
|
||||||
|
), { width: 0, height: 0 })
|
||||||
|
);
|
||||||
|
|
||||||
|
const composeStickerArrow = (ctx, x, y, angle) => {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
ctx.rotate(angle + (Math.PI * 0.75));
|
||||||
|
ctx.translate(-x, -y);
|
||||||
|
ctx.fillStyle = 'red';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
ctx.lineTo(x + 38, y + 20);
|
||||||
|
ctx.lineTo(x + 38, y + 38);
|
||||||
|
ctx.lineTo(x + 20, y + 38);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
const composeStickerText = (ctx, x, y, angle, text) => {
|
||||||
|
const rad = 56;
|
||||||
|
const tX = ((Math.cos(angle + Math.PI) * rad) - 30) + x + 28;
|
||||||
|
const tY = ((Math.sin(angle + Math.PI) * rad) - 30) + y + 29;
|
||||||
|
|
||||||
|
ctx.font = '12px "Helvetica Neue", Arial';
|
||||||
|
const lines = text.split('\n');
|
||||||
|
const { width, height } = measureText(ctx, text, 16);
|
||||||
|
|
||||||
|
// rectangle
|
||||||
|
ctx.fillStyle = '#222222';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(
|
||||||
|
tX,
|
||||||
|
tY + 3 - (height / 2) - 10,
|
||||||
|
width + 36 + 10,
|
||||||
|
height + 20,
|
||||||
|
);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
ctx.fillStyle = 'white';
|
||||||
|
lines.map((line, i) => {
|
||||||
|
ctx.fillText(
|
||||||
|
line,
|
||||||
|
tX + 36,
|
||||||
|
tY + (16 * i) + 16 - (height / 2)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const composeStickers = ({ stickers, ctx }) => {
|
||||||
|
if (!stickers || stickers.length < 0) return;
|
||||||
|
|
||||||
|
stickers.map(({ x, y, angle, text }) => {
|
||||||
|
composeStickerArrow(ctx, x, y, angle);
|
||||||
|
composeStickerText(ctx, x, y, angle, text);
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const downloadCanvas = (canvas, title) => canvas.toBlob(blob => saveAs(blob, title));
|
export const downloadCanvas = (canvas, title) => canvas.toBlob(blob => saveAs(blob, title));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue