Merge pull request #13 from muerwre/feature/title-dialog

Feature/title dialog
This commit is contained in:
muerwre 2019-03-29 11:35:13 +07:00 committed by GitHub
commit b9cdaed8c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 307 additions and 115 deletions

View file

@ -2,24 +2,22 @@ const mongoose = require('mongoose');
const { Schema } = mongoose;
const RouteSchema = new Schema(
{
_id: { type: String, required: true },
title: { type: String, default: '' },
// address: { type: String, required: true },
version: { type: Number, default: 2 },
route: { type: Array, default: [] },
stickers: { type: Array, default: [] },
owner: { type: Schema.Types.ObjectId, ref: 'User' },
distance: { type: Number, default: 0 },
is_public: { type: Boolean, default: false },
is_starred: { type: Boolean, default: false },
is_deleted: { type: Boolean, default: false },
created_at: { type: Date, default: Date.now() },
updated_at: { type: Date, default: Date.now() },
logo: { type: String, default: 'DEFAULT' },
provider: { type: String, default: 'DEFAULT' },
},
);
const RouteSchema = new Schema({
_id: { type: String, required: true },
title: { type: String, default: '' },
version: { type: Number, default: 2 },
route: { type: Array, default: [] },
stickers: { type: Array, default: [] },
owner: { type: Schema.Types.ObjectId, ref: 'User' },
distance: { type: Number, default: 0 },
is_public: { type: Boolean, default: false },
is_starred: { type: Boolean, default: false },
is_deleted: { type: Boolean, default: false },
created_at: { type: Date, default: Date.now() },
updated_at: { type: Date, default: Date.now() },
logo: { type: String, default: 'DEFAULT' },
provider: { type: String, default: 'DEFAULT' },
description: { type: String, default: '' },
});
module.exports.RouteSchema = RouteSchema;

View file

@ -1,14 +1,21 @@
const { User, Route } = require('../../models');
const { parseRoute, parseStickers, parseString, parseNumber, parseAddress } = require('../../utils/parse');
const {
parseRoute, parseStickers, parseString, parseNumber, parseAddress
} = require('../../utils/parse');
module.exports = async (req, res) => {
const { body, body: { id, token, force } } = req;
const owner = await User.findOne({ _id: id, token }).populate('routes');
if (!owner) return res.send({ success: false, reason: 'unauthorized', id, token });
if (!owner) {
return res.send({
success: false, reason: 'unauthorized', id, token
});
}
const title = parseString(body.title, 64);
const description = parseString(body.description, 256);
const address = parseAddress(body.address, 32);
const route = parseRoute(body.route);
const stickers = parseStickers(body.stickers);
@ -28,23 +35,23 @@ module.exports = async (req, res) => {
if (exists) {
await exists.set({
title, route, stickers, logo, distance, updated_at: Date.now(), provider, is_public,
title, route, stickers, logo, distance, updated_at: Date.now(), provider, is_public, description,
}).save();
return res.send({
success: true, title, address, route, stickers, mode: 'overwrited', is_public,
success: true, title, address, route, stickers, mode: 'overwrited', is_public, description,
});
}
const created = await Route.create({
_id: address, title, route, stickers, owner, logo, distance, provider, is_public,
_id: address, title, route, stickers, owner, logo, distance, provider, is_public, description,
});
await owner.routes.push(created);
await owner.save();
return res.send({
success: true, title, address, route, stickers, provider, is_public,
success: true, title, address, route, stickers, provider, is_public, description,
});
};

7
package-lock.json generated
View file

@ -10749,6 +10749,13 @@
"scheduler": "^0.13.1"
}
},
"react-expandable-textarea": {
"version": "github:muerwre/react-expandable-textarea#0cbcbbd875439090a2d48e711da241f2a83dd6b2",
"from": "github:muerwre/react-expandable-textarea",
"requires": {
"classnames": "^2.2.6"
}
},
"react-hot-loader": {
"version": "4.6.5",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.6.5.tgz",

View file

@ -90,6 +90,7 @@
"rc-slider": "8.5.0",
"react": "16.8.1",
"react-dom": "16.8.1",
"react-expandable-textarea": "github:muerwre/react-expandable-textarea",
"react-hot-loader": "^4.1.1",
"react-infinite-scroller": "^1.2.2",
"react-rangeslider": "^2.2.0",

View file

@ -1,16 +1,20 @@
import * as React from 'react';
import { copyToClipboard, getUrlData } from '$utils/history';
import { toTranslit } from '$utils/format';
import { toTranslit, parseDesc } from '$utils/format';
import { TIPS } from '$constants/tips';
import { MODES } from '$constants/modes';
import { Icon } from '$components/panels/Icon';
import { Switch } from '$components/Switch';
import classnames from 'classnames';
import { IRootState } from "$redux/user/reducer";
import { sendSaveRequest, setMode } from "$redux/user/actions";
import ExpandableTextarea from 'react-expandable-textarea';
interface Props {
address: string,
title: string,
is_public: boolean,
interface Props extends IRootState {
width: number,
setMode: typeof setMode,
sendSaveRequest: typeof sendSaveRequest,
@ -25,6 +29,7 @@ interface State {
address: string,
title: string,
is_public: boolean,
description: string,
}
export class SaveDialog extends React.Component<Props, State> {
@ -35,6 +40,7 @@ export class SaveDialog extends React.Component<Props, State> {
address: props.address || '',
title: props.title || '',
is_public: props.is_public || false,
description: props.description || '',
};
}
@ -46,22 +52,23 @@ export class SaveDialog extends React.Component<Props, State> {
};
setTitle = ({ target: { value } }) => this.setState({ title: ((value && value.substr(0, 64)) || '') });
setAddress = ({ target: { value } }) => this.setState({ address: (value && value.substr(0, 32) || '') });
setDescription = ({ target: { value } }) => this.setState({ description: (value && value.substr(0, 256) || '') });
cancelSaving = () => this.props.setMode(MODES.NONE);
sendSaveRequest = (e, force = false) => {
const { title, is_public } = this.state;
const { title, is_public, description } = this.state;
const address = this.getAddress();
this.props.sendSaveRequest({
title, address, force, is_public
title, address, force, is_public, description,
});
};
forceSaveRequest = e => this.sendSaveRequest(e, true);
cancelSaving = () => this.props.setMode(MODES.NONE);
onCopy = e => {
e.preventDefault();
const { host, protocol } = getUrlData();
@ -73,7 +80,7 @@ export class SaveDialog extends React.Component<Props, State> {
};
render() {
const { title, is_public } = this.state;
const { title, is_public, description } = this.state;
const { save_error, save_finished, save_overwriting, width, save_loading } = this.props;
const { host, protocol } = getUrlData();
@ -104,6 +111,15 @@ export class SaveDialog extends React.Component<Props, State> {
</div>
</div>
<div className="save-textarea">
<ExpandableTextarea
minRows={2}
maxRows={5}
placeholder="Описание маршрута"
value={parseDesc(description)}
onChange={this.setDescription}
/>
</div>
<div className="save-text">
{ save_error || TIPS.SAVE_INFO }
</div>

View file

@ -3,36 +3,105 @@ import { bindActionCreators } from "redux";
import { connect } from 'react-redux';
import classnames from 'classnames';
import { getStyle } from "$utils/dom";
import { nearestInt } from "$utils/geom";
import { IRootState } from "$redux/user/reducer";
import { parseDesc } from "$utils/format";
interface ITitleDialogProps {
editing: boolean,
title?: string,
editing: IRootState['editing'],
title?: IRootState['title'],
description?: IRootState['description'],
minLines?: number,
maxLines?: number,
}
interface ITitleDialogState {
raised: boolean;
height: number;
height_raised: number;
}
export class Component extends React.PureComponent<ITitleDialogProps, ITitleDialogState> {
state = {
raised: false,
height: 0,
height_raised: 0,
};
onHover = () => this.setState({ raised: true });
onLeave = () => this.setState({ raised: false });
componentDidMount() {
this.setMaxHeight();
}
componentDidUpdate() {
this.setMaxHeight();
}
setMaxHeight = (): number => {
if (!this.ref_sizer || !this.ref_title || !this.ref_text) return 0;
const { height: sizer_height } = this.ref_sizer.getBoundingClientRect();
const { height: title_height } = this.ref_title.getBoundingClientRect();
const { height: text_height } = this.ref_text.getBoundingClientRect();
if (text_height === 0) {
this.setState({ height: 0, height_raised: 0 });
return;
}
const title_margin = parseInt(getStyle(this.ref_title, 'margin-bottom'), 10) || 0;
const text_margins = (parseInt(getStyle(this.ref_text, 'margin-top'), 10) || 0) +
parseInt(getStyle(this.ref_text, 'margin-bottom'), 10) || 0;;
const text_line = parseInt(getStyle(this.ref_text, 'line-height'), 10) || 0;
const container_height = sizer_height - title_height - title_margin - text_margins;
const min_height = (this.props.minLines || 5) * text_line;
const max_height = (this.props.maxLines || 20) * text_line;
const height = nearestInt(Math.min(container_height, Math.min(text_height, min_height)), text_line) + text_margins;
const height_raised = nearestInt(Math.min(container_height, Math.min(text_height, max_height)), text_line) + text_margins;
this.setState({ height, height_raised });
};
render() {
const { editing, title } = this.props;
const { editing, title, description } = this.props;
const { raised, height, height_raised } = this.state;
return (
<div className="title-dialog-wrapper">
<div className="title-dialog-sizer" ref={el => { this.sizer = el; }}>
<div className={classnames('title-dialog', { active: title && !editing })}>
<div className="title-dialog-pane title-dialog-name">
<div className="title-dialog-sizer" ref={el => { this.ref_sizer = el; }}>
<div
className={classnames('title-dialog', { active: title && !editing })}
onMouseOver={this.onHover}
onMouseOut={this.onLeave}
>
<div
className="title-dialog-pane title-dialog-name"
ref={el => { this.ref_title = el; }}
>
<h2>{title}</h2>
</div>
<div className="title-dialog-pane title-dialog-text">
Давно выяснено, что при оценке дизайна и композиции читаемый текст мешает сосредоточиться. Lorem Ipsum используют потому, что тот обеспечивает более или менее стандартное заполнение шаблона, а также реальное распределение букв и пробелов в абзацах, которое не получается при простой дубликации "Здесь ваш текст.. Здесь ваш текст.. Здесь ваш текст.." Многие программы электронной вёрстки и редакторы HTML используют Lorem Ipsum в качестве текста по умолчанию, так что поиск по
<div
className={classnames("title-dialog-pane title-dialog-text", { has_shade: height_raised > height })}
style={{
height: (raised ? height_raised : height),
marginBottom: height === 0 ? 0 : 15,
}}
ref={el => { this.ref_overflow = el; }}
>
<div
ref={el => { this.ref_text = el; }}
>
{
parseDesc(description)
}
</div>
</div>
</div>
</div>
@ -40,11 +109,13 @@ export class Component extends React.PureComponent<ITitleDialogProps, ITitleDial
)
}
text;
sizer;
ref_sizer;
ref_title;
ref_text;
ref_overflow;
}
const mapStateToProps = ({ user: { editing, title } }) => ({ editing, title });
const mapStateToProps = ({ user: { editing, title, description } }) => ({ editing, title, description });
const mapDispatchToProps = dispatch => bindActionCreators({ }, dispatch);
export const TitleDialog = connect(mapStateToProps, mapDispatchToProps)(Component);

View file

@ -1,5 +1,5 @@
import * as React from 'react';
import { IModes, MODES } from '$constants/modes';
import { MODES } from '$constants/modes';
import { RouterDialog } from '$components/dialogs/RouterDialog';
import { StickersDialog } from '$components/dialogs/StickersDialog';

View file

@ -14,6 +14,7 @@ import { CLIENT } from '$config/frontend';
import { DIALOGS } from '$constants/dialogs';
import { IRootState } from "$redux/user/reducer";
import { Tooltip } from "$components/panels/Tooltip";
import { TitleDialog } from "$components/dialogs/TitleDialog";
interface Props extends IRootState {
userLogout: typeof userLogout,
@ -95,7 +96,7 @@ export class Component extends React.PureComponent<Props, State> {
return (
<div>
{
// <TitleDialog />
<TitleDialog />
}
<div className="panel active panel-user">
<div className="user-panel">

View file

@ -7,7 +7,7 @@
todo tower sticker
todo route description
todo polyline editing only in manual mode (or by click)
skip polyline editing only in manual mode (or by click)
todo selecting logo on crop
todo network operations notify

View file

@ -12,14 +12,14 @@ import {
resetSaveDialog,
setActiveSticker,
setAddress,
setChanged,
setChanged, setDescription,
setDistance,
setIsEmpty, setIsRouting,
setLogo,
setMarkersShown,
setMode,
setPublic,
setRouterPoints,
setRouterPoints, setStarred,
setTitle,
} from '$redux/user/actions';
import { DEFAULT_PROVIDER, IProvider, PROVIDERS } from '$constants/providers';
@ -37,15 +37,17 @@ interface IEditor {
owner: { id: string };
initialData: {
version: number,
title: string,
title: IRootState['title'],
owner: { id: string },
address: string,
address: IRootState['address'],
path: any,
route: any,
stickers: any,
provider: string,
is_public: boolean,
logo: string,
provider: IRootState['provider'],
is_public: IRootState['is_public'],
is_starred: IRootState['is_starred'],
description: IRootState['description'],
logo: IRootState['logo'],
};
activeSticker: IRootState['activeSticker'];
mode: IRootState['mode'];
@ -156,7 +158,9 @@ export class Editor {
route: null,
stickers: null,
provider: null,
is_public: null,
is_public: false,
is_starred: false,
description: '',
logo: null,
};
activeSticker: IEditor['activeSticker'];
@ -183,8 +187,10 @@ export class Editor {
setRouterPoints: typeof setRouterPoints = value => store.dispatch(setRouterPoints(value));
setActiveSticker: typeof setActiveSticker = value => store.dispatch(setActiveSticker(value));
setTitle: typeof setTitle = value => store.dispatch(setTitle(value));
setDescription: typeof setDescription = value => store.dispatch(setDescription(value));
setAddress: typeof setAddress = value => store.dispatch(setAddress(value));
setPublic: typeof setPublic = value => store.dispatch(setPublic(value));
setStarred: typeof setStarred = value => store.dispatch(setStarred(value));
setIsEmpty: typeof setIsEmpty = value => store.dispatch(setIsEmpty(value));
setIsRouting: typeof setIsRouting = value => store.dispatch(setIsRouting(value));
@ -305,7 +311,16 @@ export class Editor {
};
setData = ({
route = [], stickers = [], owner, title, address, provider = DEFAULT_PROVIDER, logo = DEFAULT_LOGO, is_public,
route = [],
stickers = [],
owner,
title,
address,
provider = DEFAULT_PROVIDER,
logo = DEFAULT_LOGO,
is_public,
is_starred,
description,
}: IEditor['initialData']): void => {
this.setTitle(title || '');
const { id } = this.getUser();
@ -332,6 +347,9 @@ export class Editor {
}
this.setPublic(is_public);
this.setStarred(is_starred);
this.setDescription(description);
this.setLogo((logo && LOGOS[DEFAULT_LOGO] && logo) || DEFAULT_LOGO);
this.setProvider((provider && PROVIDERS[provider] && provider) || DEFAULT_PROVIDER);
@ -348,7 +366,7 @@ export class Editor {
setInitialData = (): void => {
const { path } = getUrlData();
const { id } = this.getUser();
const { is_public, logo } = this.getState();
const { is_public, logo, is_starred , description} = this.getState();
const { route, stickers, provider } = this.dumpData();
this.initialData = {
@ -362,6 +380,8 @@ export class Editor {
provider,
is_public,
logo,
is_starred,
description,
};
};
@ -371,8 +391,6 @@ export class Editor {
this.setInitialData();
this.owner = { id };
// todo: implement
// if (this.poly.latlngs && this.poly.latlngs.length > 1) this.poly.poly.editor.enable();
this.poly.enableEditor();
this.stickers.startEditing();
};

View file

@ -81,16 +81,6 @@ export class Stickers {
dumpData = (): Array<IStickerDump> => this.stickers.map(sticker => sticker.dumpData());
// onSpiderify = (): void => {
// console.log('spider?');
// // todo: it has markers passed as argument. Update only them.
// if (this.editor.getEditing()) {
// this.startEditing();
// } else {
// this.stopEditing();
// }
// };
startEditing = (): void => {
this.stickers.map(sticker => sticker.startEditing());
};

View file

@ -1,20 +1,23 @@
import { ACTIONS } from '$redux/user/constants';
import { IUser } from "$constants/auth";
import { IRootState } from "$redux/user/reducer";
export const setUser = (user: IUser) => ({ type: ACTIONS.SET_USER, user });
export const userLogout = () => ({ type: ACTIONS.USER_LOGOUT });
export const setEditing = editing => ({ type: ACTIONS.SET_EDITING, editing });
export const setMode = mode => ({ type: ACTIONS.SET_MODE, mode });
export const setDistance = distance => ({ type: ACTIONS.SET_DISTANCE, distance });
export const setChanged = changed => ({ type: ACTIONS.SET_CHANGED, changed });
export const setEditing = (editing: IRootState['editing']) => ({ type: ACTIONS.SET_EDITING, editing });
export const setMode = (mode: IRootState['mode']) => ({ type: ACTIONS.SET_MODE, mode });
export const setDistance = (distance: IRootState['distance']) => ({ type: ACTIONS.SET_DISTANCE, distance });
export const setChanged = (changed: IRootState['changed']) => ({ type: ACTIONS.SET_CHANGED, changed });
export const setRouterPoints = routerPoints => ({ type: ACTIONS.SET_ROUTER_POINTS, routerPoints });
export const setActiveSticker = activeSticker => ({ type: ACTIONS.SET_ACTIVE_STICKER, activeSticker });
export const setLogo = logo => ({ type: ACTIONS.SET_LOGO, logo });
export const setTitle = title => ({ type: ACTIONS.SET_TITLE, title });
export const setDescription = description => ({ type: ACTIONS.SET_DESCRIPTION, description });
export const setAddress = address => ({ type: ACTIONS.SET_ADDRESS, address });
export const setAddressOrigin = address_origin => ({ type: ACTIONS.SET_ADDRESS_ORIGIN, address_origin });
export const setPublic = is_public => ({ type: ACTIONS.SET_PUBLIC, is_public });
export const setStarred = is_starred => ({ type: ACTIONS.SET_STARRED, is_starred });
export const setSpeed = speed => ({ type: ACTIONS.SET_SPEED, speed });
export const startEditing = () => ({ type: ACTIONS.START_EDITING });
@ -28,12 +31,31 @@ export const clearStickers = () => ({ type: ACTIONS.CLEAR_STICKERS });
export const clearAll = () => ({ type: ACTIONS.CLEAR_ALL });
export const clearCancel = () => ({ type: ACTIONS.CLEAR_CANCEL });
export const sendSaveRequest = payload => ({ type: ACTIONS.SEND_SAVE_REQUEST, ...payload });
export const sendSaveRequest = (payload: {
title: IRootState['title'],
address: IRootState['address'],
is_public: IRootState['is_public'],
description: IRootState['description'],
force: boolean,
}) => ({
type: ACTIONS.SEND_SAVE_REQUEST,
...payload,
});
export const resetSaveDialog = () => ({ type: ACTIONS.RESET_SAVE_DIALOG });
export const setSaveLoading = save_loading => ({ type: ACTIONS.SET_SAVE_LOADING, save_loading });
export const setSaveSuccess = payload => ({ type: ACTIONS.SET_SAVE_SUCCESS, ...payload });
export const setSaveError = save_error => ({ type: ACTIONS.SET_SAVE_ERROR, save_error });
export const setSaveLoading = (save_loading: IRootState['save_loading']) => ({ type: ACTIONS.SET_SAVE_LOADING, save_loading });
export const setSaveSuccess = (payload: {
address: IRootState['address'],
title: IRootState['address'],
is_public: IRootState['is_public'],
description: IRootState['description'],
save_error: string,
}) => ({ type: ACTIONS.SET_SAVE_SUCCESS, ...payload });
export const setSaveError = (save_error: IRootState['save_error']) => ({ type: ACTIONS.SET_SAVE_ERROR, save_error });
export const setSaveOverwrite = () => ({ type: ACTIONS.SET_SAVE_OVERWRITE });
export const hideRenderer = () => ({ type: ACTIONS.HIDE_RENDERER });

View file

@ -17,6 +17,8 @@ export const ACTIONS: IActions = {
SET_ADDRESS: 'SET_ADDRESS',
SET_ADDRESS_ORIGIN: 'SET_ADDRESS_ORIGIN',
SET_PUBLIC: 'SET_PUBLIC',
SET_STARRED: 'SET_STARRED',
SET_DESCRIPTION: 'SET_DESCRIPTION',
START_EDITING: 'START_EDITING',
STOP_EDITING: 'STOP_EDITING',

View file

@ -26,6 +26,7 @@ export interface IRootReducer {
logo: keyof typeof LOGOS,
routerPoints: number,
distance: number,
description: string,
estimated: number,
speed: number,
activeSticker: { set?: keyof IStickers, sticker?: string },
@ -36,6 +37,7 @@ export interface IRootReducer {
provider: keyof typeof PROVIDERS,
markers_shown: boolean,
is_starred: boolean,
is_public: boolean,
is_empty: boolean,
is_routing: boolean,
@ -140,6 +142,11 @@ const setTitle: ActionHandler<typeof ActionCreators.setTitle> = (state, { title
title
});
const setDescription: ActionHandler<typeof ActionCreators.setDescription> = (state, { description }) => ({
...state,
description
});
const setAddress: ActionHandler<typeof ActionCreators.setAddress> = (state, { address }) => ({
...state,
address
@ -172,7 +179,11 @@ const setSaveOverwrite: ActionHandler<typeof ActionCreators.setSaveOverwrite> =
});
const setSaveSuccess: ActionHandler<typeof ActionCreators.setSaveSuccess> = (state, { save_error }) => ({
...state, save_overwriting: false, save_finished: true, save_processing: false, save_error
...state,
save_overwriting: false,
save_finished: true,
save_processing: false,
save_error,
});
const resetSaveDialog: ActionHandler<typeof ActionCreators.resetSaveDialog> = (state) => ({
@ -276,6 +287,8 @@ const searchSetLoading: ActionHandler<typeof ActionCreators.searchSetLoading> =
});
const setPublic: ActionHandler<typeof ActionCreators.setPublic> = (state, { is_public = false }) => ({ ...state, is_public });
const setStarred: ActionHandler<typeof ActionCreators.setStarred> = (state, { is_starred = false }) => ({ ...state, is_starred });
const setSpeed: ActionHandler<typeof ActionCreators.setSpeed> = (state, { speed = 15 }) => ({
...state,
speed,
@ -330,6 +343,7 @@ const HANDLERS = ({
[ACTIONS.SET_ACTIVE_STICKER]: setActiveSticker,
[ACTIONS.SET_LOGO]: setLogo,
[ACTIONS.SET_TITLE]: setTitle,
[ACTIONS.SET_DESCRIPTION]: setDescription,
[ACTIONS.SET_ADDRESS]: setAddress,
[ACTIONS.SET_ADDRESS_ORIGIN]: setAddressOrigin,
@ -356,6 +370,7 @@ const HANDLERS = ({
[ACTIONS.SEARCH_PUT_ROUTES]: searchPutRoutes,
[ACTIONS.SEARCH_SET_LOADING]: searchSetLoading,
[ACTIONS.SET_PUBLIC]: setPublic,
[ACTIONS.SET_STARRED]: setStarred,
[ACTIONS.SET_SPEED]: setSpeed,
[ACTIONS.SET_MARKERS_SHOWN]: setMarkersShown,
@ -376,6 +391,7 @@ export const INITIAL_STATE: IRootReducer = {
logo: DEFAULT_LOGO,
routerPoints: 0,
distance: 0,
description: '',
estimated: 0,
speed: 15,
activeSticker: { set: null, sticker: null },
@ -387,6 +403,8 @@ export const INITIAL_STATE: IRootReducer = {
markers_shown: true,
changed: false,
editing: false,
is_starred: false,
is_public: false,
is_empty: true,
is_routing: false,

View file

@ -32,7 +32,7 @@ import {
setProvider,
changeProvider,
setSaveLoading,
mapsSetShift, searchChangeDistance, clearAll, setFeature, searchSetTitle, setRouteStarred,
mapsSetShift, searchChangeDistance, clearAll, setFeature, searchSetTitle, setRouteStarred, setDescription,
} from '$redux/user/actions';
import { getUrlData, parseQuery, pushLoaderState, pushNetworkInitError, pushPath, replacePath } from '$utils/history';
import { editor } from '$modules/Editor';
@ -307,7 +307,7 @@ function* clearSaga({ type }) {
}
function* sendSaveRequestSaga({
title, address, force, is_public
title, address, force, is_public, description,
}: ReturnType<typeof ActionCreators.sendSaveRequest>) {
if (editor.isEmpty) return yield put(setSaveError(TIPS.SAVE_EMPTY));
@ -319,7 +319,7 @@ function* sendSaveRequestSaga({
const { result, timeout, cancel } = yield race({
result: postMap({
id, token, route, stickers, title, force, address, logo, distance, provider, is_public
id, token, route, stickers, title, force, address, logo, distance, provider, is_public, description,
}),
timeout: delay(10000),
cancel: take(ACTIONS.RESET_SAVE_DIALOG),
@ -333,17 +333,15 @@ function* sendSaveRequestSaga({
if (timeout || !result || !result.success || !result.address) return yield put(setSaveError(TIPS.SAVE_TIMED_OUT));
return yield put(setSaveSuccess({
address: result.address, save_error: TIPS.SAVE_SUCCESS, title, is_public: result.is_public
address: result.address,
title: result.title,
is_public: result.is_public,
description: result.description,
save_error: TIPS.SAVE_SUCCESS,
}));
}
// function* refreshUserData() {
// const user = yield select(getUser);
// const data = yield call(checkUserToken, user);
//
// return yield put(setUser(data));
// }
function* getRenderData() {
yield put(setRenderer({ info: 'Загрузка тайлов', progress: 0.1 }));
@ -570,7 +568,7 @@ function* searchSetTabSaga() {
}
function* setSaveSuccessSaga({
address, title, is_public
address, title, is_public, description,
}: ReturnType<typeof ActionCreators.setSaveSuccess>) {
const { id } = yield select(getUser);
const { dialog_active } = yield select(getState);
@ -580,6 +578,7 @@ function* setSaveSuccessSaga({
yield put(setTitle(title));
yield put(setAddress(address));
yield put(setPublic(is_public));
yield put(setDescription(description));
yield put(setChanged(false));
yield editor.owner = { id };

View file

@ -641,27 +641,31 @@
.title-dialog {
z-index: 2;
opacity: 0;
transition: opacity 500ms, transform 1s;
transform: translate(0, 68px);
user-select: none;
pointer-events: all;
touch-action: auto;
display: flex;
flex-direction: column;
opacity: 0;
@media(max-width: @mobile_breakpoint) {
display: none;
}
&.active {
opacity: 0.5;
opacity: 1;
transform: translate(0, 0);
&:hover {
opacity: 1;
}
}
.title-dialog-pane {
margin-bottom: 10px;
padding: 10px;
background: #111111;
background: @dialog_background;
color: fade(white, 50%);
font-size: 13px;
box-sizing: border-box;
h2 {
margin: 0;
@ -671,4 +675,38 @@
color: white;
}
}
.title-dialog-text {
overflow: hidden;
transition: height 500ms;
line-height: 14px;
padding: 0;
position: relative;
> div {
margin: 10px;
white-space: pre-line;
}
&.has_shade {
::after {
content: ' ';
width: 100%;
height: 40px;
background: linear-gradient(fade(@dialog_background, 0), @dialog_background);
position: absolute;
bottom: 0;
left: 0;
transition: opacity 250ms;
pointer-events: none;
touch-action: none;
}
&:hover {
::after {
opacity: 0;
}
}
}
}
}

View file

@ -46,7 +46,7 @@
&.active {
opacity: 1;
touch-action: all;
touch-action: auto;
pointer-events: all;
svg {
@ -90,6 +90,7 @@
border-radius: 2px;
display: flex;
margin-bottom: 5px;
input {
padding: 5px 5px 5px 2px;
background: transparent;
@ -150,14 +151,15 @@
.save-description {
textarea {
background: rgba(0,0,0,0.3);
outline: none;
border: none;
border-radius: 3px;
width: 100%;
resize: none;
color: inherit;
font: inherit;
height: 5.5em;
padding: 0.25em;
padding: 5px 10px;
font-size: 14px;
}
}

View file

@ -21,6 +21,7 @@ interface IPostMap {
distance: IRootState['distance'],
provider: IRootState['provider'],
is_public: IRootState['is_public'],
description: IRootState['description'],
}
interface IGetRouteList {
@ -67,7 +68,7 @@ export const getStoredMap = (
));
export const postMap = ({
title, address, route, stickers, id, token, force, logo, distance, provider, is_public,
title, address, route, stickers, id, token, force, logo, distance, provider, is_public, description,
}: IPostMap) => axios.post(API.POST_MAP, {
title,
address,
@ -80,6 +81,7 @@ export const postMap = ({
distance,
provider,
is_public,
description,
}).then(result => (result && result.data && result.data));
export const checkIframeToken = (

9
src/utils/dom.ts Normal file
View file

@ -0,0 +1,9 @@
export const getStyle = (oElm: any, strCssRule: string): string => {
if(document.defaultView && document.defaultView.getComputedStyle){
return document.defaultView.getComputedStyle(oElm, '').getPropertyValue(strCssRule);
} else if(oElm.currentStyle){
return oElm.currentStyle[strCssRule.replace(/\-(\w)/g, (strMatch, p1) => p1.toUpperCase())];
}
return '';
};

View file

@ -13,3 +13,5 @@ export const toHours = (info: number): string => {
export const toTranslit = (string: string): string => (
removeGarbage(ru.reduce((text, el, i) => (text.replace(new RegExp(ru[i], 'g'), en[i])), (String(string) || '')))
);
export const parseDesc = (text: string): string => text.replace(/(\n{2,})/ig, "\n\n");

View file

@ -90,25 +90,14 @@ const distToSegmentSquared = (A: LatLng, B: LatLng, C: LatLng): number => {
};
export const distToSegment = (A: LatLng, B: LatLng, C: LatLng): number => Math.sqrt(distToSegmentSquared(A, B, C));
// if C between A and B
export const pointBetweenPoints = (A: LatLng, B: LatLng, C: LatLng): boolean => (distToSegment(A, B, C) < 0.01);
export const angleBetweenPoints = (A: Point, B: Point): number => parseFloat(((Math.atan2(B.y - A.y, B.x - A.x))* 180 / Math.PI).toFixed());
export const angleBetweenPointsRad = (A: Point, B: Point): number => ((Math.atan2(B.x - A.x, B.y - A.y)));
// export const pointOnDistance = (A: Point, B: Point, shift: number): Point => {
// const c = Math.sqrt((((B.x - A.x) ** 2) + ((B.y - A.y) ** 2)));
// const angle = angleBetweenPointsRad(A, B);
//
// // console.log({ angle, c, shift });
// const x = Math.floor(B.x - c * Math.sin(angle) * shift);
// const y = Math.floor(B.y - c * Math.cos(angle) * shift);
//
// // console.log({ x, y });
//
// return new Point(x, y);
// };
export const allwaysPositiveAngleDeg = (angle: number): number => (
(angle >= -90 && angle <= 90) ? angle : (180 + angle)
);
export const nearestInt = (value: number, parts: number): number => (value - (value % parts));