From 32e1b4c3fdf4a61950406dbbaaae4d36d1ef8407 Mon Sep 17 00:00:00 2001 From: muerwre <gotham48@gmail.com> Date: Mon, 11 Feb 2019 15:36:55 +0700 Subject: [PATCH] basic UI for row editing --- backend/routes/auth/check.js | 18 ++--- backend/routes/route/list.js | 4 +- src/components/Switch.jsx | 1 - src/components/dialogs/MapListDialog.jsx | 25 ++++++- src/components/maps/RouteRow.jsx | 80 ++++++++++++++-------- src/components/maps/RouteRowEditor.jsx | 81 ++++++++++++++++++++++ src/components/panels/UserPanel.jsx | 3 - src/modules/Editor.js | 2 +- src/redux/user/sagas.js | 4 +- src/sprites/icon.svg | 2 +- src/styles/colors.less | 2 +- src/styles/dialogs.less | 87 ++++++++++++++++++++---- src/styles/main.less | 4 ++ 13 files changed, 248 insertions(+), 65 deletions(-) create mode 100644 src/components/maps/RouteRowEditor.jsx diff --git a/backend/routes/auth/check.js b/backend/routes/auth/check.js index 858ea73..76c07f3 100644 --- a/backend/routes/auth/check.js +++ b/backend/routes/auth/check.js @@ -4,15 +4,15 @@ const { generateGuest, generateRandomUrl } = require('./guest'); module.exports = async (req, res) => { const { id, token } = req.query; - const user = await User.findOne({ _id: id, token }) - .populate({ - path: 'routes', - select: '_id title distance owner updated_at', - options: { - limit: 200, - sort: { updated_at: -1 }, - } - }); + const user = await User.findOne({ _id: id, token }); + // .populate({ + // path: 'routes', + // select: '_id title distance owner updated_at', + // options: { + // limit: 200, + // sort: { updated_at: -1 }, + // } + // }) const random_url = await generateRandomUrl(); diff --git a/backend/routes/route/list.js b/backend/routes/route/list.js index 423ad95..13c7fab 100644 --- a/backend/routes/route/list.js +++ b/backend/routes/route/list.js @@ -32,12 +32,12 @@ module.exports = async (req, res) => { { ...criteria, }, - '_id title distance owner updated_at', + '_id title distance owner updated_at is_public', { limit: 500, sort: { updated_at: -1 }, } - ).populate('owner'); + ).populate('owner', '_id'); list = list.filter(item => ( !author || item.owner._id === author diff --git a/src/components/Switch.jsx b/src/components/Switch.jsx index 022487b..d65662b 100644 --- a/src/components/Switch.jsx +++ b/src/components/Switch.jsx @@ -4,7 +4,6 @@ import classnames from 'classnames'; type Props = { active: Boolean, - onPress: Function, } export const Switch = ({ active, onPress = () => {} }: Props) => ( diff --git a/src/components/dialogs/MapListDialog.jsx b/src/components/dialogs/MapListDialog.jsx index a28dbee..36047cc 100644 --- a/src/components/dialogs/MapListDialog.jsx +++ b/src/components/dialogs/MapListDialog.jsx @@ -28,8 +28,11 @@ type Props = { author: String, distance: Array<Number>, tab: Array<string>, - } + min: number, + max: number, + }, }, + marks: { [x: number]: string }, editing: Boolean, routes_sorted: Array<string>, @@ -39,7 +42,18 @@ type Props = { setDialogActive: Function, }; -class Component extends React.Component<Props> { +type State = { + editing_item: ?string, +} + +class Component extends React.Component<Props, State> { + state = { + editing_item: null, + }; + + startEditing = editing_item => this.setState({ editing_item }); + stopEditing = () => this.setState({ editing_item: null }); + setTitle = ({ target: { value } }) => { this.props.searchSetTitle(value); }; @@ -67,6 +81,8 @@ class Component extends React.Component<Props> { marks, } = this.props; + const { editing_item } = this.state; + return ( <div className="dialog-content"> { list.length === 0 && loading && @@ -136,9 +152,12 @@ class Component extends React.Component<Props> { <RouteRow editing={editing} {...route} - key={route._id} openRoute={this.openRoute} tab={tab} + is_editing={(editing_item === route._id)} + startEditing={this.startEditing} + stopEditing={this.stopEditing} + key={route._id} /> )) } diff --git a/src/components/maps/RouteRow.jsx b/src/components/maps/RouteRow.jsx index bdd1128..340f250 100644 --- a/src/components/maps/RouteRow.jsx +++ b/src/components/maps/RouteRow.jsx @@ -1,41 +1,67 @@ // @flow import React from 'react'; import { Icon } from '$components/panels/Icon'; +import classnames from 'classnames'; +import { RouteRowEditor } from '$components/maps/RouteRowEditor'; type Props = { - title: String, - distance: Number, - created_at: String, - _id: String, - editing: Boolean, + _id: string, + title: string, + distance: number, + tab: string, + is_editing: boolean, + is_public: boolean, - openRoute: Function, + + openRoute: (_id: string) => {}, + startEditing: (_id: string) => {}, + stopEditing: () => {}, }; export const RouteRow = ({ - title, distance, _id, openRoute, tab, + title, distance, _id, openRoute, tab, is_editing, startEditing, stopEditing, is_public }: Props) => ( - <div className="route-row-wrapper"> - <div - className="route-row" - onClick={() => openRoute(_id)} - > - <div className="route-row-edit"> + <div className={classnames('route-row-wrapper', { is_editing })}> + { + tab === 'mine' && + <div className="route-row-edit" onClick={() => startEditing(_id)}> <Icon icon="icon-edit-1" /> </div> - <div className="route-title"> - {title || _id} - </div> - <div className="route-description"> - <span> - <Icon icon="icon-link-1" /> - {_id} - </span> - <span> - <Icon icon="icon-cycle-1" /> - {(distance && `${distance} km`) || '0 km'} - </span> - </div> - </div> + } + { + !is_editing + ? + <div + className="route-row" + > + <div onClick={() => openRoute(_id)}> + <div className="route-title"> + <span>{(title || _id)}</span> + </div> + <div className="route-description"> + <span> + <Icon icon="icon-link-1" /> + {_id} + </span> + <span> + <Icon icon="icon-cycle-1" /> + {(distance && `${distance} km`) || '0 km'} + </span> + </div> + </div> + <div className="route-row-panel"> + <div className=""> + <Icon icon="icon-trash-4" size={24} /> + Удалить + </div> + <div className="flex_1 justify-end" onClick={() => startEditing(_id)}> + <Icon icon="icon-edit-1" size={24} /> + Правка + </div> + </div> + </div> + : <RouteRowEditor title={title} is_public={is_public} distance={distance} _id={_id} /> + } + </div> ); diff --git a/src/components/maps/RouteRowEditor.jsx b/src/components/maps/RouteRowEditor.jsx new file mode 100644 index 0000000..9f2d7d7 --- /dev/null +++ b/src/components/maps/RouteRowEditor.jsx @@ -0,0 +1,81 @@ +// @flow +import React from 'react'; +import { Icon } from '$components/panels/Icon'; +import { Switch } from '$components/Switch'; + +type Props = { + title: string, + is_public: boolean, + distance: number, + _id: string, +}; + +type State = { + title: string, + is_public: boolean, +}; + +export class RouteRowEditor extends React.PureComponent<Props, State> { + constructor(props) { + super(props); + + this.state = { + title: props.title, + is_public: props.is_public, + }; + } + + stopEditing = () => console.log(); + setPublic = () => this.setState({ is_public: !this.state.is_public }); + setTitle = ({ target: { value } }: { target: { value: string } }) => this.setState({ title: value }); + + render() { + const { + state: { title, is_public }, + props: { distance, _id } + } = this; + + return ( + <div + className="route-row" + > + <div className="route-title"> + <input + type="text" + value={title} + onChange={this.setTitle} + placeholder="Введите название" + autoFocus + /> + </div> + <div className="route-description"> + <span> + <Icon icon="icon-link-1" /> + {_id} + </span> + <span> + <Icon icon="icon-cycle-1" /> + {(distance && `${distance} km`) || '0 km'} + </span> + </div> + <div className="route-row-editor"> + <div className="route-row-buttons"> + <div className="flex_1" onClick={this.setPublic}> + <Switch + active={is_public} + /> + { + is_public + ? ' В каталоге карт' + : ' Только по ссылке' + } + </div> + <div className="button primary" onClick={this.stopEditing}> + OK + </div> + </div> + </div> + </div> + ); + } +} diff --git a/src/components/panels/UserPanel.jsx b/src/components/panels/UserPanel.jsx index 06e6e1c..6905ce6 100644 --- a/src/components/panels/UserPanel.jsx +++ b/src/components/panels/UserPanel.jsx @@ -92,8 +92,6 @@ export class Component extends React.PureComponent<Props, void> { state: { menuOpened }, } = this; - const route_count = Object.keys(user.routes).length; - return ( <div> <div className="panel active panel-user"> @@ -114,7 +112,6 @@ export class Component extends React.PureComponent<Props, void> { <div className="control-bar"> <button className={classnames({ - disabled: route_count <= 0, active: dialog_active && (dialog === DIALOGS.MAP_LIST) })} onClick={this.openMapsDialog} diff --git a/src/modules/Editor.js b/src/modules/Editor.js index 21bcc8b..65cddd9 100644 --- a/src/modules/Editor.js +++ b/src/modules/Editor.js @@ -228,7 +228,7 @@ export class Editor { }; setData = ({ - route = [], stickers = [], owner, title, address, provider = DEFAULT_PROVIDER, logo = DEFAULT_LOGO, public: is_public, + route = [], stickers = [], owner, title, address, provider = DEFAULT_PROVIDER, logo = DEFAULT_LOGO, is_public, }) => { this.setTitle(title || ''); const { id } = this.getUser(); diff --git a/src/redux/user/sagas.js b/src/redux/user/sagas.js index 7a622b9..718d788 100644 --- a/src/redux/user/sagas.js +++ b/src/redux/user/sagas.js @@ -17,7 +17,7 @@ import { setSaveError, setSaveOverwrite, setSaveSuccess, setTitle, searchSetTab, - setUser, setDialog, setPublic, setAddressOrigin, setProvider, changeProvider, + setUser, setDialog, setPublic, setAddressOrigin, setProvider, changeProvider, openMapDialog, } from '$redux/user/actions'; import { getUrlData, parseQuery, pushLoaderState, pushNetworkInitError, pushPath, replacePath } from '$utils/history'; import { editor } from '$modules/Editor'; @@ -70,8 +70,6 @@ function* startEmptyEditorSaga() { } function* startEditingSaga() { - // yield put(setEditing(true)); - // yield editor.startEditing(); const { path } = getUrlData(); yield pushPath(`/${path}/edit`); } diff --git a/src/sprites/icon.svg b/src/sprites/icon.svg index 604873a..6e38daa 100644 --- a/src/sprites/icon.svg +++ b/src/sprites/icon.svg @@ -375,5 +375,5 @@ </svg> </defs> - <use xlink:href="#icon-edit-1" /> + <use xlink:href="#icon-sad-1" /> </svg> diff --git a/src/styles/colors.less b/src/styles/colors.less index 03de625..f06ba7a 100644 --- a/src/styles/colors.less +++ b/src/styles/colors.less @@ -4,7 +4,7 @@ @router_line: #4597d0; @bar_background: #333333; -@dialog_background: #222222; +@dialog_background: #271535; @location_line: #ff3344; diff --git a/src/styles/dialogs.less b/src/styles/dialogs.less index 7f71e5e..3217deb 100644 --- a/src/styles/dialogs.less +++ b/src/styles/dialogs.less @@ -71,7 +71,7 @@ } .dialog-content { - background: #271535; + background: @dialog_background; height: 100%; overflow: hidden; position: relative; @@ -163,21 +163,40 @@ } .route-row-wrapper { - overflow: hidden; padding: 0 10px; + position: relative; + margin-bottom: 10px; + transition: all 500ms; - &:hover { - .route-row { - transform: translateX(-48px); - } - .route-row-edit { - opacity: 1; - } + //&:hover { + // .route-row { transform: translateX(-58px); } + // .route-row-edit { transform: translateX(-58px); } + // + // &.is_editing { + // .route-row { transform: translateX(0); } + // .route-row-edit { transform: translateX(0); } + // } + //} + + &.is_editing { + //transform: translateY(-2px); + .route-row { background: rgba(255, 100, 100, 0.2); } } } +.route-row-editor { + color: white; + padding: 20px 0 5px; +} + +.route-row-buttons { + flex: 1; + flex-direction: row; + display: flex; + align-items: center; +} + .route-row { - margin-bottom: 10px; background: rgba(255, 255, 255, 0.05); padding: 10px 10px 5px 10px; color: white; @@ -188,23 +207,63 @@ &:hover { background: rgba(255, 255, 255, 0.1); + + .route-row-panel { + transform: scaleY(1); + pointer-events: all; + touch-action: initial; + } + } +} + +.route-row-panel { + position: absolute; + top: 100%; + height: 32px; + width: 100%; + left: 0; + background: mix(@dialog_background, white, 80%); + border-radius: 0 0 @panel_radius @panel_radius; + z-index: 1; + transform: scaleY(0); + pointer-events: none; + touch-action: none; + transition: transform 250ms; + transform-origin: 0 0; + padding: 0 5px; + box-sizing: border-box; + display: flex; + align-items: center; + fill: white; + + & > div { + display: flex; + align-items: center; + + svg { + margin-right: 2px; + } } } .route-row-edit { - fill: rgba(0, 0, 0, 0.5); - left: 100%; + fill: rgba(255, 255, 255, 0.3); + right: -48px; + padding-left: 0px; stroke: none; position: absolute; top: 0; width: 58px; - background: rgba(255, 255, 255, 0.2); height: 100%; - opacity: 0; transition: all 500ms; display: flex; align-items: center; justify-content: center; + cursor: pointer; + + &:hover { + fill: @green_secondary; + } } .route-title { diff --git a/src/styles/main.less b/src/styles/main.less index eb8364a..f521dd8 100644 --- a/src/styles/main.less +++ b/src/styles/main.less @@ -153,3 +153,7 @@ input { .relative { position: relative; } + +.justify-end { + justify-content: flex-end; +}