import { Map } from '$modules/Map'; import { Poly } from '$modules/Poly'; import { MODES } from '$constants/modes'; import { ILatLng, Stickers } from '$modules/Stickers'; import { Router } from '$modules/Router'; import { DEFAULT_LOGO, ILogos, LOGOS } from '$constants/logos'; import { getUrlData } from '$utils/history'; import { store } from '$redux/store'; import { changeProvider, resetSaveDialog, setActiveSticker, setAddress, setChanged, setDistance, setIsEmpty, setIsRouting, setLogo, setMarkersShown, setMode, setPublic, setRouterPoints, setTitle, } from '$redux/user/actions'; import { DEFAULT_PROVIDER, IProvider, PROVIDERS } from '$constants/providers'; import { STICKERS } from '$constants/stickers'; import { IRootState } from "$redux/user/reducer"; import { DEFAULT_USER, IUser } from "$constants/auth"; interface IEditor { map: Map; poly: Poly; stickers: Stickers; router: Router; logo: keyof ILogos; owner: { id: string }; initialData: { version: number, title: string, owner: { id: string }, address: string, path: any, route: any, stickers: any, provider: string, is_public: boolean, logo: string, }; activeSticker: IRootState['activeSticker']; mode: IRootState['mode']; provider: IProvider; switches: { [x: string]: { start?: () => any, stop?: () => any, toggle?: () => any, } }; clickHandlers: { [x: string]: (event) => void }; user: IUser; } export class Editor { constructor() { this.logo = DEFAULT_LOGO; this.owner = null; this.map = new Map({ container: 'map' }); this.activeSticker = {}; this.mode = MODES.NONE; this.provider = PROVIDERS[DEFAULT_PROVIDER]; const { triggerOnChange, lockMapClicks, routerMoveStart, pushPolyPoints, map: { map } } = this; this.poly = new Poly({ map, routerMoveStart, lockMapClicks, setDistance: this.setDistance, triggerOnChange, editor: this, }); this.stickers = new Stickers({ map, lockMapClicks, triggerOnChange, editor: this }); this.router = new Router({ map, lockMapClicks, pushPolyPoints, setRouterPoints: this.setRouterPoints, setIsRouting: this.setIsRouting, }); this.switches = { [MODES.POLY]: { start: this.startPoly, stop: this.poly.stop, toggle: this.clearMode, }, [MODES.ROUTER]: { toggle: this.clearMode, start: this.routerSetStart, }, [MODES.STICKERS]: { toggle: this.clearSticker, }, [MODES.STICKERS_SELECT]: { toggle: this.clearSticker, }, [MODES.TRASH]: { toggle: this.clearMode, }, [MODES.CONFIRM_CANCEL]: { toggle: this.cancelEditing, }, [MODES.LOGO]: { toggle: this.clearMode, }, [MODES.SAVE]: { toggle: this.clearMode, stop: this.resetSaveDialog, }, [MODES.PROVIDER]: { toggle: this.clearMode, } }; this.clickHandlers = { [MODES.STICKERS]: this.createStickerOnClick, [MODES.ROUTER]: this.router.pushWaypointOnClick, }; map.addEventListener('mouseup', this.onClick); map.addEventListener('dragstart', () => lockMapClicks(true)); map.addEventListener('dragstop', () => lockMapClicks(false)); } map: IEditor['map']; poly: IEditor['poly']; stickers: IEditor['stickers']; router: IEditor['router']; logo: IEditor['logo'] = DEFAULT_LOGO; owner: IEditor['owner'] = null; initialData: IEditor['initialData'] = { version: null, title: null, owner: null, address: null, path: null, route: null, stickers: null, provider: null, is_public: null, logo: null, }; activeSticker: IEditor['activeSticker']; mode: IEditor['mode']; provider: IEditor['provider']; switches: IEditor['switches']; clickHandlers: IEditor['clickHandlers']; user: IEditor['user'] = DEFAULT_USER; getState = (): IRootState => store.getState().user; getUser = () => this.getState().user; getMode = () => this.getState().mode; getProvider = () => this.getState().provider; getTitle = () => this.getState().title; getEditing = () => this.getState().editing; getChanged = () => this.getState().changed; getRouterPoints = () => this.getState().routerPoints; getDistance = () => this.getState().distance; getIsEmpty = () => this.getState().is_empty; setLogo: typeof setLogo = logo => store.dispatch(setLogo(logo)); setMode: typeof setMode = value => store.dispatch(setMode(value)); setRouterPoints: typeof setRouterPoints = value => store.dispatch(setRouterPoints(value)); setActiveSticker: typeof setActiveSticker = value => store.dispatch(setActiveSticker(value)); setTitle: typeof setTitle = value => store.dispatch(setTitle(value)); setAddress: typeof setAddress = value => store.dispatch(setAddress(value)); setPublic: typeof setPublic = value => store.dispatch(setPublic(value)); setIsEmpty: typeof setIsEmpty = value => store.dispatch(setIsEmpty(value)); setIsRouting: typeof setIsRouting = value => store.dispatch(setIsRouting(value)); setMarkersShown = (value: boolean): void => { if (this.getState().markers_shown !== value) store.dispatch(setMarkersShown(value)); }; resetSaveDialog = (): void => { store.dispatch(resetSaveDialog()); }; setDistance = (value: number): void => { if (this.getDistance() !== value) store.dispatch(setDistance(value)); }; setChanged = (value: boolean): void => { if (this.getChanged() !== value) store.dispatch(setChanged(value)); }; clearMode = (): void => { this.setMode(MODES.NONE); }; clearChanged = (): void => { store.dispatch(setChanged(false)); }; startPoly = (): void => { if (this.getRouterPoints()) this.router.clearAll(); this.poly.continue(); }; triggerOnChange = (): void => { if (this.isEmpty !== this.getIsEmpty()) this.setIsEmpty(this.isEmpty); if (this.getEditing() && !this.getChanged()) this.setChanged(true); }; createStickerOnClick = (e): void => { if (!e || !e.latlng || !this.activeSticker) return; const { latlng }: { latlng: ILatLng } = e; this.stickers.createSticker({ latlng, sticker: this.activeSticker.sticker, set: this.activeSticker.set }); this.setActiveSticker(null); this.setChanged(true); this.setMode(MODES.STICKERS_SELECT); }; changeMode = (mode: IRootState['mode']): void => { if (this.mode === mode) { if (this.switches[mode] && this.switches[mode].toggle) { // if we have special function on mode when it clicked again this.switches[mode].toggle(); } else { this.disableMode(mode); // this.setMode(MODES.NONE); this.mode = MODES.NONE; } } else { this.disableMode(this.mode); // this.setMode(mode); this.mode = mode; this.enableMode(mode); } }; enableMode = (mode: IRootState['mode']): void => { if (this.switches[mode] && this.switches[mode].start) this.switches[mode].start(); }; disableMode = (mode: IRootState['mode']): void => { if (this.switches[mode] && this.switches[mode].stop) this.switches[mode].stop(); }; onClick = (e): void => { if (e.originalEvent.which === 3) return; // skip right / middle click if (this.clickHandlers[this.mode]) this.clickHandlers[this.mode](e); }; lockMapClicks = (lock: boolean): void => { if (lock) { this.map.map.removeEventListener('mouseup', this.onClick); this.map.map.addEventListener('mouseup', this.unlockMapClicks); } else { this.map.map.removeEventListener('mouseup', this.unlockMapClicks); this.map.map.addEventListener('mouseup', this.onClick); } }; unlockMapClicks = (): void => { this.lockMapClicks(false); }; routerSetStart = (): void => { const { latlngs } = this.poly; if (!latlngs || !latlngs.length) return; this.router.startFrom(latlngs[latlngs.length - 1]); }; routerMoveStart = (): void => { const { _latlngs } = this.poly.poly; if (_latlngs) this.router.moveStart(_latlngs[_latlngs.length - 1]); }; pushPolyPoints = (latlngs: Array): void => { this.poly.pushPoints(latlngs); }; clearSticker = (): void => { if (this.activeSticker) { this.setActiveSticker(null); } else { this.setMode(MODES.NONE); } }; clearAll = (): void => { this.poly.clearAll(); this.router.clearAll(); this.stickers.clearAll(); this.setIsEmpty(true); }; setData = ({ route = [], stickers = [], owner, title, address, provider = DEFAULT_PROVIDER, logo = DEFAULT_LOGO, is_public, }: IEditor['initialData']): void => { this.setTitle(title || ''); const { id } = this.getUser(); if (address && id && owner && id === owner.id) { this.setAddress(address); } if (route) this.poly.setPoints(route); this.stickers.clearAll(); if (stickers) { stickers.map(sticker => sticker.set && STICKERS[sticker.set].url && this.stickers.createSticker({ latlng: sticker.latlng, angle: sticker.angle, sticker: sticker.sticker, set: sticker.set, text: sticker.text, }) ); } this.setPublic(is_public); this.setLogo((logo && LOGOS[DEFAULT_LOGO] && logo) || DEFAULT_LOGO); this.setProvider((provider && PROVIDERS[provider] && provider) || DEFAULT_PROVIDER); if (owner) this.owner = owner; }; fitDrawing = (): void => { if (this.poly.isEmpty) return; const bounds = this.poly.poly.getBounds(); if (bounds && Object.values(bounds)) this.map.map.fitBounds(bounds); }; setInitialData = (): void => { const { path } = getUrlData(); const { id } = this.getUser(); const { is_public, logo } = this.getState(); const { route, stickers, provider } = this.dumpData(); this.initialData = { version: 2, title: this.getTitle(), owner: this.owner, address: (this.owner.id === id ? path : null), path, route, stickers, provider, is_public, logo, }; }; startEditing = (): void => { const { id } = this.getUser(); this.setInitialData(); this.owner = { id }; // todo: implement // if (this.poly.latlngs && this.poly.latlngs.length > 1) this.poly.poly.editor.enable(); this.poly.enableEditor(); this.stickers.startEditing(); }; stopEditing = (): void => { this.poly.poly.editor.disable(); this.stickers.stopEditing(); this.router.clearAll(); }; cancelEditing = (): void => { if (this.hasEmptyHistory) { this.clearAll(); this.startEditing(); this.clearChanged(); return; } else { this.setData(this.initialData); } this.stopEditing(); this.clearChanged(); }; dumpData = () => ({ route: this.poly.dumpData(), stickers: this.stickers.dumpData(), provider: this.getProvider(), }); setProvider: typeof changeProvider = provider => store.dispatch(changeProvider(provider)); get isEmpty(): boolean { const { route, stickers } = this.dumpData(); return (!route || route.length <= 1) && (!stickers || stickers.length <= 0); } get hasEmptyHistory(): boolean { const { route, stickers } = this.initialData; return (!route || route.length < 1) && (!stickers || stickers.length <= 0); }; } export const editor = new Editor(); // for debug purposes declare let window: any; window.editor = editor;