diff --git a/package-lock.json b/package-lock.json index 098fac2..6d5716b 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#9a3b826abd5203c5d6adf77cf4bfdd10131de417", + "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..5ce7e31 100644 --- a/src/components/dialogs/SaveDialog.tsx +++ b/src/components/dialogs/SaveDialog.tsx @@ -7,10 +7,14 @@ 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/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/modules/Editor.ts b/src/modules/Editor.ts index 16fab94..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, }; }; diff --git a/src/redux/user/actions.ts b/src/redux/user/actions.ts index 9dd9a00..88417d3 100644 --- a/src/redux/user/actions.ts +++ b/src/redux/user/actions.ts @@ -1,5 +1,6 @@ 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 }); @@ -12,9 +13,11 @@ export const setRouterPoints = routerPoints => ({ type: ACTIONS.SET_ROUTER_POINT 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,7 +31,17 @@ 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 }); 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..389e29d 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 @@ -276,6 +283,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 +339,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 +366,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 +387,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 +399,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..51b1156 100644 --- a/src/redux/user/sagas.ts +++ b/src/redux/user/sagas.ts @@ -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), 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 = (