dialog-editor: drop and edit items

This commit is contained in:
muerwre 2019-03-01 10:46:18 +07:00
parent a2607b257d
commit 970d0967c8
10 changed files with 279 additions and 150 deletions

View file

@ -1,7 +1,7 @@
import * as React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { RouteRow } from '$components/maps/RouteRow';
import { RouteRowWrapper } from '$components/maps/RouteRowWrapper';
import { Scroll } from '$components/Scroll';
import {
searchSetDistance,
@ -21,6 +21,8 @@ import { IRootState, IRouteListItem } from '$redux/user/reducer';
export interface IMapListDialogProps extends IRootState {
marks: { [x: number]: string },
routes_sorted: Array<IRouteListItem>,
routes: IRootState['routes'],
ready: IRootState['ready'],
mapsLoadMore: typeof mapsLoadMore,
searchSetDistance: typeof searchSetDistance,
@ -30,32 +32,44 @@ export interface IMapListDialogProps extends IRootState {
}
export interface IMapListDialogState {
selected_item: IRouteListItem['_id'],
selected_item_mode: 'menu' | 'edit' | 'drop' | null,
menu_target: IRouteListItem['_id'],
editor_target: IRouteListItem['_id'],
is_editing: boolean,
is_dropping: boolean,
}
class Component extends React.Component<IMapListDialogProps, IMapListDialogState> {
state = {
selected_item: null,
selected_item_mode: null,
menu_target: null,
editor_target: null,
is_editing: false,
is_dropping: false,
};
startEditing = (selected_item: IRouteListItem['_id']): void => this.setState({
selected_item: ((this.state.selected_item !== selected_item && selected_item) || null),
selected_item_mode: 'edit',
startEditing = (editor_target: IRouteListItem['_id']): void => this.setState({
editor_target,
menu_target: null,
is_editing: true,
is_dropping: false,
});
showMenu = (selected_item: IRouteListItem['_id']): void => this.setState({
selected_item: ((this.state.selected_item !== selected_item && selected_item) || null),
selected_item_mode: 'menu',
showMenu = (menu_target: IRouteListItem['_id']): void => this.setState({
menu_target,
});
showDropCard = (selected_item: IRouteListItem['_id']): void => this.setState({
selected_item: ((this.state.selected_item !== selected_item && selected_item) || null),
selected_item_mode: 'drop',
showDropCard = (editor_target: IRouteListItem['_id']): void => this.setState({
editor_target,
menu_target: null,
is_editing: false,
is_dropping: true,
});
stopEditing = (): void => this.setState({ selected_item: null });
stopEditing = (): void => {
console.log('stop it!');
this.setState({ editor_target: null });
};
setTitle = ({ target: { value } }: { target: { value: string }}): void => {
this.props.searchSetTitle(value);
@ -64,6 +78,8 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
openRoute = (_id: string): void => {
if (isMobile()) this.props.setDialogActive(false);
this.stopEditing();
pushPath(`/${_id}/${this.props.editing ? 'edit' : ''}`);
};
@ -80,6 +96,8 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
}
};
dropRoute = (): void => null;
render() {
const {
ready,
@ -97,7 +115,7 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
marks,
}: IMapListDialogProps = this.props;
const { selected_item, selected_item_mode } = this.state;
const { editor_target, menu_target, is_editing, is_dropping } = this.state;
return (
<div className="dialog-content">
@ -166,19 +184,21 @@ class Component extends React.Component<IMapListDialogProps, IMapListDialogState
<div className="dialog-maplist">
{
list.map(route => (
<RouteRow
<RouteRowWrapper
title={route.title}
distance={route.distance}
_id={route._id}
is_public={route.is_public}
tab={tab}
selected={(selected_item === route._id)}
mode={selected_item_mode}
is_editing_mode={is_dropping ? 'drop' : 'edit'}
is_editing_target={editor_target === route._id}
is_menu_target={menu_target === route._id}
openRoute={this.openRoute}
startEditing={this.startEditing}
stopEditing={this.stopEditing}
showMenu={this.showMenu}
showDropCard={this.showDropCard}
dropRoute={this.dropRoute}
key={route._id}
/>
))

View file

@ -1,69 +0,0 @@
// @flow
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import classnames from 'classnames';
import { IMapListDialogState, MapListDialog } from "$components/dialogs/MapListDialog";
import { Tooltip } from "$components/panels/Tooltip";
interface Props {
_id: string,
tab: string,
selected: boolean,
title: string,
distance: number,
is_public: boolean,
mode: IMapListDialogState['selected_item_mode'],
openRoute: typeof MapListDialog.openRoute,
startEditing: typeof MapListDialog.startEditing,
stopEditing: typeof MapListDialog.stopEditing,
showMenu: typeof MapListDialog.showMenu,
showDropCard: typeof MapListDialog.showDropCard,
key: string,
}
export const RouteRow = ({
title, distance, _id, openRoute, tab, selected, startEditing, showMenu, showDropCard, mode
}: Props) => (
<div
className={classnames('route-row-wrapper', {
selected,
has_menu: selected && mode === 'menu',
has_drop: selected && mode === 'drop',
has_edit: selected && mode === 'edit',
})}
>
<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>
<div className="route-row-edit-button pointer" onClick={() => showMenu(_id)}>
<Icon icon="icon-more-vert" />
<div className="route-row-edit-menu">
<div onClick={() => showDropCard(_id)}>
<Tooltip>Удалить</Tooltip>
<Icon icon="icon-trash-3" size={32} />
</div>
<div onClick={() => startEditing(_id)}>
<Tooltip>Редактировать</Tooltip>
<Icon icon="icon-edit-1" size={32} />
</div>
</div>
</div>
</div>
);

View file

@ -0,0 +1,29 @@
// @flow
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import { MapListDialog } from "$components/dialogs/MapListDialog";
import { Tooltip } from "$components/panels/Tooltip";
import { ReactElement } from "react";
interface Props {
_id: string,
stopEditing: typeof MapListDialog.stopEditing,
dropRoute: typeof MapListDialog.dropRoute,
}
export const RouteRowDrop = ({
_id, stopEditing, dropRoute,
}: Props): ReactElement<Props, null> => (
<div
className="route-row-drop"
>
<div
className="route-row"
>
<div className="button-group">
<div className="button" onClick={() => dropRoute(_id)}>Удалить</div>
<div className="button primary" onClick={stopEditing}>Отмена</div>
</div>
</div>
</div>
);

View file

@ -5,9 +5,8 @@ import { Switch } from '$components/Switch';
interface Props {
title: string;
is_editing: boolean;
distance: number;
_id: string;
is_public: boolean,
}
interface State {
@ -32,13 +31,12 @@ export class RouteRowEditor extends React.Component<Props, State> {
render() {
const {
state: { title, is_public },
props: { distance, _id }
props: { _id }
} = this;
return (
<div
className="route-row"
>
<div className="route-row-edit">
<div className="route-row">
<div className="route-title">
<input
type="text"
@ -48,22 +46,10 @@ export class RouteRowEditor extends React.Component<Props, State> {
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}
/>
<Switch active={is_public} />
{
is_public
? ' В каталоге карт'
@ -76,6 +62,7 @@ export class RouteRowEditor extends React.Component<Props, State> {
</div>
</div>
</div>
</div>
);
}
}

View file

@ -0,0 +1,61 @@
// @flow
import * as React from 'react';
import { Icon } from '$components/panels/Icon';
import { MapListDialog } from "$components/dialogs/MapListDialog";
import { Tooltip } from "$components/panels/Tooltip";
import { ReactElement } from "react";
interface Props {
_id: string,
tab: string,
title: string,
distance: number,
is_public: boolean,
openRoute: typeof MapListDialog.openRoute,
startEditing: typeof MapListDialog.startEditing,
stopEditing: typeof MapListDialog.stopEditing,
showMenu: typeof MapListDialog.showMenu,
showDropCard: typeof MapListDialog.showDropCard,
}
export const RouteRowView = ({
title, distance, _id, openRoute, tab, startEditing, showMenu, showDropCard
}: Props): ReactElement<Props, null> => (
<div
className="route-row-view"
>
<div
className="route-row"
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-edit-button pointer" onClick={() => showMenu(_id)}>
<Icon icon="icon-more-vert" />
</div>
<div className="route-row-edit-menu pointer">
<div onMouseDown={() => showDropCard(_id)}>
<Tooltip>Удалить</Tooltip>
<Icon icon="icon-trash-3" size={32} />
</div>
<div onMouseDown={() => startEditing(_id)}>
<Tooltip>Редактировать</Tooltip>
<Icon icon="icon-edit-1" size={32} />
</div>
</div>
</div>
);

View file

@ -0,0 +1,72 @@
import * as React from 'react';
import classnames from 'classnames';
import { MapListDialog } from "$components/dialogs/MapListDialog";
import { RouteRowView } from "$components/maps/RouteRowView";
import { RouteRowEditor } from "$components/maps/RouteRowEditor";
import { RouteRowDrop } from "$components/maps/RouteRowDrop";
import { ReactElement } from "react";
interface Props {
_id: string,
tab: string,
title: string,
distance: number,
is_public: boolean,
is_editing_target: boolean,
is_menu_target: boolean,
openRoute: typeof MapListDialog.openRoute,
startEditing: typeof MapListDialog.startEditing,
stopEditing: typeof MapListDialog.stopEditing,
showMenu: typeof MapListDialog.showMenu,
showDropCard: typeof MapListDialog.showDropCard,
dropRoute: typeof MapListDialog.dropRoute,
is_editing_mode: 'edit' | 'drop',
}
export const RouteRowWrapper = ({
title, distance, _id, openRoute, tab, startEditing, showMenu,
showDropCard, is_public, is_editing_target, is_menu_target, is_editing_mode,
dropRoute, stopEditing,
}: Props): ReactElement<Props, null> => (
<div
className={classnames('route-row-wrapper', {
is_menu_target,
is_editing_target,
})}
>
{
is_editing_target && is_editing_mode === 'edit' &&
<RouteRowEditor
title={title}
_id={_id}
is_public={is_public}
/>
}
{
is_editing_target && is_editing_mode === 'drop' &&
<RouteRowDrop
_id={_id}
dropRoute={dropRoute}
stopEditing={stopEditing}
/>
}
{
!is_editing_target &&
<RouteRowView
_id={_id}
tab={tab}
title={title}
distance={distance}
is_public={is_public}
openRoute={openRoute}
startEditing={startEditing}
stopEditing={stopEditing}
showMenu={showMenu}
showDropCard={showDropCard}
/>
}
</div>
);

View file

@ -1,11 +1,10 @@
import { Map, LayerGroup, LatLng, LatLngLiteral, LeafletEventHandlerFn, marker, divIcon, Marker } from 'leaflet';
import { Map, LatLng } from 'leaflet';
import { EditablePolyline } from '$utils/EditablePolyline';
import { simplify } from '$utils/simplify';
import { CLIENT } from '$config/frontend';
import { editor, Editor } from "$modules/Editor";
import { ILatLng } from "$modules/Stickers";
import { InteractivePoly } from "$modules/InteractivePoly";
import { clusterIcon } from "$utils/clusterIcon";
import { MarkerClusterGroup } from 'leaflet.markercluster/dist/leaflet.markercluster-src.js';
import { angleBetweenPoints, dist2, distToSegment, middleCoord } from "$utils/geom";
import { arrowClusterIcon, createArrow } from "$utils/arrow";
@ -28,12 +27,12 @@ export class Poly {
weight: 6,
maxMarkers: 100,
smoothFactor: 3,
// bubblingMouseEvents: false,
})
.on('distancechange', this.onDistanceUpdate)
.on('allvertexhide', this.onVertexHide)
.on('allvertexshow', this.onVertexShow)
.on('latlngschange', this.updateArrows);
.on('latlngschange', this.updateArrows)
.on('latlngschange', triggerOnChange);
this.poly.addTo(map);
this.editor = editor;
@ -162,7 +161,7 @@ export class Poly {
return (!this.latlngs || Object.values(this.latlngs).length <= 0);
}
poly: EditablePolyline;
poly;
arrowLayer: MarkerClusterGroup = new MarkerClusterGroup({
spiderfyOnMaxZoom: false,
showCoverageOnHover: false,

View file

@ -13,6 +13,7 @@
box-shadow: inset rgba(100,100,100, 0.3) 1px 0, inset rgba(0,0,0, 0.1) -1px 0;
color: white;
font-weight: 400;
border: none;
&.primary {
background: #3c78db;

View file

@ -188,13 +188,14 @@
margin-bottom: 10px;
transition: all 500ms;
display: flex;
flex-direction: column;
&.has_edit {
//transform: translateY(-2px);
.route-row { background: rgba(255, 100, 100, 0.2); }
.route-row { background: fade(@green_secondary, 30%); }
}
&.has_menu {
&.is_menu_target {
.route-row {
transform: translateX(-100px);
}
@ -204,18 +205,37 @@
}
}
}
//
//.route-row-editor {
// color: white;
// padding: 20px 0 5px;
//}
//
//.route-row-buttons {
// flex: 1;
// flex-direction: row;
// display: flex;
// align-items: center;
//}
.route-row-editor {
color: white;
padding: 5px 0 5px;
}
.route-row-buttons {
flex: 1;
flex-direction: row;
display: flex;
align-items: center;
}
.route-row-view {
overflow: hidden;
transition: height 500ms;
padding-right: 32px;
position: relative;
}
.route-row-edit {
background: fade(@green_secondary, 30%);
}
.route-row-drop {
background: fade(@red_secondary, 20%);
.route-row {
align-items: center;
}
}
.route-row {
background: rgba(255, 255, 255, 0.05);
@ -226,6 +246,12 @@
transition: background 250ms, transform 500ms;
position: relative;
flex: 1;
min-height: 64px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
align-items: stretch;
&:hover {
background: rgba(255, 255, 255, 0.1);
@ -246,7 +272,10 @@
justify-content: center;
align-items: center;
transition: all 250ms;
position: relative;
position: absolute;
top: 0;
right: 0;
height: 100%;
&:hover {
background: rgba(255, 255, 255, 0.1);
@ -256,13 +285,14 @@
.route-row-edit-menu {
width: 0;
height: 100%;
right: 100%;
right: 32px;
bottom: 0;
position: absolute;
background: rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: all 500ms;
display: flex;
fill: fade(white, 30%);
div {
width: 50px;

View file

@ -41,7 +41,6 @@ const resolve = {
$utils: join(__dirname, 'src/utils'),
$modules: join(__dirname, 'src/modules'),
},
extensions: ['*', '.ts', '.tsx', '.js', '.jsx', '.json']
};