mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-25 02:56:41 +07:00
render: cropper & download image
This commit is contained in:
parent
857a2a0c12
commit
34d1b85513
14 changed files with 308 additions and 40 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
@ -3630,6 +3630,11 @@
|
||||||
"sha.js": "^2.4.8"
|
"sha.js": "^2.4.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"croppr": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/croppr/-/croppr-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-0rvTl4VmR3I4AahjJPF1u9IlT7ckvjIcgaLnUjYaY+UZsP9oxlVYZWYDuqM3SVCQiaI7DXMjR7wOEYT+mydOFg=="
|
||||||
|
},
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
|
||||||
|
@ -5234,6 +5239,11 @@
|
||||||
"schema-utils": "^0.4.5"
|
"schema-utils": "^0.4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"file-saver": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-cYM1ic5DAkg25pHKgi5f10ziAM7RJU37gaH1XQlyNDrtUnzhC/dfoV9zf2OmF0RMKi42jG5B0JWBnPQqyj/G6g=="
|
||||||
|
},
|
||||||
"filename-regex": {
|
"filename-regex": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
|
||||||
|
|
|
@ -50,6 +50,8 @@
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"clean-webpack-plugin": "^0.1.9",
|
"clean-webpack-plugin": "^0.1.9",
|
||||||
|
"croppr": "^2.3.1",
|
||||||
|
"file-saver": "^2.0.0",
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
"leaflet": "^1.3.4",
|
"leaflet": "^1.3.4",
|
||||||
"leaflet-editable": "^1.1.0",
|
"leaflet-editable": "^1.1.0",
|
||||||
|
|
|
@ -9,10 +9,8 @@ import { EditorDialog } from '$components/panels/EditorDialog';
|
||||||
import { LogoPreview } from '$components/logo/LogoPreview';
|
import { LogoPreview } from '$components/logo/LogoPreview';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { setMode, startEditing, stopEditing, setLogo, showRenderer } from '$redux/user/actions';
|
import { setMode, startEditing, stopEditing, setLogo, takeAShot } from '$redux/user/actions';
|
||||||
import type { UserType } from '$constants/types';
|
import type { UserType } from '$constants/types';
|
||||||
import { editor } from '$modules/Editor';
|
|
||||||
import { getTilePlacement } from '$utils/renderer';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: UserType,
|
user: UserType,
|
||||||
|
@ -31,7 +29,7 @@ type Props = {
|
||||||
startEditing: Function,
|
startEditing: Function,
|
||||||
stopEditing: Function,
|
stopEditing: Function,
|
||||||
setLogo: Function,
|
setLogo: Function,
|
||||||
showRenderer: Function,
|
takeAShot: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Component extends React.PureComponent<Props, void> {
|
class Component extends React.PureComponent<Props, void> {
|
||||||
|
@ -39,8 +37,6 @@ class Component extends React.PureComponent<Props, void> {
|
||||||
const obj = document.getElementById('control-dialog');
|
const obj = document.getElementById('control-dialog');
|
||||||
const { width } = this.panel.getBoundingClientRect();
|
const { width } = this.panel.getBoundingClientRect();
|
||||||
|
|
||||||
console.log(obj, this.panel);
|
|
||||||
|
|
||||||
if (!this.panel || !obj) return;
|
if (!this.panel || !obj) return;
|
||||||
|
|
||||||
obj.style.width = width;
|
obj.style.width = width;
|
||||||
|
@ -109,7 +105,7 @@ class Component extends React.PureComponent<Props, void> {
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={classnames('disabled', { active: mode === MODES.SHOTTER })}
|
className={classnames('disabled', { active: mode === MODES.SHOTTER })}
|
||||||
onClick={this.props.showRenderer}
|
onClick={this.props.takeAShot}
|
||||||
// onClick={getTilePlacement}
|
// onClick={getTilePlacement}
|
||||||
>
|
>
|
||||||
<Icon icon="icon-shot-3" />
|
<Icon icon="icon-shot-3" />
|
||||||
|
@ -221,7 +217,7 @@ const mapDispatchToProps = dispatch => bindActionCreators({
|
||||||
setLogo,
|
setLogo,
|
||||||
startEditing,
|
startEditing,
|
||||||
stopEditing,
|
stopEditing,
|
||||||
showRenderer,
|
takeAShot,
|
||||||
}, dispatch);
|
}, dispatch);
|
||||||
|
|
||||||
export const EditorPanel = connect(
|
export const EditorPanel = connect(
|
||||||
|
|
|
@ -1,30 +1,148 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getPolyPlacement, getTilePlacement, composeImages, composePoly, fetchImages } from '$utils/renderer';
|
|
||||||
|
|
||||||
export class Renderer extends React.Component {
|
import { hideRenderer, cropAShot } from '$redux/user/actions';
|
||||||
componentDidMount() {
|
import { bindActionCreators } from 'redux';
|
||||||
if (this.canvas) this.init();
|
import { connect } from 'react-redux';
|
||||||
|
import Croppr from 'croppr';
|
||||||
|
import 'croppr/dist/croppr.css';
|
||||||
|
import { Icon } from '$components/panels/Icon';
|
||||||
|
import { LOGOS } from '$constants/logos';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: String,
|
||||||
|
logo: String,
|
||||||
|
hideRenderer: Function,
|
||||||
|
cropAShot: Function,
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class Component extends React.Component<Props, State> {
|
||||||
|
onImageLoaded = () => {
|
||||||
|
this.croppr = new Croppr(this.image, {
|
||||||
|
onCropMove: this.moveLogo,
|
||||||
|
onInitialize: this.onCropInit,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.croppr.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
onCropInit = (crop) => {
|
||||||
const ctx = this.canvas.getContext('2d');
|
|
||||||
const geometry = getTilePlacement();
|
|
||||||
const points = getPolyPlacement();
|
|
||||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
||||||
|
|
||||||
fetchImages(ctx, geometry)
|
|
||||||
.then(images => composeImages({ geometry, images, ctx }))
|
const { regionEl, box } = crop;
|
||||||
.then(() => composePoly({ points, ctx }))
|
const scale = ((box.x2 - box.x1) / window.innerWidth);
|
||||||
.then(() => this.canvas.toDataURL('image/jpeg'));
|
|
||||||
// .then(image => window.open().document.write(`<img src="${image}" />`))
|
console.log('CROP', crop, scale);
|
||||||
}
|
|
||||||
|
this.logo = document.createElement('div');
|
||||||
|
this.logo.className = 'renderer-logo';
|
||||||
|
this.logo.style.transform = `scale(${scale})`;
|
||||||
|
|
||||||
|
this.logoImg = document.createElement('img');
|
||||||
|
this.logoImg.src = LOGOS[this.props.logo][1];
|
||||||
|
|
||||||
|
this.logo.append(this.logoImg);
|
||||||
|
regionEl.append(this.logo);
|
||||||
|
};
|
||||||
|
|
||||||
|
moveLogo = ({ x, y, width, height }) => {
|
||||||
|
if (!this.logo) return;
|
||||||
|
|
||||||
|
this.logo.style.color = 'blue';
|
||||||
|
};
|
||||||
|
|
||||||
|
croppr;
|
||||||
|
|
||||||
|
getImage = () => {
|
||||||
|
this.props.cropAShot(this.croppr.getValue());
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { data } = this.props;
|
||||||
|
const { innerWidth, innerHeight } = window;
|
||||||
|
const padding = 30;
|
||||||
|
const paddingBottom = 80;
|
||||||
|
|
||||||
|
let width;
|
||||||
|
let height;
|
||||||
|
|
||||||
|
// if (innerWidth > innerHeight) {
|
||||||
|
height = innerHeight - padding - paddingBottom;
|
||||||
|
width = height * (innerWidth / innerHeight);
|
||||||
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="renderer-shade" onClick={this.props.onClick}>
|
<div>
|
||||||
<canvas width={window.innerWidth} height={window.innerHeight} ref={el => { this.canvas = el; }} />
|
<div
|
||||||
|
className="renderer-shade"
|
||||||
|
style={{
|
||||||
|
padding,
|
||||||
|
paddingBottom,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{ width, height }}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={data}
|
||||||
|
alt=""
|
||||||
|
id="rendererOutput"
|
||||||
|
ref={el => { this.image = el; }}
|
||||||
|
onLoad={this.onImageLoaded}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="panel active"
|
||||||
|
style={{
|
||||||
|
zIndex: 1000,
|
||||||
|
left: '50%',
|
||||||
|
right: 'auto',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="control-bar control-bar-padded">
|
||||||
|
<button>
|
||||||
|
<Icon icon="icon-logo-3" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="control-sep" />
|
||||||
|
|
||||||
|
<div className="control-bar">
|
||||||
|
<button
|
||||||
|
className="highlighted cancel"
|
||||||
|
onClick={this.props.hideRenderer}
|
||||||
|
>
|
||||||
|
<Icon icon="icon-cancel-1" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="success"
|
||||||
|
onClick={this.getImage}
|
||||||
|
>
|
||||||
|
<span>СКАЧАТЬ</span>
|
||||||
|
<Icon icon="icon-get-1" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({ ...state.user.renderer, logo: state.user.logo });
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => bindActionCreators({
|
||||||
|
hideRenderer,
|
||||||
|
cropAShot,
|
||||||
|
}, dispatch);
|
||||||
|
|
||||||
|
export const Renderer = connect(mapStateToProps, mapDispatchToProps)(Component);
|
||||||
|
|
|
@ -26,9 +26,16 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canvas#renderer {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<canvas id="renderer"></canvas>
|
||||||
<section id="map" style="position: absolute; width: 100%; height: 100%;"></section>
|
<section id="map" style="position: absolute; width: 100%; height: 100%;"></section>
|
||||||
<section id="loader"></section>
|
<section id="loader"></section>
|
||||||
<section id="index"></section>
|
<section id="index"></section>
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
/*
|
/*
|
||||||
|
todo shot mechanism (50%)
|
||||||
|
done client-side shot mechanism
|
||||||
|
todo croppr.js
|
||||||
|
todo shot stickers
|
||||||
|
|
||||||
todo hotkeys via sagas
|
todo hotkeys via sagas
|
||||||
todo shot mechanism
|
|
||||||
todo crop mechanism
|
|
||||||
todo map catalogue
|
todo map catalogue
|
||||||
|
todo map preview on save
|
||||||
todo tooltips
|
todo tooltips
|
||||||
|
|
||||||
todo client-side shot mechanism
|
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
|
@ -34,3 +34,7 @@ export const setSaveOverwrite = () => ({ type: ACTIONS.SET_SAVE_OVERWRITE });
|
||||||
|
|
||||||
export const showRenderer = () => ({ type: ACTIONS.SHOW_RENDERER });
|
export const showRenderer = () => ({ type: ACTIONS.SHOW_RENDERER });
|
||||||
export const hideRenderer = () => ({ type: ACTIONS.HIDE_RENDERER });
|
export const hideRenderer = () => ({ type: ACTIONS.HIDE_RENDERER });
|
||||||
|
export const setRenderer = payload => ({ type: ACTIONS.SET_RENDERER, payload });
|
||||||
|
export const takeAShot = () => ({ type: ACTIONS.TAKE_A_SHOT });
|
||||||
|
export const cropAShot = payload => ({ type: ACTIONS.CROP_A_SHOT, ...payload });
|
||||||
|
|
||||||
|
|
|
@ -33,4 +33,7 @@ export const ACTIONS = {
|
||||||
|
|
||||||
SHOW_RENDERER: 'SHOW_RENDERER',
|
SHOW_RENDERER: 'SHOW_RENDERER',
|
||||||
HIDE_RENDERER: 'HIDE_RENDERER',
|
HIDE_RENDERER: 'HIDE_RENDERER',
|
||||||
|
SET_RENDERER: 'SET_RENDERER',
|
||||||
|
TAKE_A_SHOT: 'TAKE_A_SHOT',
|
||||||
|
CROP_A_SHOT: 'CROP_A_SHOT',
|
||||||
};
|
};
|
||||||
|
|
|
@ -65,6 +65,11 @@ const hideRenderer = state => ({
|
||||||
renderer: { ...state.renderer, renderer_active: false }
|
renderer: { ...state.renderer, renderer_active: false }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setRenderer = (state, { payload }) => ({
|
||||||
|
...state,
|
||||||
|
renderer: { ...state.renderer, ...payload }
|
||||||
|
});
|
||||||
|
|
||||||
const HANDLERS = {
|
const HANDLERS = {
|
||||||
[ACTIONS.SET_USER]: setUser,
|
[ACTIONS.SET_USER]: setUser,
|
||||||
[ACTIONS.SET_EDITING]: setEditing,
|
[ACTIONS.SET_EDITING]: setEditing,
|
||||||
|
@ -85,6 +90,7 @@ const HANDLERS = {
|
||||||
|
|
||||||
[ACTIONS.SHOW_RENDERER]: showRenderer,
|
[ACTIONS.SHOW_RENDERER]: showRenderer,
|
||||||
[ACTIONS.HIDE_RENDERER]: hideRenderer,
|
[ACTIONS.HIDE_RENDERER]: hideRenderer,
|
||||||
|
[ACTIONS.SET_RENDERER]: setRenderer,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const INITIAL_STATE = {
|
export const INITIAL_STATE = {
|
||||||
|
|
|
@ -3,10 +3,11 @@ import { delay } from 'redux-saga';
|
||||||
import { takeLatest, select, call, put, takeEvery, race, take } from 'redux-saga/effects';
|
import { takeLatest, select, call, put, takeEvery, race, take } from 'redux-saga/effects';
|
||||||
import { checkUserToken, getGuestToken, getStoredMap, postMap } from '$utils/api';
|
import { checkUserToken, getGuestToken, getStoredMap, postMap } from '$utils/api';
|
||||||
import {
|
import {
|
||||||
|
hideRenderer,
|
||||||
setActiveSticker, setAddress,
|
setActiveSticker, setAddress,
|
||||||
setChanged,
|
setChanged,
|
||||||
setEditing,
|
setEditing,
|
||||||
setMode,
|
setMode, setRenderer,
|
||||||
setSaveError,
|
setSaveError,
|
||||||
setSaveOverwrite, setSaveSuccess, setTitle,
|
setSaveOverwrite, setSaveSuccess, setTitle,
|
||||||
setUser
|
setUser
|
||||||
|
@ -17,6 +18,15 @@ import { ACTIONS } from '$redux/user/constants';
|
||||||
import { MODES } from '$constants/modes';
|
import { MODES } from '$constants/modes';
|
||||||
import { DEFAULT_USER } from '$constants/auth';
|
import { DEFAULT_USER } from '$constants/auth';
|
||||||
import { TIPS } from '$constants/tips';
|
import { TIPS } from '$constants/tips';
|
||||||
|
import {
|
||||||
|
composeImages,
|
||||||
|
composePoly, downloadCanvas,
|
||||||
|
fetchImages,
|
||||||
|
getPolyPlacement,
|
||||||
|
getTilePlacement,
|
||||||
|
imageFetcher
|
||||||
|
} from '$utils/renderer';
|
||||||
|
import { LOGOS } from '$constants/logos';
|
||||||
|
|
||||||
const getUser = state => (state.user.user);
|
const getUser = state => (state.user.user);
|
||||||
const getState = state => (state.user);
|
const getState = state => (state.user);
|
||||||
|
@ -217,6 +227,61 @@ function* setSaveSuccessSaga({ address, title }) {
|
||||||
return yield editor.setInitialData();
|
return yield editor.setInitialData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function* getRenderData() {
|
||||||
|
const canvas = document.getElementById('renderer');
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
const geometry = getTilePlacement();
|
||||||
|
const points = getPolyPlacement();
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const images = yield fetchImages(ctx, geometry);
|
||||||
|
yield composeImages({ geometry, images, ctx });
|
||||||
|
yield composePoly({ points, ctx });
|
||||||
|
|
||||||
|
return yield canvas.toDataURL('image/jpeg');
|
||||||
|
}
|
||||||
|
|
||||||
|
function* takeAShotSaga() {
|
||||||
|
const data = yield call(getRenderData);
|
||||||
|
|
||||||
|
yield put(setRenderer({
|
||||||
|
data, renderer_active: true, width: window.innerWidth, height: window.innerHeight
|
||||||
|
}));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function* getCropData({
|
||||||
|
x, y, width, height
|
||||||
|
}) {
|
||||||
|
const { logo, renderer: { data } } = yield select(getState);
|
||||||
|
const canvas = document.getElementById('renderer');
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const image = yield imageFetcher(data);
|
||||||
|
const logoImage = yield imageFetcher(LOGOS[logo][1]);
|
||||||
|
|
||||||
|
ctx.drawImage(image, -x, -y);
|
||||||
|
ctx.drawImage(logoImage, width - logoImage.width, height - logoImage.height);
|
||||||
|
|
||||||
|
return yield canvas.toDataURL('image/jpeg');
|
||||||
|
}
|
||||||
|
|
||||||
|
function* cropAShotSaga(params) {
|
||||||
|
const { title, address } = yield select(getState);
|
||||||
|
yield call(getCropData, params);
|
||||||
|
const canvas = document.getElementById('renderer');
|
||||||
|
|
||||||
|
downloadCanvas(canvas, (title || address));
|
||||||
|
|
||||||
|
return yield put(hideRenderer());
|
||||||
|
}
|
||||||
|
|
||||||
export function* userSaga() {
|
export function* userSaga() {
|
||||||
// ASYNCHRONOUS!!! :-)
|
// ASYNCHRONOUS!!! :-)
|
||||||
|
|
||||||
|
@ -241,4 +306,6 @@ export function* userSaga() {
|
||||||
|
|
||||||
yield takeLatest(ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga);
|
yield takeLatest(ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga);
|
||||||
yield takeLatest(ACTIONS.SET_SAVE_SUCCESS, setSaveSuccessSaga);
|
yield takeLatest(ACTIONS.SET_SAVE_SUCCESS, setSaveSuccessSaga);
|
||||||
|
yield takeLatest(ACTIONS.TAKE_A_SHOT, takeAShotSaga);
|
||||||
|
yield takeLatest(ACTIONS.CROP_A_SHOT, cropAShotSaga);
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,8 +293,14 @@
|
||||||
|
|
||||||
<path xmlns="http://www.w3.org/2000/svg" d="M23 8c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2zm0 0c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2z" fill="white" stroke="white" stroke-width="1" transform="translate(4 4)"/>
|
<path xmlns="http://www.w3.org/2000/svg" d="M23 8c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2zm0 0c0 1.1-.9 2-2 2-.18 0-.35-.02-.51-.07l-3.56 3.55c.05.16.07.34.07.52 0 1.1-.9 2-2 2s-2-.9-2-2c0-.18.02-.36.07-.52l-2.55-2.55c-.16.05-.34.07-.52.07s-.36-.02-.52-.07l-4.55 4.56c.05.16.07.33.07.51 0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.18 0 .35.02.51.07l4.56-4.55C8.02 9.36 8 9.18 8 9c0-1.1.9-2 2-2s2 .9 2 2c0 .18-.02.36-.07.52l2.55 2.55c.16-.05.34-.07.52-.07s.36.02.52.07l3.55-3.56C19.02 8.35 19 8.18 19 8c0-1.1.9-2 2-2s2 .9 2 2z" fill="white" stroke="white" stroke-width="1" transform="translate(4 4)"/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
|
<g id="icon-get-1" stroke="none">
|
||||||
|
<path stroke-opacity=".941" stroke-width=".265" d="M0 0h32v32H0z" fill="black"/>
|
||||||
|
|
||||||
|
<path xmlns="http://www.w3.org/2000/svg" d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z" fill="white" stroke="white" stroke-width="1" transform="translate(4 4)"/>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<use xlink:href="#icon-shot-3" />
|
<use xlink:href="#icon-get-1" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
@ -117,6 +117,11 @@
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
background: linear-gradient(150deg, @green_primary, @green_secondary) 50% 50% no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&.danger {
|
&.danger {
|
||||||
background: linear-gradient(150deg, @red_primary, @red_secondary) 50% 50% no-repeat;
|
background: linear-gradient(150deg, @red_primary, @red_secondary) 50% 50% no-repeat;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
|
|
|
@ -5,11 +5,48 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
> div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
//canvas {
|
img#rendererOutput {
|
||||||
// width: 50vw;
|
width: 300px;
|
||||||
// height: 50vh;
|
height: 300px;
|
||||||
//}
|
}
|
||||||
|
|
||||||
|
.croppr-region {
|
||||||
|
box-shadow: rgba(255, 255, 255, 0.2) 0 0 0 6px, rgba(0, 0, 0, 0.3) 0 0 0 1px;
|
||||||
|
border: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.croppr-handle {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.3) 0 0 0 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.renderer-logo {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
transform-origin: 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { editor } from '$modules/Editor';
|
import { editor } from '$modules/Editor';
|
||||||
import { COLORS, CONFIG } from '$config';
|
import { COLORS, CONFIG } from '$config';
|
||||||
|
import saveAs from 'file-saver';
|
||||||
const { map } = editor.map;
|
|
||||||
map.addEventListener('mousedown', ({ latlng }) => console.log('CLICK', latlng));
|
|
||||||
|
|
||||||
const latLngToTile = latlng => {
|
const latLngToTile = latlng => {
|
||||||
|
const { map } = editor.map;
|
||||||
const zoom = map.getZoom();
|
const zoom = map.getZoom();
|
||||||
const xtile = parseInt(Math.floor((latlng.lng + 180) / 360 * (1 << zoom)));
|
const xtile = parseInt(Math.floor((latlng.lng + 180) / 360 * (1 << zoom)));
|
||||||
const ytile = parseInt(Math.floor((1 - Math.log(Math.tan(latlng.lat * Math.PI / 180) + 1 / Math.cos(latlng.lat * Math.PI / 180)) / Math.PI) / 2 * (1 << zoom)));
|
const ytile = parseInt(Math.floor((1 - Math.log(Math.tan(latlng.lat * Math.PI / 180) + 1 / Math.cos(latlng.lat * Math.PI / 180)) / Math.PI) / 2 * (1 << zoom)));
|
||||||
|
@ -13,6 +12,7 @@ const latLngToTile = latlng => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const tileToLatLng = point => {
|
const tileToLatLng = point => {
|
||||||
|
const { map } = editor.map;
|
||||||
const z = map.getZoom();
|
const z = map.getZoom();
|
||||||
const lng = (point.x / Math.pow(2, z) * 360 - 180);
|
const lng = (point.x / Math.pow(2, z) * 360 - 180);
|
||||||
const n = Math.PI - 2 * Math.PI * point.y / Math.pow(2, z);
|
const n = Math.PI - 2 * Math.PI * point.y / Math.pow(2, z);
|
||||||
|
@ -22,6 +22,7 @@ const tileToLatLng = point => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTilePlacement = () => {
|
export const getTilePlacement = () => {
|
||||||
|
const { map } = editor.map;
|
||||||
const width = window.innerWidth;
|
const width = window.innerWidth;
|
||||||
const height = window.innerHeight;
|
const height = window.innerHeight;
|
||||||
|
|
||||||
|
@ -60,12 +61,12 @@ export const getTilePlacement = () => {
|
||||||
export const getPolyPlacement = () => (
|
export const getPolyPlacement = () => (
|
||||||
(!editor.poly.poly || !editor.poly.poly.getLatLngs() || editor.poly.poly.getLatLngs().length <= 0)
|
(!editor.poly.poly || !editor.poly.poly.getLatLngs() || editor.poly.poly.getLatLngs().length <= 0)
|
||||||
? []
|
? []
|
||||||
: editor.poly.poly.getLatLngs().map((latlng) => ({ ...map.latLngToContainerPoint(latlng) }))
|
: editor.poly.poly.getLatLngs().map((latlng) => ({ ...editor.map.map.latLngToContainerPoint(latlng) }))
|
||||||
);
|
);
|
||||||
|
|
||||||
const getImageSource = ({ x, y, zoom }) => (`http://b.basemaps.cartocdn.com/light_all/${zoom}/${x}/${y}.png`);
|
const getImageSource = ({ x, y, zoom }) => (`http://b.basemaps.cartocdn.com/light_all/${zoom}/${x}/${y}.png`);
|
||||||
|
|
||||||
const imageFetcher = source => new Promise((resolve, reject) => {
|
export const imageFetcher = source => new Promise((resolve, reject) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.crossOrigin = 'anonymous';
|
img.crossOrigin = 'anonymous';
|
||||||
img.onload = () => resolve(img);
|
img.onload = () => resolve(img);
|
||||||
|
@ -140,3 +141,6 @@ export const composePoly = ({ points, ctx }) => {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const downloadCanvas = (canvas, title) => canvas.toBlob(blob => saveAs(blob, title));
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue