mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-25 02:56:41 +07:00
dialog-editor: drop and edit items
This commit is contained in:
parent
a2607b257d
commit
970d0967c8
10 changed files with 279 additions and 150 deletions
|
@ -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}
|
||||
/>
|
||||
))
|
||||
|
|
|
@ -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>
|
||||
);
|
29
src/components/maps/RouteRowDrop.tsx
Normal file
29
src/components/maps/RouteRowDrop.tsx
Normal 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>
|
||||
);
|
|
@ -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,46 +31,34 @@ 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-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 className="route-row-edit">
|
||||
<div className="route-row">
|
||||
<div className="route-title">
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={this.setTitle}
|
||||
placeholder="Введите название"
|
||||
autoFocus
|
||||
/>
|
||||
</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>
|
||||
|
|
61
src/components/maps/RouteRowView.tsx
Normal file
61
src/components/maps/RouteRowView.tsx
Normal 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>
|
||||
);
|
72
src/components/maps/RouteRowWrapper.tsx
Normal file
72
src/components/maps/RouteRowWrapper.tsx
Normal 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>
|
||||
);
|
|
@ -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";
|
||||
|
@ -23,17 +22,17 @@ export class Poly {
|
|||
constructor({
|
||||
map, routerMoveStart, lockMapClicks, setDistance, triggerOnChange, editor,
|
||||
}: Props) {
|
||||
this.poly = new InteractivePoly([], {
|
||||
this.poly = new InteractivePoly([ ], {
|
||||
color: 'url(#activePathGradient)',
|
||||
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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -41,7 +41,6 @@ const resolve = {
|
|||
$utils: join(__dirname, 'src/utils'),
|
||||
$modules: join(__dirname, 'src/modules'),
|
||||
},
|
||||
|
||||
extensions: ['*', '.ts', '.tsx', '.js', '.jsx', '.json']
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue