diff --git a/backend/models/Route.js b/backend/models/Route.js index 32c3696..be39b3e 100644 --- a/backend/models/Route.js +++ b/backend/models/Route.js @@ -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; diff --git a/backend/routes/route/post.js b/backend/routes/route/post.js index f2ac8af..e1251f4 100644 --- a/backend/routes/route/post.js +++ b/backend/routes/route/post.js @@ -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, }); }; diff --git a/package-lock.json b/package-lock.json index 098fac2..c0a0544 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index df3a790..6c21f09 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/dialogs/SaveDialog.tsx b/src/components/dialogs/SaveDialog.tsx index ff81319..aa1f521 100644 --- a/src/components/dialogs/SaveDialog.tsx +++ b/src/components/dialogs/SaveDialog.tsx @@ -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 { @@ -35,6 +40,7 @@ export class SaveDialog extends React.Component { 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 { }; 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 { }; 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 { +
+ +
{ save_error || TIPS.SAVE_INFO }
diff --git a/src/components/dialogs/TitleDialog.tsx b/src/components/dialogs/TitleDialog.tsx index 91291c0..3537c11 100644 --- a/src/components/dialogs/TitleDialog.tsx +++ b/src/components/dialogs/TitleDialog.tsx @@ -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 { 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 (
-
{ this.sizer = el; }}> -
-
+
{ this.ref_sizer = el; }}> +
+
{ this.ref_title = el; }} + >

{title}

-
- Давно выяснено, что при оценке дизайна и композиции читаемый текст мешает сосредоточиться. Lorem Ipsum используют потому, что тот обеспечивает более или менее стандартное заполнение шаблона, а также реальное распределение букв и пробелов в абзацах, которое не получается при простой дубликации "Здесь ваш текст.. Здесь ваш текст.. Здесь ваш текст.." Многие программы электронной вёрстки и редакторы HTML используют Lorem Ipsum в качестве текста по умолчанию, так что поиск по + +
height })} + style={{ + height: (raised ? height_raised : height), + marginBottom: height === 0 ? 0 : 15, + }} + ref={el => { this.ref_overflow = el; }} + > +
{ this.ref_text = el; }} + > + { + parseDesc(description) + } +
@@ -40,11 +109,13 @@ export class Component extends React.PureComponent ({ editing, title }); +const mapStateToProps = ({ user: { editing, title, description } }) => ({ editing, title, description }); const mapDispatchToProps = dispatch => bindActionCreators({ }, dispatch); export const TitleDialog = connect(mapStateToProps, mapDispatchToProps)(Component); diff --git a/src/components/panels/EditorDialog.tsx b/src/components/panels/EditorDialog.tsx index c1e3b8d..d4b2b45 100644 --- a/src/components/panels/EditorDialog.tsx +++ b/src/components/panels/EditorDialog.tsx @@ -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'; diff --git a/src/components/panels/UserPanel.tsx b/src/components/panels/UserPanel.tsx index 25665f4..b3ad9dd 100644 --- a/src/components/panels/UserPanel.tsx +++ b/src/components/panels/UserPanel.tsx @@ -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 { return (
{ - // + }
diff --git a/src/index.tsx b/src/index.tsx index 10a0f87..82c2ccf 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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 diff --git a/src/modules/Editor.ts b/src/modules/Editor.ts index 02a1165..00fcb5d 100644 --- a/src/modules/Editor.ts +++ b/src/modules/Editor.ts @@ -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(); }; diff --git a/src/modules/Stickers.ts b/src/modules/Stickers.ts index 6989352..907344e 100644 --- a/src/modules/Stickers.ts +++ b/src/modules/Stickers.ts @@ -81,16 +81,6 @@ export class Stickers { dumpData = (): Array => 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()); }; diff --git a/src/redux/user/actions.ts b/src/redux/user/actions.ts index 9dd9a00..ae42b87 100644 --- a/src/redux/user/actions.ts +++ b/src/redux/user/actions.ts @@ -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 }); diff --git a/src/redux/user/constants.ts b/src/redux/user/constants.ts index 2429a38..3dc2869 100644 --- a/src/redux/user/constants.ts +++ b/src/redux/user/constants.ts @@ -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', diff --git a/src/redux/user/reducer.ts b/src/redux/user/reducer.ts index d356f23..00d2aaa 100644 --- a/src/redux/user/reducer.ts +++ b/src/redux/user/reducer.ts @@ -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 = (state, { title title }); +const setDescription: ActionHandler = (state, { description }) => ({ + ...state, + description +}); + const setAddress: ActionHandler = (state, { address }) => ({ ...state, address @@ -172,7 +179,11 @@ const setSaveOverwrite: ActionHandler = }); const setSaveSuccess: ActionHandler = (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 = (state) => ({ @@ -276,6 +287,8 @@ const searchSetLoading: ActionHandler = }); const setPublic: ActionHandler = (state, { is_public = false }) => ({ ...state, is_public }); +const setStarred: ActionHandler = (state, { is_starred = false }) => ({ ...state, is_starred }); + const setSpeed: ActionHandler = (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, diff --git a/src/redux/user/sagas.ts b/src/redux/user/sagas.ts index 90d1489..b608a43 100644 --- a/src/redux/user/sagas.ts +++ b/src/redux/user/sagas.ts @@ -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) { 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) { 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 }; diff --git a/src/styles/panel.less b/src/styles/panel.less index ade2a81..ad555a3 100644 --- a/src/styles/panel.less +++ b/src/styles/panel.less @@ -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; + } + } + } + } } diff --git a/src/styles/save.less b/src/styles/save.less index ef1cb06..fcdfcc4 100644 --- a/src/styles/save.less +++ b/src/styles/save.less @@ -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; } } diff --git a/src/utils/api.ts b/src/utils/api.ts index 66650c1..d8528a6 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -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 = ( diff --git a/src/utils/dom.ts b/src/utils/dom.ts new file mode 100644 index 0000000..25eb73a --- /dev/null +++ b/src/utils/dom.ts @@ -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 ''; +}; diff --git a/src/utils/format.ts b/src/utils/format.ts index f0c55a5..5710576 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -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"); diff --git a/src/utils/geom.ts b/src/utils/geom.ts index 7c21275..6517084 100644 --- a/src/utils/geom.ts +++ b/src/utils/geom.ts @@ -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));