Browse Source

重构editorAction

master
Cmen 3 years ago
parent
commit
5b91a604ef
  1. 1
      package.json
  2. 13
      src/editor/Catalog/index.tsx
  3. 12
      src/editor/Menu/File.tsx
  4. 17
      src/editor/Plot/Tools/index.tsx
  5. 23
      src/editor/Plot/index.tsx
  6. 203
      src/map/MapEditor.ts
  7. 223
      src/map/index.ts
  8. 26
      src/map/type.ts
  9. 18
      src/store/actions/StoreAction.ts
  10. 142
      src/store/actions/index.ts
  11. 15
      src/store/index.ts
  12. 3
      src/store/initState.ts
  13. 8
      src/store/reducers/index.ts
  14. 19
      src/store/reducers/map/index.ts
  15. 4
      src/store/selectors.ts
  16. 5
      src/store/type.ts
  17. 4
      src/store/utils/getGeoJSON.ts
  18. 5
      yarn.lock

1
package.json

@ -18,6 +18,7 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-redux": "^7.2.6", "react-redux": "^7.2.6",
"redux": "^4.1.2", "redux": "^4.1.2",
"redux-thunk": "^2.4.1",
"uid": "^2.0.0" "uid": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {

13
src/editor/Catalog/index.tsx

@ -1,7 +1,7 @@
import { Collapse } from "antd"; import { Collapse } from "antd";
import { useDispatch, useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { IOverlay, EditorAction, mapStateSelector } from "@store"; import { IOverlay, editorAction, mapOptionsSelector } from "@store";
import "./style.less"; import "./style.less";
import classNames from "classnames"; import classNames from "classnames";
@ -14,11 +14,8 @@ export type OverlayListProps = {
const OverlayList = (props: OverlayListProps) => { const OverlayList = (props: OverlayListProps) => {
const { overlays } = props; const { overlays } = props;
const dispatch = useDispatch(); const onClick = (id: string) => () => editorAction.selectOverlay(id);
const { selectedIds } = useSelector(mapOptionsSelector);
const onClick = (id: string) => () =>
dispatch(EditorAction.selectOverlay(id));
const { selectedIds } = useSelector(mapStateSelector);
return ( return (
<> <>
@ -39,7 +36,7 @@ const OverlayList = (props: OverlayListProps) => {
const Catalog = () => { const Catalog = () => {
const { rectangles, polygons, polylines, circles } = const { rectangles, polygons, polylines, circles } =
useSelector(mapStateSelector); useSelector(mapOptionsSelector);
return ( return (
<Collapse <Collapse
defaultActiveKey={["1", "2", "3", "4"]} defaultActiveKey={["1", "2", "3", "4"]}

12
src/editor/Menu/File.tsx

@ -1,4 +1,4 @@
import { globalController } from "@store"; import { editorAction } from "@store";
import MenuGroup from "./MenuGroup"; import MenuGroup from "./MenuGroup";
const FileMenu = () => { const FileMenu = () => {
@ -9,27 +9,27 @@ const FileMenu = () => {
items: [ items: [
{ {
name: "打开GeoJSON", name: "打开GeoJSON",
onClick: globalController.openGeoJSON, onClick: editorAction.openGeoJSON,
hotkey: "Alt+O", hotkey: "Alt+O",
}, },
{ {
name: "打开工程", name: "打开工程",
onClick: globalController.openProject, onClick: editorAction.openProject,
hotkey: "Ctrl+O", hotkey: "Ctrl+O",
}, },
{ {
name: "暂存工程", name: "暂存工程",
onClick: globalController.saveTemp, onClick: editorAction.saveTemp,
hotkey: "Alt+S", hotkey: "Alt+S",
}, },
{ {
name: "保存GeoJSON", name: "保存GeoJSON",
onClick: globalController.saveGeoJSON, onClick: editorAction.saveGeoJSON,
hotkey: "Ctrl+S", hotkey: "Ctrl+S",
}, },
{ {
name: "保存工程", name: "保存工程",
onClick: globalController.saveProject, onClick: editorAction.saveProject,
hotkey: "Ctrl+Alt+S", hotkey: "Ctrl+Alt+S",
}, },
], ],

17
src/editor/Plot/Tools/index.tsx

@ -1,8 +1,13 @@
import { Tooltip } from "antd"; import { Tooltip } from "antd";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import classnames from "classnames"; import classnames from "classnames";
import { globalController, OverlayNamePrefixs, mapStateSelector } from "@store"; import {
import { OverlayTypes } from "@types"; editorAction,
OverlayNamePrefixs,
statusSelector,
overlayTypeSelector,
} from "@store";
import { OverlayTypes, Status } from "@types";
type IconWithTipProps = { type IconWithTipProps = {
type: string; type: string;
@ -39,10 +44,12 @@ const OverlayTool = (props: OverlayToolProps) => {
const { type } = props; const { type } = props;
const text = OverlayNamePrefixs[type]; const text = OverlayNamePrefixs[type];
const { overlayType } = useSelector(mapStateSelector); const overlayType = useSelector(overlayTypeSelector);
const selected = type === overlayType; const status = useSelector(statusSelector);
const onClick = () => globalController.createOverlay(type); const selected = status === Status.CreateOverlay && type === overlayType;
const onClick = () => editorAction.createOverlay(type);
return <IconWithTip {...{ text, onClick, type, selected }} />; return <IconWithTip {...{ text, onClick, type, selected }} />;
}; };

23
src/editor/Plot/index.tsx

@ -1,22 +1,13 @@
import { Layout } from "antd";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { MapEditor } from "@map"; import { MapEditor } from "@map";
import { IEditorState } from "@store"; import { IEditorState, editorAction } from "@store";
import Tools from "./Tools"; import Tools from "./Tools";
import { registerMapEventHooks } from "./mapHooks";
import "./index.less"; import "./index.less";
const mapStateSelector = (state: IEditorState) => state.map;
const { Header, Footer, Sider, Content } = Layout;
const Plot = () => { const Plot = () => {
const mapStageRef = useRef<HTMLDivElement>(null); const mapStageRef = useRef<HTMLDivElement>(null);
const mapState = useSelector(mapStateSelector);
const dispatch = useDispatch();
const [mapEditor, setMapEditor] = useState<MapEditor | null>(null); const [mapEditor, setMapEditor] = useState<MapEditor | null>(null);
@ -25,13 +16,9 @@ const Plot = () => {
const editor = new MapEditor(mapStageRef.current!); const editor = new MapEditor(mapStageRef.current!);
(window as any).mapEditor = editor; (window as any).mapEditor = editor;
setMapEditor(editor); setMapEditor(editor);
registerMapEventHooks(editor, dispatch); editorAction.registerMapEditor(editor);
editor.init().then(() => {
editor.update(mapState);
});
} }
}); }, [mapEditor]);
mapEditor?.update(mapState);
return ( return (
<> <>
@ -44,8 +31,4 @@ const Plot = () => {
); );
}; };
// const mapStateToProps = (state: IEditorState) => {
// return state.map;
// };
export default Plot; export default Plot;

203
src/map/MapEditor.ts

@ -0,0 +1,203 @@
import Emitter from "@finevis/emitter";
import "@amap/amap-jsapi-types";
import { getOverlayPaths, getUID, registerHotkey } from "@utils";
import {
BaseOverlayEditor,
RectangleEditor,
PolygonEditor,
PolylineEditor,
CircleEditor,
} from "./editors";
import { IMapOptions, IMapEditor } from "./type";
import { PolygonOptions, PolylineOptions, SelectedOptions } from "./constants";
import { EventTypes, OverlayTypes } from "../types/enum";
type AMapOverlayEditor =
| AMap.RectangleEditor
| AMap.PolygonEditor
| AMap.PolylineEditor
| AMap.CircleEditor;
type OverlayTemp = {
type: OverlayTypes;
target: AMap.MapOverlay;
};
export class MapEditor extends Emitter implements IMapEditor {
dom: HTMLDivElement;
private _map: AMap.Map | undefined;
private overlayEditors: BaseOverlayEditor<AMapOverlayEditor>[] = [];
private overlayMap: Record<string, OverlayTemp> = {};
private currentOverlayEditor:
| BaseOverlayEditor<AMapOverlayEditor>
| undefined;
private selectedIds: string[] = [];
private editorStatus: "editing" | "creating" | null = null;
constructor(dom: HTMLDivElement) {
super();
this.dom = dom;
}
get map() {
return this._map!;
}
async init() {
await AMapLoader.load({
key: "a4171ad2d7df42823b4de7d25c8c35ee",
version: "2.0",
plugins: [
"AMap.RectangleEditor",
"AMap.PolylineEditor",
"AMap.PolygonEditor",
"AMap.CircleEditor",
"AMap.PlaceSearch",
"AMap.AutoComplete",
],
});
this._map = new AMap.Map(this.dom);
this.initEditors();
// Space的key是空字符串, 这就离谱.
registerHotkey(" ", { callback: this.finishEditOverlay.bind(this) });
registerHotkey("e", {
callback: this.editSelectedOverlay.bind(this),
alt: true,
});
}
initEditors() {
const { map } = this;
this.overlayEditors = [
new RectangleEditor(map),
new PolygonEditor(map),
new PolylineEditor(map),
new CircleEditor(map),
];
}
getEditorByType(overlayType: OverlayTypes) {
return this.overlayEditors.find(
(editor) => editor.getType() === overlayType
);
}
importProject(options: IMapOptions) {
this.clearOverlays();
const { rectangles, polygons, polylines, circles } = options;
const rectangleEditor = this.getEditorByType(OverlayTypes.Rectangle);
const polygonEditor = this.getEditorByType(OverlayTypes.Polygon);
const polylineEditor = this.getEditorByType(OverlayTypes.Polyline);
const circleEditor = this.getEditorByType(OverlayTypes.Circle);
const addOverlay = (
id: string,
target: AMap.MapOverlay,
type: OverlayTypes
) => {
this.overlayMap[id] = {
type,
target,
};
this.map.add(target);
};
rectangles.forEach((rect) =>
addOverlay(rect.id, rectangleEditor!.build(rect), OverlayTypes.Rectangle)
);
polygons.forEach((polygon) =>
addOverlay(
polygon.id,
polygonEditor!.build(polygon),
OverlayTypes.Polygon
)
);
polylines.forEach((polyline) =>
addOverlay(
polyline.id,
polylineEditor!.build(polyline),
OverlayTypes.Polyline
)
);
circles.forEach((circle) =>
addOverlay(circle.id, circleEditor!.build(circle), OverlayTypes.Circle)
);
}
importGeoJSON(geojson: GeoJSON.FeatureCollection): void {
throw new Error("Method not implemented.");
}
// 清空所有覆盖物
clearOverlays() {
for (let id in this.overlayMap) {
this.map.remove(this.overlayMap[id].target);
}
this.overlayMap = {};
}
createOverlay(type: OverlayTypes) {
this.currentOverlayEditor = this.getEditorByType(type!);
this.currentOverlayEditor?.create();
this.editorStatus = "creating";
}
selectOverlays(ids?: string[]) {
if (!ids?.length) return;
this.selectedIds = ids;
ids.forEach((id) => {
const { target } = this.overlayMap[id];
target.setOptions(SelectedOptions);
});
}
deleteOverlays() {
if (this.selectedIds?.length === 0) return;
this.selectedIds.forEach((id) => {
const { target } = this.overlayMap[id];
this.map.remove(target);
delete this.overlayMap[id];
});
}
finishEditOverlay() {
if (this.currentOverlayEditor == null) return;
const target = this.currentOverlayEditor.finish();
if (target == null) return;
const type = this.currentOverlayEditor.getType();
const isCreatingOverlay = this.editorStatus === "creating";
let id = getUID();
if (isCreatingOverlay) {
this.overlayMap[id] = { type, target };
} else {
id = this.selectedIds[0];
}
const evt: any = { id, type };
if (type === OverlayTypes.Circle) {
const circle = target as AMap.Circle;
const { lng, lat } = circle.getCenter();
evt.lngLat = [lng, lat];
evt.radius = circle.getRadius();
} else {
evt.path = getOverlayPaths(target, type);
}
target.setOptions(
type === OverlayTypes.Polyline ? PolylineOptions : PolygonOptions
);
this.emit(EventTypes.FinishEditOverlay, evt);
this.editorStatus = null;
}
editSelectedOverlay() {
if (this.selectedIds.length !== 1) return;
const [id] = this.selectedIds;
const { target, type } = this.overlayMap[id];
this.currentOverlayEditor = this.getEditorByType(type);
this.currentOverlayEditor?.edit(target);
this.editorStatus = "editing";
}
}

223
src/map/index.ts

@ -1,221 +1,2 @@
import Emitter from "@finevis/emitter"; export * from "./MapEditor";
import "@amap/amap-jsapi-types"; export * from "./type";
import { IMapState, IOverlay } from "@store";
import { getOverlayPaths, getUID } from "@utils";
import {
BaseOverlayEditor,
RectangleEditor,
PolygonEditor,
PolylineEditor,
CircleEditor,
} from "./editors";
import { registerHotkey } from "../utils/hotkeys";
import { PolygonOptions, PolylineOptions, SelectedOptions } from "./constants";
import { Command, EventTypes, OverlayTypes } from "../types/enum";
type AMapOverlayEditor =
| AMap.RectangleEditor
| AMap.PolygonEditor
| AMap.PolylineEditor
| AMap.CircleEditor;
type OverlayTemp = {
type: OverlayTypes;
target: AMap.MapOverlay;
};
export class MapEditor extends Emitter {
dom: HTMLDivElement;
private _map: AMap.Map | undefined;
private overlayEditors: BaseOverlayEditor<AMapOverlayEditor>[] = [];
private overlayMap: Record<string, OverlayTemp> = {};
private _currentOverlayEditor:
| BaseOverlayEditor<AMapOverlayEditor>
| undefined;
private _selectedIds: string[] = [];
private _editorStatus: "editing" | "creating" | null = null;
constructor(dom: HTMLDivElement) {
super();
this.dom = dom;
}
get map() {
return this._map!;
}
async init() {
await AMapLoader.load({
key: "a4171ad2d7df42823b4de7d25c8c35ee",
version: "2.0",
plugins: [
"AMap.RectangleEditor",
"AMap.PolylineEditor",
"AMap.PolygonEditor",
"AMap.CircleEditor",
"AMap.PlaceSearch",
"AMap.AutoComplete",
],
});
this._map = new AMap.Map(this.dom);
this.initEditors();
// Space的key是空字符串, 这就离谱.
registerHotkey(" ", { callback: this._finishEditOverlay.bind(this) });
registerHotkey("e", {
callback: this._editSelectedOverlay.bind(this),
alt: true,
});
}
update(mapState: IMapState) {
if (this._editorStatus != null) {
this._finishEditOverlay();
}
const { command } = mapState;
switch (command) {
case Command.CreateOverlay:
this._createOverlay(mapState);
break;
case Command.SelectOverlay:
this._selectOverlays(mapState.selectedIds);
break;
case Command.DeleteOverlays:
this._deleteOverlays();
break;
case Command.OpenProject:
this.importOverlays(mapState);
break;
}
}
initEditors() {
const { map } = this;
this.overlayEditors = [
new RectangleEditor(map),
new PolygonEditor(map),
new PolylineEditor(map),
new CircleEditor(map),
];
}
getEditorByType(overlayType: OverlayTypes) {
return this.overlayEditors.find(
(editor) => editor.getType() === overlayType
);
}
importOverlays(mapState: IMapState) {
this.clearOverlays();
const { rectangles, polygons, polylines, circles } = mapState;
const rectangleEditor = this.getEditorByType(OverlayTypes.Rectangle);
const polygonEditor = this.getEditorByType(OverlayTypes.Polygon);
const polylineEditor = this.getEditorByType(OverlayTypes.Polyline);
const circleEditor = this.getEditorByType(OverlayTypes.Circle);
const addOverlay = (
id: string,
target: AMap.MapOverlay,
type: OverlayTypes
) => {
this.overlayMap[id] = {
type,
target,
};
this.map.add(target);
};
rectangles.forEach((rect) =>
addOverlay(rect.id, rectangleEditor!.build(rect), OverlayTypes.Rectangle)
);
polygons.forEach((polygon) =>
addOverlay(
polygon.id,
polygonEditor!.build(polygon),
OverlayTypes.Polygon
)
);
polylines.forEach((polyline) =>
addOverlay(
polyline.id,
polylineEditor!.build(polyline),
OverlayTypes.Polyline
)
);
circles.forEach((circle) =>
addOverlay(circle.id, circleEditor!.build(circle), OverlayTypes.Circle)
);
}
// 清空所有覆盖物
clearOverlays() {
for (let id in this.overlayMap) {
this.map.remove(this.overlayMap[id].target);
}
this.overlayMap = {};
}
_createOverlay(mapState: IMapState) {
const { overlayType } = mapState;
this._currentOverlayEditor = this.getEditorByType(overlayType!);
this._currentOverlayEditor?.create();
this._editorStatus = "creating";
}
_selectOverlays(ids?: string[]) {
if (!ids?.length) return;
this._selectedIds = ids;
ids.forEach((id) => {
const { target } = this.overlayMap[id];
target.setOptions(SelectedOptions);
});
}
_deleteOverlays() {
if (this._selectedIds?.length === 0) return;
this._selectedIds.forEach((id) => {
const { target } = this.overlayMap[id];
this.map.remove(target);
delete this.overlayMap[id];
});
}
_finishEditOverlay() {
if (this._currentOverlayEditor == null) return;
const target = this._currentOverlayEditor.finish();
if (target == null) return;
const type = this._currentOverlayEditor.getType();
const isCreatingOverlay = this._editorStatus === "creating";
let id = getUID();
if (isCreatingOverlay) {
this.overlayMap[id] = { type, target };
} else {
id = this._selectedIds[0];
}
const evt: any = { id, type };
if (type === OverlayTypes.Circle) {
const circle = target as AMap.Circle;
const { lng, lat } = circle.getCenter();
evt.lngLat = [lng, lat];
evt.radius = circle.getRadius();
} else {
evt.path = getOverlayPaths(target, type);
}
target.setOptions(
type === OverlayTypes.Polyline ? PolylineOptions : PolygonOptions
);
this.emit(EventTypes.FinishEditOverlay, evt);
this._editorStatus = null;
}
_editSelectedOverlay() {
if (this._selectedIds.length !== 1) return;
const [id] = this._selectedIds;
const { target, type } = this.overlayMap[id];
this._currentOverlayEditor = this.getEditorByType(type);
this._currentOverlayEditor?.edit(target);
this._editorStatus = "editing";
}
}

26
src/map/type.ts

@ -0,0 +1,26 @@
import { EventTypes, OverlayTypes } from "@types";
export interface IMapEditor {
on(type: EventTypes, evt: any): void;
init(): Promise<void>;
createOverlay(type: OverlayTypes): void;
importProject(options: IMapOptions): void;
importGeoJSON(geojson: GeoJSON.FeatureCollection): void;
selectOverlays(ids: string[]): void;
}
export interface IOverlay {
id: string;
name: string;
type: OverlayTypes;
lngLat?: GeoJSON.Position;
path?: GeoJSON.Position[];
radius?: number;
}
export interface IMapOptions {
polygons: IOverlay[];
polylines: IOverlay[];
circles: IOverlay[];
rectangles: IOverlay[];
selectedIds?: string[];
}

18
src/store/actions.ts → src/store/actions/StoreAction.ts

@ -1,8 +1,7 @@
import { OverlayTypes } from "@types"; import { OverlayTypes } from "@types";
import { IEditorState } from "./type"; import { IOverlay } from "@map";
import { IEditorState } from "../type";
export enum ActionTypes { export enum ActionTypes {
// AddRect = "addRect",
CreateOverlay = "createOverlay", CreateOverlay = "createOverlay",
FinishEditOverlay = "finishEditOverlay", FinishEditOverlay = "finishEditOverlay",
SelectOverlay = "selectOverlay", SelectOverlay = "selectOverlay",
@ -10,24 +9,17 @@ export enum ActionTypes {
ReplaceState = "replaceState", ReplaceState = "replaceState",
} }
export type CreatedOverlay = { export const StoreAction = {
id: string;
type: OverlayTypes;
};
type ActionCreator = (payload?: any) => { type: ActionTypes; payload: any };
export const EditorAction: Record<ActionTypes, ActionCreator> = {
createOverlay(type: OverlayTypes) { createOverlay(type: OverlayTypes) {
return { return {
type: ActionTypes.CreateOverlay, type: ActionTypes.CreateOverlay,
payload: type, payload: type,
}; };
}, },
finishEditOverlay(overlay: any) { finishEditOverlay(overlay: IOverlay) {
return { return {
type: ActionTypes.FinishEditOverlay, type: ActionTypes.FinishEditOverlay,
payload: overlay as CreatedOverlay, payload: overlay,
}; };
}, },
selectOverlay(id: string) { selectOverlay(id: string) {

142
src/store/GlobalController.ts → src/store/actions/index.ts

@ -1,61 +1,99 @@
import { message, Popconfirm, Modal } from "antd"; import { IMapEditor } from "@map";
import { Action } from "redux";
import { Modal } from "antd";
import { registerHotkey, downloadJSON } from "@utils"; import { registerHotkey, downloadJSON } from "@utils";
import { IStore, IEditorState } from "./type"; import { getGeoJSON } from "../utils";
import { getGeoJSON } from "./utils"; import { OverlayTypes, EventTypes } from "@types";
import { OverlayTypes } from "../types/enum"; import { IOverlay, IMapOptions } from "@map";
import { EditorAction } from "./actions"; import { StoreAction } from "./StoreAction";
import { IStore, IEditorState } from "../type";
interface IGlobalController { import { initState } from "../initState";
saveGeoJSON: () => void;
saveProject: () => void; export { ActionTypes } from "./StoreAction";
saveTemp: () => void;
clearEditor: () => void;
openGeoJSON: () => void;
openProject: () => void;
showCommand: () => void;
createRectangle: () => void;
createPolygon: () => void;
createPolyline: () => void;
createCircle: () => void;
deleteOverlays: () => void;
}
// 全局控制器, 用以复用一些快捷方法.
export class GlobelController implements IGlobalController {
store: IStore;
export class EditorAction {
mapEditor: IMapEditor | null = null;
store: IStore;
constructor(store: IStore) { constructor(store: IStore) {
this.store = store; this.store = store;
this._bindOperations(); this._bindOperations();
this._registerHotkeys(); this._registerHotkeys();
}
get mapOptions() {
return this.store.getState().map;
}
dispatch(action: Action) {
this.store.dispatch(action);
}
registerMapEditor(mapEditor: IMapEditor) {
this.mapEditor = mapEditor;
this.mapEditor.init().then(() => {
this.mapReady();
});
// 创建覆盖物结束
this.mapEditor.on(EventTypes.FinishEditOverlay, (overlay: IOverlay) => {
this.dispatch(StoreAction.finishEditOverlay(overlay));
});
}
mapReady() {
const cached = sessionStorage.getItem("fine-geojson"); const cached = sessionStorage.getItem("fine-geojson");
if (cached != null) { if (cached != null) {
setTimeout(() => { setTimeout(() => {
this._replaceState(JSON.parse(cached)); this._openProject(JSON.parse(cached));
}, 3000); }, 3000);
} }
setInterval(() => { setInterval(() => {
sessionStorage.setItem( sessionStorage.setItem("fine-geojson", JSON.stringify(this.mapOptions));
"fine-geojson",
JSON.stringify(this.store.getState())
);
console.log("updated!"); console.log("updated!");
}, 10000); }, 10000);
} }
// ---------- public methods -------------
createRectangle() {
this.createOverlay(OverlayTypes.Rectangle);
}
createPolygon() {
this.createOverlay(OverlayTypes.Polygon);
}
createPolyline() {
this.createOverlay(OverlayTypes.Polyline);
}
createCircle() {
this.createOverlay(OverlayTypes.Circle);
}
get mapState() { createOverlay(type: OverlayTypes) {
return this.store.getState().map; this.mapEditor?.createOverlay(type);
this.dispatch(StoreAction.createOverlay(type));
}
deleteOverlays() {
const { selectedIds } = this.mapOptions;
if (selectedIds?.length == 0) return;
Modal.confirm({
title: "确定删除覆盖物吗?",
onOk: () => {
// this.store.dispatch(EditorAction.deleteOverlays());
},
});
} }
saveGeoJSON() { saveGeoJSON() {
const geojson = getGeoJSON(this.mapState); const geojson = getGeoJSON(this.mapOptions);
downloadJSON(geojson, "fine.geojson"); downloadJSON(geojson, "fine.geojson");
} }
selectOverlay(id: string) {
//
}
saveProject() { saveProject() {
// //
} }
@ -71,39 +109,19 @@ export class GlobelController implements IGlobalController {
openProject() { openProject() {
// //
} }
showCommand() { showCommand() {
// //
} }
createRectangle() {
this.createOverlay(OverlayTypes.Rectangle);
}
createPolygon() {
this.createOverlay(OverlayTypes.Polygon);
}
createPolyline() {
this.createOverlay(OverlayTypes.Polyline);
}
createCircle() {
this.createOverlay(OverlayTypes.Circle);
}
createOverlay(type: OverlayTypes) { private _openProject(options: IMapOptions) {
this.store.dispatch(EditorAction.createOverlay(type)); this.mapEditor?.importProject(options);
} this.dispatch(
StoreAction.replaceState({
deleteOverlays() { ...initState,
const { selectedIds } = this.mapState; map: options,
if (selectedIds?.length == 0) return; })
Modal.confirm({ );
title: "确定删除覆盖物吗?",
onOk: () => {
this.store.dispatch(EditorAction.deleteOverlays());
},
});
}
private _replaceState(state: IEditorState) {
this.store.dispatch(EditorAction.replaceState(state));
} }
private _bindOperations() { private _bindOperations() {

15
src/store/index.ts

@ -1,15 +1,16 @@
import { createStore } from "redux"; import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
// import { createContext, Context } from "react"; // import { createContext, Context } from "react";
import { reducer } from "./reducers"; import { reducer } from "./reducers";
import { IStore } from "./type"; import { IStore } from "./type";
import { EditorAction } from "./actions"; import { EditorAction } from "./actions";
import { GlobelController } from "./GlobalController"; import { initState } from "./initState";
export * from "./type"; export * from "./type";
export * from "./selectors"; export * from "./selectors";
export * from "./constants"; export * from "./constants";
export { EditorAction }; // export { EditorAction };
declare global { declare global {
interface Window { interface Window {
@ -17,9 +18,13 @@ declare global {
} }
} }
export const store: IStore = createStore(reducer); export const store: IStore = createStore(
reducer,
initState,
applyMiddleware(thunk)
);
export const globalController = new GlobelController(store); export const editorAction = new EditorAction(store);
window.store = store; window.store = store;

3
src/store/initState.ts

@ -1,10 +1,9 @@
import { IEditorState } from "./type"; import { IEditorState } from "./type";
export const initState: IEditorState = { export const initState: IEditorState = {
map: {
status: null, status: null,
command: null,
overlayType: null, overlayType: null,
map: {
polygons: [], polygons: [],
polylines: [], polylines: [],
circles: [], circles: [],

8
src/store/reducers/index.ts

@ -26,9 +26,11 @@ const actionReducers: Record<ActionTypes, ActionReducer> = {
}; };
function replaceState(state = initState, payload: IEditorState) { function replaceState(state = initState, payload: IEditorState) {
return produce(payload, (draft) => { return payload;
draft.map.command = Command.OpenProject; // return produce(payload, (draft) => {
}); // // draft.map.command = Command.OpenProject;
// retr
// });
} }
export function reducer(state = initState, action: Action) { export function reducer(state = initState, action: Action) {

19
src/store/reducers/map/index.ts

@ -1,13 +1,13 @@
import produce from "immer"; import produce from "immer";
import { initState } from "../../initState"; import { initState } from "../../initState";
import { IOverlay, OverlayNamePrefixs } from "@store"; import { IOverlay, OverlayNamePrefixs } from "@store";
import { OverlayTypes, Status, Command } from "@types"; import { OverlayTypes, Status } from "@types";
export function createOverlay(state = initState, payload: any) { export function createOverlay(state = initState, payload: OverlayTypes) {
return produce(state, (draft) => { return produce(state, (draft) => {
draft.map.status = Status.CreateOverlay; draft.status = Status.CreateOverlay;
draft.map.command = Command.CreateOverlay; draft.overlayType = payload;
draft.map.overlayType = payload as OverlayTypes; console.log(123);
}); });
} }
@ -17,7 +17,7 @@ export function deleteOverlays(state = initState, payload: any) {
const filterFunc = (overlay: IOverlay) => overlay.id !== id; const filterFunc = (overlay: IOverlay) => overlay.id !== id;
return produce(state, (draft) => { return produce(state, (draft) => {
// draft.map.status = Status.CreateOverlay; // draft.map.status = Status.CreateOverlay;
draft.map.command = Command.DeleteOverlays; // draft.map.command = Command.DeleteOverlays;
draft.map.rectangles = draft.map.rectangles.filter(filterFunc); draft.map.rectangles = draft.map.rectangles.filter(filterFunc);
draft.map.polygons = draft.map.polygons.filter(filterFunc); draft.map.polygons = draft.map.polygons.filter(filterFunc);
draft.map.polylines = draft.map.polylines.filter(filterFunc); draft.map.polylines = draft.map.polylines.filter(filterFunc);
@ -33,9 +33,6 @@ export function finishEditOverlay(state = initState, payload: any) {
const { type, id } = overlay; const { type, id } = overlay;
// todo: uniqueName. // todo: uniqueName.
overlay.name = OverlayNamePrefixs[type] + overlay.id; overlay.name = OverlayNamePrefixs[type] + overlay.id;
draft.map.status = null;
draft.map.command = null;
draft.map.overlayType = null;
const overlays = const overlays =
type === OverlayTypes.Rectangle type === OverlayTypes.Rectangle
? draft.map.rectangles ? draft.map.rectangles
@ -50,14 +47,14 @@ export function finishEditOverlay(state = initState, payload: any) {
} else { } else {
overlays.push(overlay); overlays.push(overlay);
} }
draft.overlayType = null;
draft.status = null;
}); });
} }
export function selectOverlay(state = initState, payload: any) { export function selectOverlay(state = initState, payload: any) {
const id = payload as string; const id = payload as string;
return produce(state, (draft) => { return produce(state, (draft) => {
draft.map.command = Command.SelectOverlay;
draft.map.selectedIds = [id]; draft.map.selectedIds = [id];
draft.map.status = null;
}); });
} }

4
src/store/selectors.ts

@ -1,3 +1,5 @@
import { IEditorState } from "@store"; import { IEditorState } from "@store";
export const mapStateSelector = (state: IEditorState) => state.map; export const mapOptionsSelector = (state: IEditorState) => state.map;
export const overlayTypeSelector = (state: IEditorState) => state.overlayType;
export const statusSelector = (state: IEditorState) => state.status;

5
src/store/type.ts

@ -1,6 +1,7 @@
import { Store } from "redux"; import { Store } from "redux";
import { Action } from "./reducers"; import { Action } from "./reducers";
import { OverlayTypes, Status, Command } from "@types"; import { OverlayTypes, Status, Command } from "@types";
import { IMapOptions } from "@map";
export type IStore = Store<IEditorState, Action>; export type IStore = Store<IEditorState, Action>;
export interface IOverlay { export interface IOverlay {
@ -23,5 +24,7 @@ export interface IMapState {
} }
export interface IEditorState { export interface IEditorState {
map: IMapState; map: IMapOptions;
status: Status | null;
overlayType: OverlayTypes | null;
} }

4
src/store/utils/getGeoJSON.ts

@ -1,9 +1,9 @@
import { OverlayTypes } from "../../types/enum"; import { OverlayTypes } from "../../types/enum";
import { IMapState } from "../type"; import { IMapOptions } from "@map";
const EarthRadius = 6378137; const EarthRadius = 6378137;
export function getGeoJSON(state: IMapState): GeoJSON.FeatureCollection { export function getGeoJSON(state: IMapOptions): GeoJSON.FeatureCollection {
// //
const features: GeoJSON.Feature[] = []; const features: GeoJSON.Feature[] = [];
const { rectangles, polygons, polylines, circles } = state; const { rectangles, polygons, polylines, circles } = state;

5
yarn.lock

@ -4016,6 +4016,11 @@ react@^17.0.2:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" object-assign "^4.1.1"
redux-thunk@^2.4.1:
version "2.4.1"
resolved "https://registry.npmmirror.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"
integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==
redux@^4.0.0, redux@^4.1.2: redux@^4.0.0, redux@^4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.npmmirror.com/redux/download/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" resolved "https://registry.npmmirror.com/redux/download/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104"

Loading…
Cancel
Save