mirror of
https://github.com/muerwre/orchidmap-front.git
synced 2025-04-25 11:06:40 +07:00
save: save dialog and sagas
This commit is contained in:
parent
b586663827
commit
8fcca6587e
10 changed files with 161 additions and 88 deletions
|
@ -11,7 +11,6 @@ import { CancelDialog } from '$components/save/CancelDialog';
|
|||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import {
|
||||
setMode,
|
||||
setLogo,
|
||||
|
@ -24,12 +23,13 @@ import {
|
|||
clearCancel,
|
||||
stopEditing,
|
||||
setEditing,
|
||||
sendSaveRequest,
|
||||
} from '$redux/user/actions';
|
||||
|
||||
type Props = {
|
||||
mode: String,
|
||||
activeSticker: String,
|
||||
windth: Number,
|
||||
width: Number,
|
||||
}
|
||||
|
||||
export const Component = (props: Props) => {
|
||||
|
@ -83,6 +83,7 @@ const mapDispatchToProps = dispatch => bindActionCreators({
|
|||
stopEditing,
|
||||
setEditing,
|
||||
setMode,
|
||||
sendSaveRequest,
|
||||
}, dispatch);
|
||||
|
||||
export const EditorDialog = connect(
|
||||
|
|
|
@ -3,86 +3,63 @@ import { getUrlData, pushPath } from '$utils/history';
|
|||
import { toTranslit } from '$utils/format';
|
||||
import { TIPS } from '$constants/tips';
|
||||
import { MODES } from '$constants/modes';
|
||||
import { postMap } from '$utils/api';
|
||||
|
||||
import classnames from 'classnames';
|
||||
type Props = {
|
||||
address: String, // initial?
|
||||
title: String, // initial?
|
||||
|
||||
export class SaveDialog extends React.Component {
|
||||
save_error: String,
|
||||
save_finished: Boolean,
|
||||
save_overwriting: Boolean,
|
||||
save_processing: Boolean,
|
||||
|
||||
setMode: Function,
|
||||
sendSaveRequest: Function,
|
||||
};
|
||||
|
||||
type State = {
|
||||
address: String,
|
||||
title: String,
|
||||
};
|
||||
|
||||
export class SaveDialog extends React.Component<Props, State> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
address: props.address || '',
|
||||
title: props.title || '',
|
||||
error: '',
|
||||
sending: false,
|
||||
finished: false,
|
||||
overwriting: false,
|
||||
};
|
||||
}
|
||||
|
||||
getAddress = () => {
|
||||
const { path } = getUrlData();
|
||||
const { title, address } = this.state;
|
||||
return toTranslit(address.trim()) || toTranslit(title.trim()) || toTranslit(path.trim());
|
||||
|
||||
return toTranslit(address.trim()) || toTranslit(title.trim().toLowerCase()) || toTranslit(path.trim());
|
||||
};
|
||||
|
||||
setTitle = ({ target: { value } }) => this.setState({ title: (value || '') });
|
||||
|
||||
setAddress = ({ target: { value } }) => this.setState({ address: (value || '') });
|
||||
|
||||
cancelSaving = () => this.props.editor.changeMode(MODES.NONE);
|
||||
// cancelSaving = () => this.props.editor.changeMode(MODES.NONE);
|
||||
cancelSaving = () => this.props.setMode(MODES.NONE);
|
||||
|
||||
sendSaveRequest = (e, force = false) => {
|
||||
const { route, stickers } = this.props.editor.dumpData();
|
||||
const { title } = this.state;
|
||||
const { id, token } = this.props.user;
|
||||
const address = this.getAddress();
|
||||
|
||||
postMap({
|
||||
id,
|
||||
token,
|
||||
route,
|
||||
stickers,
|
||||
title,
|
||||
force,
|
||||
address: this.getAddress(),
|
||||
}).then(this.parseResponse).catch(console.warn);
|
||||
this.props.sendSaveRequest({
|
||||
title, address, force,
|
||||
});
|
||||
};
|
||||
|
||||
forceSaveRequest = e => this.sendSaveRequest(e, true);
|
||||
|
||||
parseResponse = data => {
|
||||
if (data.success) return this.setSuccess(data);
|
||||
if (data.mode === 'overwriting') return this.setOverwrite(data.description);
|
||||
return this.setError(data.description);
|
||||
};
|
||||
|
||||
setSuccess = ({ address, description }) => {
|
||||
pushPath(`/${address}/edit`);
|
||||
|
||||
console.log('addr?', address);
|
||||
this.props.editor.setAddress(address);
|
||||
this.props.editor.owner = this.props.user.id;
|
||||
|
||||
this.props.editor.setInitialData();
|
||||
|
||||
this.setState({
|
||||
error: description, finished: true, sending: true, overwriting: false
|
||||
});
|
||||
};
|
||||
|
||||
setOverwrite = error => this.setState({
|
||||
error, finished: false, sending: true, overwriting: true
|
||||
});
|
||||
|
||||
setError = error => this.setState({
|
||||
error, finished: false, sending: true, overwriting: false
|
||||
});
|
||||
|
||||
render() {
|
||||
const {
|
||||
title, error, finished, overwriting, sending
|
||||
} = this.state;
|
||||
const { title } = this.state;
|
||||
const { save_error, save_finished, save_overwriting, save_processing } = this.props;
|
||||
const { host } = getUrlData();
|
||||
|
||||
return (
|
||||
|
@ -90,41 +67,39 @@ export class SaveDialog extends React.Component {
|
|||
<div className="save-title">
|
||||
<div className="save-title-input">
|
||||
<label className="save-title-label">Название</label>
|
||||
<input type="text" value={title} onChange={this.setTitle} autoFocus />
|
||||
<input type="text" value={title} onChange={this.setTitle} autoFocus readOnly={save_finished} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="save-description">
|
||||
<div className="save-address-input">
|
||||
<label className="save-address-label">http://{host}/</label>
|
||||
<input type="text" value={this.getAddress().substr(0, 32)} onChange={this.setAddress} />
|
||||
<input type="text" value={this.getAddress().substr(0, 32)} onChange={this.setAddress} readOnly={save_finished} />
|
||||
</div>
|
||||
|
||||
<div className="save-text">
|
||||
{
|
||||
error || TIPS.SAVE_INFO
|
||||
}
|
||||
{ save_error || TIPS.SAVE_INFO }
|
||||
</div>
|
||||
|
||||
<div className="save-buttons">
|
||||
<div className="save-buttons-text" />
|
||||
<div className={classnames({ 'button-group': !finished })}>
|
||||
<div>
|
||||
|
||||
{ !finished &&
|
||||
{ !save_finished &&
|
||||
<div className="button" onClick={this.cancelSaving}>Отмена</div>
|
||||
}
|
||||
|
||||
{
|
||||
(!sending || (sending && !overwriting && !finished)) &&
|
||||
!save_finished && !save_overwriting &&
|
||||
<div className="button primary" onClick={this.sendSaveRequest}>Сохранить</div>
|
||||
}
|
||||
|
||||
{
|
||||
sending && overwriting &&
|
||||
save_overwriting &&
|
||||
<div className="button danger" onClick={this.forceSaveRequest}>Перезаписать</div>
|
||||
}
|
||||
|
||||
{ finished &&
|
||||
{ save_finished &&
|
||||
<div className="button success" onClick={this.cancelSaving}>Отлично, спасибо!</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
export const TIPS = {
|
||||
SAVE_INFO: 'Вы можете задать своё название маршрута и адрес, по которому он будет доступен.'
|
||||
SAVE_INFO: 'Никто, кроме вас не сможет изменить маршрут - только создать его копию и сохранить по другому адресу',
|
||||
SAVE_TIMED_OUT: 'Сервер не ответил на запрос, попробуйте позже',
|
||||
SAVE_EMPTY: 'Этот маршрут пуст, нарисуйте что-нибудь для начала',
|
||||
SAVE_SUCCESS: 'Маршрут сохранен - он будет доступен по указанному адресу'
|
||||
};
|
||||
|
|
|
@ -95,20 +95,23 @@ export class Editor {
|
|||
getEditing = () => store.getState().user.editing;
|
||||
getChanged = () => store.getState().user.changed;
|
||||
getRouterPoints = () => store.getState().user.routerPoints;
|
||||
getDistance = () => store.getState().user.distance;
|
||||
|
||||
setMode = value => store.dispatch(setMode(value));
|
||||
setDistance = value => store.dispatch(setDistance(value));
|
||||
setChanged = value => store.dispatch(setChanged(value));
|
||||
setRouterPoints = value => store.dispatch(setRouterPoints(value));
|
||||
setActiveSticker = value => store.dispatch(setActiveSticker(value));
|
||||
setTitle = value => store.dispatch(setTitle(value));
|
||||
setAddress = value => store.dispatch(setAddress(value));
|
||||
|
||||
setDistance = value => {
|
||||
if (this.getDistance() !== value) store.dispatch(setDistance(value));
|
||||
};
|
||||
|
||||
clearMode = () => this.setMode(MODES.NONE);
|
||||
clearChanged = () => store.dispatch(setChanged(false));
|
||||
|
||||
startPoly = () => {
|
||||
console.log(this.getRouterPoints());
|
||||
if (this.getRouterPoints()) this.router.clearAll();
|
||||
|
||||
this.poly.continue();
|
||||
|
@ -121,16 +124,15 @@ export class Editor {
|
|||
};
|
||||
|
||||
createStickerOnClick = (e) => {
|
||||
// todo: move to sagas?
|
||||
if (!e || !e.latlng || !this.activeSticker) return;
|
||||
const { latlng } = e;
|
||||
|
||||
this.stickers.createSticker({ latlng, sticker: this.activeSticker });
|
||||
this.setActiveSticker(null);
|
||||
this.setChanged(true);
|
||||
};
|
||||
|
||||
changeMode = mode => {
|
||||
// todo: check if TOGGLING works (we changing MODE from the sagas now)
|
||||
if (this.mode === mode) {
|
||||
if (this.switches[mode] && this.switches[mode].toggle) {
|
||||
// if we have special function on mode when it clicked again
|
||||
|
@ -202,14 +204,9 @@ export class Editor {
|
|||
};
|
||||
|
||||
clearAll = () => {
|
||||
// todo: move to sagas
|
||||
this.poly.clearAll();
|
||||
this.router.clearAll();
|
||||
this.stickers.clearAll();
|
||||
|
||||
// this.setActiveSticker(null);
|
||||
// this.setMode(MODES.NONE);
|
||||
// this.clearChanged();
|
||||
};
|
||||
|
||||
|
||||
|
@ -308,11 +305,17 @@ export class Editor {
|
|||
// return (route.length > 1 && stickers.length > 0);
|
||||
// };
|
||||
|
||||
isEmpty = () => {
|
||||
const { route, stickers } = this.dumpData();
|
||||
|
||||
return (!route || route.length < 1) && (!stickers || stickers.length <= 0);
|
||||
};
|
||||
|
||||
hasEmptyHistory = () => {
|
||||
const { route, stickers } = this.initialData;
|
||||
|
||||
return (!route || route.length < 1) && (!stickers || stickers.length <= 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const editor = new Editor({});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ACTIONS } from '$redux/user/constants';
|
||||
|
||||
export const setUser = user => ({ type: ACTIONS.SET_USER, user });
|
||||
export const userLogout = user => ({ type: ACTIONS.USER_LOGOUT });
|
||||
export const userLogout = () => ({ type: ACTIONS.USER_LOGOUT });
|
||||
|
||||
|
||||
export const setEditing = editing => ({ type: ACTIONS.SET_EDITING, editing });
|
||||
|
@ -24,3 +24,11 @@ export const clearPoly = () => ({ type: ACTIONS.CLEAR_POLY });
|
|||
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 cancelSaveRequest = () => ({ type: ACTIONS.CANCEL_SAVE_REQUEST });
|
||||
|
||||
export const setSaveSuccess = payload => ({ type: ACTIONS.SET_SAVE_SUCCESS, ...payload });
|
||||
export const setSaveError = save_error => ({ type: ACTIONS.SET_SAVE_ERROR, save_error });
|
||||
export const setSaveOverwrite = () => ({ type: ACTIONS.SET_SAVE_OVERWRITE });
|
||||
|
||||
|
|
|
@ -22,4 +22,11 @@ export const ACTIONS = {
|
|||
CLEAR_STICKERS: 'CLEAR_STICKERS',
|
||||
CLEAR_ALL: 'CLEAR_ALL',
|
||||
CLEAR_CANCEL: 'CLEAR_CANCEL',
|
||||
|
||||
SEND_SAVE_REQUEST: 'SEND_SAVE_REQUEST',
|
||||
CANCEL_SAVE_REQUEST: 'CANCEL_SAVE_REQUEST',
|
||||
|
||||
SET_SAVE_SUCCESS: 'SET_SAVE_SUCCESS',
|
||||
SET_SAVE_ERROR: 'SET_SAVE_ERROR',
|
||||
SET_SAVE_OVERWRITE: 'SET_SAVE_OVERWRITE',
|
||||
};
|
||||
|
|
|
@ -18,7 +18,9 @@ const setUser = (state, { user }) => ({
|
|||
});
|
||||
|
||||
const setEditing = (state, { editing }) => ({ ...state, editing });
|
||||
const setChanged = (state, { changed }) => ({ ...state, changed });
|
||||
const setChanged = (state, { changed }) => ({
|
||||
...state, changed, ...state, save_overwriting: false, save_finished: false, save_processing: false, save_error: '',
|
||||
});
|
||||
const setMode = (state, { mode }) => ({ ...state, mode });
|
||||
const setDistance = (state, { distance }) => ({
|
||||
...state,
|
||||
|
@ -33,6 +35,17 @@ const setLogo = (state, { logo }) => ({ ...state, logo });
|
|||
const setTitle = (state, { title }) => ({ ...state, title });
|
||||
const setAddress = (state, { address }) => ({ ...state, address });
|
||||
|
||||
const sendSaveRequest = state => ({ ...state, save_processing: true, });
|
||||
const setSaveError = (state, { save_error }) => ({
|
||||
...state, save_error, save_finished: false, save_processing: false
|
||||
});
|
||||
const setSaveOverwrite = state => ({
|
||||
...state, save_overwriting: true, save_finished: false, save_processing: false
|
||||
});
|
||||
const setSaveSuccess = (state, { save_error }) => ({
|
||||
...state, save_overwriting: false, save_finished: true, save_processing: false, save_error
|
||||
});
|
||||
|
||||
const HANDLERS = {
|
||||
[ACTIONS.SET_USER]: setUser,
|
||||
[ACTIONS.SET_EDITING]: setEditing,
|
||||
|
@ -44,6 +57,11 @@ const HANDLERS = {
|
|||
[ACTIONS.SET_LOGO]: setLogo,
|
||||
[ACTIONS.SET_TITLE]: setTitle,
|
||||
[ACTIONS.SET_ADDRESS]: setAddress,
|
||||
|
||||
[ACTIONS.SET_SAVE_ERROR]: setSaveError,
|
||||
[ACTIONS.SET_SAVE_OVERWRITE]: setSaveOverwrite,
|
||||
[ACTIONS.SET_SAVE_SUCCESS]: setSaveSuccess,
|
||||
[ACTIONS.SEND_SAVE_REQUEST]: sendSaveRequest,
|
||||
};
|
||||
|
||||
export const INITIAL_STATE = {
|
||||
|
@ -58,6 +76,11 @@ export const INITIAL_STATE = {
|
|||
title: 0,
|
||||
address: '',
|
||||
changed: false,
|
||||
|
||||
save_error: '',
|
||||
save_finished: false,
|
||||
save_overwriting: false,
|
||||
save_processing: false,
|
||||
};
|
||||
|
||||
export const userReducer = createReducer(INITIAL_STATE, HANDLERS);
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
import { REHYDRATE } from 'redux-persist';
|
||||
import { takeLatest, select, call, put, takeEvery } from 'redux-saga/effects';
|
||||
import { checkUserToken, getGuestToken, getStoredMap } from '$utils/api';
|
||||
import { setActiveSticker, setChanged, setEditing, setMode, setUser } from '$redux/user/actions';
|
||||
import { delay } from 'redux-saga';
|
||||
import { takeLatest, select, call, put, takeEvery, race, take } from 'redux-saga/effects';
|
||||
import { checkUserToken, getGuestToken, getStoredMap, postMap } from '$utils/api';
|
||||
import {
|
||||
setActiveSticker, setAddress,
|
||||
setChanged,
|
||||
setEditing,
|
||||
setMode,
|
||||
setSaveError,
|
||||
setSaveOverwrite, setSaveSuccess, setTitle,
|
||||
setUser
|
||||
} from '$redux/user/actions';
|
||||
import { getUrlData, pushPath } from '$utils/history';
|
||||
import { editor } from '$modules/Editor';
|
||||
import { ACTIONS } from '$redux/user/constants';
|
||||
import { MODES } from '$constants/modes';
|
||||
import { DEFAULT_USER } from '$constants/auth';
|
||||
import { TIPS } from '$constants/tips';
|
||||
|
||||
const getUser = state => (state.user.user);
|
||||
const getState = state => (state.user);
|
||||
|
@ -172,6 +182,41 @@ function* clearSaga({ type }) {
|
|||
yield put(setMode(MODES.NONE));
|
||||
}
|
||||
|
||||
function* sendSaveRequestSaga({ title, address, force }) {
|
||||
if (editor.isEmpty()) {
|
||||
return yield put(setSaveError(TIPS.SAVE_EMPTY));
|
||||
}
|
||||
|
||||
const { route, stickers } = editor.dumpData();
|
||||
const { id, token } = yield select(getUser);
|
||||
|
||||
const { result, timeout, cancel } = yield race({
|
||||
result: postMap({
|
||||
id, token, route, stickers, title, force, address
|
||||
}),
|
||||
timeout: delay(10000),
|
||||
cancel: take(ACTIONS.CANCEL_SAVE_REQUEST),
|
||||
});
|
||||
|
||||
if (cancel) return yield put(setMode(MODES.NONE));
|
||||
if (result && result.mode === 'overwriting') return yield put(setSaveOverwrite());
|
||||
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 }));
|
||||
}
|
||||
|
||||
function* setSaveSuccessSaga({ address, title }) {
|
||||
const { id } = yield select(getUser);
|
||||
|
||||
pushPath(`/${address}/edit`);
|
||||
yield put(setTitle(title));
|
||||
yield put(setAddress(address));
|
||||
// yield editor.setAddress(address);
|
||||
yield editor.owner = id;
|
||||
|
||||
return yield editor.setInitialData();
|
||||
}
|
||||
|
||||
export function* userSaga() {
|
||||
// ASYNCHRONOUS!!! :-)
|
||||
|
||||
|
@ -194,4 +239,6 @@ export function* userSaga() {
|
|||
ACTIONS.CLEAR_CANCEL,
|
||||
], clearSaga);
|
||||
|
||||
yield takeLatest(ACTIONS.SEND_SAVE_REQUEST, sendSaveRequestSaga);
|
||||
yield takeLatest(ACTIONS.SET_SAVE_SUCCESS, setSaveSuccessSaga);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
.save-helper {
|
||||
width: 443px;
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
@ -7,9 +6,9 @@
|
|||
.save-title {
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background: linear-gradient(160deg, @green_primary, @green_secondary);
|
||||
background: linear-gradient(150deg, @green_primary, @green_secondary);
|
||||
flex-direction: column;
|
||||
border-radius: 3px 3px 0 0;
|
||||
border-radius: @panel_radius @panel_radius 0 0;
|
||||
font-weight: 200;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
@ -19,8 +18,8 @@
|
|||
}
|
||||
|
||||
.save-title-input {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 2px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: @panel_radius;
|
||||
display: flex;
|
||||
|
||||
input {
|
||||
|
@ -72,11 +71,18 @@
|
|||
|
||||
.save-text {
|
||||
padding: 10px;
|
||||
line-height: 1.1em;
|
||||
min-height: 2.2em;
|
||||
}
|
||||
|
||||
.save-buttons {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
padding: 0px;
|
||||
margin-top: 20px;
|
||||
|
||||
.button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.save-buttons-text {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const ru = [' ','\\.',',',':','\\?','#','Я','я','Ю','ю','Ч','ч','Ш','ш','Щ','щ','Ж','ж','А','а','Б','б','В','в','Г','г','Д','д','Е','е','Ё','ё','З','з','И','и','Й','й','К','к','Л','л','М','м','Н','н', 'О','о','П','п','Р','р','С','с','Т','т','У','у','Ф','ф','Х','х','Ц','ц','Ы','ы','Ь','ь','Ъ','ъ','Э','э'];
|
||||
const en = ['_','','','','','','Ya','ya','Yu','yu','Ch','ch','Sh','sh','Sh','sh','Zh','zh','A','a','B','b','V','v','G','g','D','d','E','e','E','e','Z','z','I','i','J','j','K','k','L','l','M','m','N','n', 'O','o','P','p','R','r','S','s','T','t','U','u','F','f','H','h','C','c','Y','y','`','`','\'','\'','E', 'e'];
|
||||
const en = ['_','','','','','','Ya','ya','Yu','yu','Ch','ch','Sh','sh','Sh','sh','Zh','zh','A','a','B','b','V','v','G','g','D','d','E','e','E','e','Z','z','I','i','J','j','K','k','L','l','M','m','N','n', 'O','o','P','p','R','r','S','s','T','t','U','u','F','f','H','h','C','c','Y','y','','','','','E', 'e'];
|
||||
|
||||
export const toHours = (info) => {
|
||||
const hrs = parseInt(Number(info), 10);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue