├── src ├── present │ └── gridModel.tsx ├── style │ ├── index.css │ ├── block.less │ ├── layer.less │ ├── util.less │ ├── canvas.less │ ├── App.less │ └── font.less ├── constants │ ├── grid.tsx │ ├── index.tsx │ ├── block.tsx │ └── layer.tsx ├── types │ ├── block.tsx │ ├── grid.tsx │ ├── index.tsx │ └── layer.tsx ├── App.test.tsx ├── index.tsx ├── redux │ ├── store │ │ └── index.tsx │ ├── actions │ │ ├── index.tsx │ │ ├── block.tsx │ │ └── layer.tsx │ └── reducers │ │ ├── index.tsx │ │ ├── block.tsx │ │ └── layer.tsx ├── utils │ ├── createMatrix.ts │ └── saveFile.ts ├── App.tsx ├── mixins │ └── templates.tsx ├── logo.svg ├── registerServiceWorker.ts └── container │ ├── layer.tsx │ ├── grid.tsx │ ├── block.tsx │ └── utils.tsx ├── .vscode └── settings.json ├── tsconfig.prod.json ├── react-desktop └── macOs.d.ts ├── tslint.json ├── public ├── favicon.ico ├── manifest.json └── index.html ├── tsconfig.test.json ├── redux-persist-immutable.d.ts ├── react-desktop.d.ts ├── redux-undo.d.ts ├── images.d.ts ├── config ├── jest │ ├── typescriptTransform.js │ ├── fileTransform.js │ └── cssTransform.js ├── polyfills.js ├── paths.js ├── env.js ├── webpackDevServer.config.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── .gitignore ├── tsconfig.json ├── scripts ├── test.js ├── start.js └── build.js ├── README.md └── package.json /src/present/gridModel.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | } -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } -------------------------------------------------------------------------------- /react-desktop/macOs.d.ts: -------------------------------------------------------------------------------- 1 | export const Box:any 2 | 3 | export const Text:any -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "no-implicit-dependencies": ["optional", ["src"]] 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qqqdu/React-map-editor/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /redux-persist-immutable.d.ts: -------------------------------------------------------------------------------- 1 | export function persistStore(a: any): any; 2 | export function persistReducer(a: any,b: any): any; 3 | -------------------------------------------------------------------------------- /react-desktop.d.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | declare module "react-desktop" { 4 | interface macOs { 5 | // 定义基本使用的类型 6 | Box: object; 7 | } 8 | } -------------------------------------------------------------------------------- /redux-undo.d.ts: -------------------------------------------------------------------------------- 1 | export function includeAction(a: any): any; 2 | export var ActionCreators:any 3 | export default function undoable(a: any,b: any): any; 4 | -------------------------------------------------------------------------------- /images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' 2 | declare module '*.png' 3 | declare module '*.jpg' 4 | declare module '*.jpeg' 5 | declare module '*.gif' 6 | declare module '*.bmp' 7 | declare module '*.tiff' 8 | -------------------------------------------------------------------------------- /src/style/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | padding: 100px 345px 15px 15px; 5 | font-family: sans-serif; 6 | width: 100%; 7 | height: 100%; 8 | box-sizing: border-box; 9 | } 10 | -------------------------------------------------------------------------------- /config/jest/typescriptTransform.js: -------------------------------------------------------------------------------- 1 | // Copyright 2004-present Facebook. All Rights Reserved. 2 | 3 | 'use strict'; 4 | 5 | const tsJestPreprocessor = require('ts-jest/preprocessor'); 6 | 7 | module.exports = tsJestPreprocessor; 8 | -------------------------------------------------------------------------------- /src/constants/grid.tsx: -------------------------------------------------------------------------------- 1 | export const SET_CUR_BLOCK = 'SET_CUR_BLOCK'; 2 | export type SET_CUR_BLOCK = typeof SET_CUR_BLOCK; 3 | 4 | export const SET_CUR_LAYER = 'SET_CUR_LAYER'; 5 | export type SET_CUR_LAYER = typeof SET_CUR_LAYER -------------------------------------------------------------------------------- /src/types/block.tsx: -------------------------------------------------------------------------------- 1 | export interface blockItem { 2 | id: number, 3 | src: string, 4 | width: number, 5 | height: number, 6 | name: string, 7 | extra: Array 8 | } 9 | 10 | export interface block { 11 | blockList: Array 12 | } -------------------------------------------------------------------------------- /src/constants/index.tsx: -------------------------------------------------------------------------------- 1 | export const INCREMENT_ENTHUSIASM = 'INCREMENT_ENTHUSIASM'; 2 | export type INCREMENT_ENTHUSIASM = typeof INCREMENT_ENTHUSIASM; 3 | 4 | 5 | export const DECREMENT_ENTHUSIASM = 'DECREMENT_ENTHUSIASM'; 6 | export type DECREMENT_ENTHUSIASM = typeof DECREMENT_ENTHUSIASM; -------------------------------------------------------------------------------- /src/types/grid.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { blockItem } from './block' 3 | export interface gridConfig { 4 | curBlock: blockItem | undefined; 5 | curLayerId: number | undefined; 6 | tableRow: number; 7 | tableCol: number; 8 | // 单元格宽度和高度 9 | boxWidth: number; 10 | boxHeight: number; 11 | } -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/style/block.less: -------------------------------------------------------------------------------- 1 | .block { 2 | position: fixed; 3 | right: 0; 4 | width: 300px; 5 | min-height: 60px; 6 | top: 360px; 7 | right: 30px; 8 | padding: 10px; 9 | box-shadow: 0 0 6px black; 10 | .file { 11 | display: none; 12 | } 13 | img { 14 | max-width: 100% !important; 15 | } 16 | &__content { 17 | max-height: 300px; 18 | overflow: auto; 19 | } 20 | a { 21 | font-size: 16px; 22 | } 23 | } -------------------------------------------------------------------------------- /src/constants/block.tsx: -------------------------------------------------------------------------------- 1 | 2 | // 新建 block 3 | export const CREATE_BLOCK = 'CREATE_BLOCK' 4 | export type CREATE_BLOCK = typeof CREATE_BLOCK 5 | 6 | // 删除block 7 | export const DEL_BLOCK = 'DEL_BLOCK' 8 | export type DEL_BLOCK = typeof DEL_BLOCK 9 | 10 | // 修改block 11 | export const EDIT_BLOCK = 'EDIT_BLOCK' 12 | export type EDIT_BLOCK = typeof EDIT_BLOCK 13 | 14 | // 导入block 15 | export const IMPORT_BLOCK = 'IMPORT_BLOCK' 16 | export type IMPORT_BLOCK = typeof IMPORT_BLOCK -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import '@/style/index.css'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | import { Provider } from 'react-redux' 7 | import store from '@/redux/store/index' 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') as HTMLElement 13 | ); 14 | registerServiceWorker(); 15 | -------------------------------------------------------------------------------- /src/redux/store/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {createStore, applyMiddleware} from 'redux'; 4 | import * as reducer from '../reducers'; 5 | import thunk from 'redux-thunk'; 6 | import { combineReducers } from 'redux-immutable' 7 | 8 | //创建一个 Redux store 来以存放应用中所有的 state,应用中应有且仅有一个 store。 9 | console.log('包装好的reducer') 10 | console.log(reducer) 11 | var store = createStore( 12 | combineReducers(reducer), // 将所有reducer合并成一个大的reducer 13 | applyMiddleware(thunk) //将所有中间件作为一个数组,并执行, 14 | ); 15 | 16 | export default store; -------------------------------------------------------------------------------- /src/style/layer.less: -------------------------------------------------------------------------------- 1 | .layer { 2 | position: fixed; 3 | right: 0; 4 | width: 300px; 5 | min-height: 60px; 6 | top: 30px; 7 | right: 30px; 8 | padding: 10px; 9 | box-shadow: 0 0 6px black; 10 | a { 11 | font-size: 16px; 12 | } 13 | } 14 | .layer_content { 15 | max-height: 200px; 16 | overflow-y: auto; 17 | } 18 | li { 19 | display: flex; 20 | justify-content: space-between; 21 | align-items: center; 22 | padding: 5px 0 5px; 23 | } 24 | .chooseLayer { 25 | input { 26 | color: red; 27 | } 28 | } -------------------------------------------------------------------------------- /src/types/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局类型的定义 3 | */ 4 | import { layer } from './layer' 5 | import { block } from './block' 6 | import { gridConfig } from './grid' 7 | export interface enthusiasm { 8 | languageName: string; 9 | enthusiasmLevel: number; 10 | } 11 | export interface StoreState { 12 | enthusiasm: enthusiasm; 13 | layer: layer; 14 | block: block; 15 | gridConfig: gridConfig 16 | } 17 | 18 | export interface matrixItem { 19 | src: string | undefined; 20 | width: number; 21 | row: number; 22 | col: number; 23 | height: number; 24 | name: string; 25 | } -------------------------------------------------------------------------------- /src/utils/createMatrix.ts: -------------------------------------------------------------------------------- 1 | 2 | import { layer } from "@/types/layer"; 3 | export default function createMatrix(state: layer) { 4 | const map = []; 5 | for (let i = 0; i < state.tableRow; i++) { 6 | const row = []; 7 | for (let j = 0; j < state.tableCol; j++) { 8 | const col = { 9 | src: undefined, 10 | width: state.boxWidth, 11 | row: state.tableRow, 12 | col: state.tableCol, 13 | height: state.boxHeight, 14 | name: '' 15 | }; 16 | row.push(col); 17 | } 18 | map.push(row); 19 | } 20 | return map 21 | } -------------------------------------------------------------------------------- /src/redux/actions/index.tsx: -------------------------------------------------------------------------------- 1 | import * as constants from '../../constants' 2 | console.log(constants) 3 | export interface IncrementEnthusiasm { 4 | type: constants.INCREMENT_ENTHUSIASM; 5 | } 6 | 7 | export interface DecrementEnthusiasm { 8 | type: constants.DECREMENT_ENTHUSIASM; 9 | } 10 | 11 | export type EnthusiasmAction = IncrementEnthusiasm | DecrementEnthusiasm; 12 | 13 | export function incrementEnthusiasm(): IncrementEnthusiasm { 14 | return { 15 | type: constants.INCREMENT_ENTHUSIASM 16 | } 17 | } 18 | 19 | export function decrementEnthusiasm(): DecrementEnthusiasm { 20 | return { 21 | type: constants.DECREMENT_ENTHUSIASM 22 | } 23 | } -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import './style/App.less'; 3 | // import './style/font.less' 4 | import Utils from '@/container/utils' 5 | import Layer from '@/container/layer' 6 | import Block from '@/container/block' 7 | import Grid from '@/container/grid' 8 | 9 | class App extends React.Component { 10 | public render() { 11 | return ( 12 |
ev.preventDefault() } 14 | onDragStart = { (ev) => ev.preventDefault()} 15 | > 16 | 17 | 18 | 19 | 20 |
21 | ); 22 | } 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/types/layer.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局类型的定义 3 | */ 4 | import { blockItem } from './block' 5 | import { matrixItem } from './index' 6 | import { List } from 'immutable'; 7 | export interface LayerItem { 8 | // 图层id 9 | id: number, 10 | // 图层名称 11 | name: string, 12 | // 排序 13 | sort: number, 14 | // 是否显示 15 | show: boolean, 16 | matrix: List> 17 | } 18 | export interface layer { 19 | layers: Array; 20 | curBlock: blockItem | undefined; 21 | curLayerId: number; 22 | tableRow: number; 23 | tableCol: number; 24 | // 单元格宽度和高度 25 | boxWidth: number; 26 | boxHeight: number; 27 | showLine: boolean; 28 | eraser: boolean; 29 | name: string 30 | } -------------------------------------------------------------------------------- /src/redux/reducers/index.tsx: -------------------------------------------------------------------------------- 1 | import { EnthusiasmAction } from '../actions'; 2 | import { enthusiasm } from '../../types/index'; 3 | import { INCREMENT_ENTHUSIASM, DECREMENT_ENTHUSIASM } from '../../constants/index'; 4 | import layer from './layer' 5 | import { block } from './block' 6 | const initState= { 7 | enthusiasmLevel: 1, 8 | languageName: 'TypeScript', 9 | }; 10 | export function enthusiasm(state: enthusiasm = initState, action: EnthusiasmAction): enthusiasm { 11 | switch (action.type) { 12 | case INCREMENT_ENTHUSIASM: 13 | return { ...state, enthusiasmLevel: state.enthusiasmLevel + 1 }; 14 | case DECREMENT_ENTHUSIASM: 15 | return { ...state, enthusiasmLevel: Math.max(1, state.enthusiasmLevel - 1) }; 16 | } 17 | return {...state} 18 | } 19 | 20 | export { layer, block } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "lib": ["es6", "dom"], 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "jsx": "react", 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": true, 17 | "importHelpers": true, 18 | "strictNullChecks": true, 19 | "suppressImplicitAnyIndexErrors": true, 20 | "noUnusedLocals": true, 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | }, 24 | }, 25 | "exclude": [ 26 | "node_modules", 27 | "build", 28 | "scripts", 29 | "acceptance-tests", 30 | "webpack", 31 | "jest", 32 | "src/setupTests.ts" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | let argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI, in coverage mode, or explicitly running all tests 22 | if ( 23 | !process.env.CI && 24 | argv.indexOf('--coverage') === -1 && 25 | argv.indexOf('--watchAll') === -1 26 | ) { 27 | argv.push('--watch'); 28 | } 29 | 30 | 31 | jest.run(argv); 32 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | 18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 19 | // We don't polyfill it in the browser--this is user's responsibility. 20 | if (process.env.NODE_ENV === 'test') { 21 | require('raf').polyfill(global); 22 | } 23 | -------------------------------------------------------------------------------- /src/style/util.less: -------------------------------------------------------------------------------- 1 | .util { 2 | position: fixed; 3 | right: 0; 4 | width: 300px; 5 | min-height: 60px; 6 | top: 30px; 7 | left: 30px; 8 | padding: 10px; 9 | box-shadow: 0 0 6px black; 10 | .file { 11 | display: none; 12 | } 13 | img { 14 | max-width: 100% !important; 15 | } 16 | &__content { 17 | max-height: 300px; 18 | overflow: auto; 19 | } 20 | a { 21 | font-size: 16px; 22 | } 23 | } 24 | li { 25 | display: flex !important; 26 | align-items: center; 27 | i { 28 | margin-left: 10px; 29 | } 30 | p { 31 | margin-bottom: 0; 32 | } 33 | } 34 | .file { 35 | display: none !important; 36 | } 37 | .hidden { 38 | display: none !important; 39 | } 40 | .export_choose { 41 | width: 100%; 42 | height: 60px; 43 | display: inline-block; 44 | line-height: 60px; 45 | text-align: center; 46 | &.choose { 47 | border: 1px solid #08c; 48 | } 49 | span { 50 | color: red; 51 | } 52 | } -------------------------------------------------------------------------------- /src/style/canvas.less: -------------------------------------------------------------------------------- 1 | 2 | .item { 3 | width: 40px; 4 | height: 40px; 5 | flex-shrink: 0; 6 | flex-grow: 0; 7 | text-align: left; 8 | } 9 | .item-grid { 10 | border: .5px solid #08c; 11 | } 12 | .item img { 13 | width: 100%; 14 | height: 100%; 15 | user-select: none; 16 | } 17 | .table-item { 18 | width: 100%; 19 | height: 100%; 20 | position: relative; 21 | overflow-x: auto; 22 | border:1px solid black; 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | flex-wrap: wrap; 27 | flex-shrink: 0; 28 | flex-grow: 0; 29 | } 30 | .layer-item { 31 | width: 100%; 32 | height: 100%; 33 | position: absolute; 34 | left: 0; 35 | top: 0; 36 | } 37 | .row { 38 | width: 100%; 39 | height: auto; 40 | display: flex; 41 | } 42 | .m-choose { 43 | position: absolute; 44 | width: 100px; 45 | height: 100px; 46 | background: #08c; 47 | opacity: .5; 48 | } 49 | .current-img { 50 | position:absolute; 51 | right: 40px; 52 | top: 20px; 53 | } -------------------------------------------------------------------------------- /src/style/App.less: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; 2 | body { 3 | font-size: 62.5%; 4 | } 5 | #root { 6 | width: 100%; 7 | height: 100%; 8 | } 9 | .App { 10 | text-align: center; 11 | width: 100%; 12 | height: 100%; 13 | } 14 | 15 | .App-logo { 16 | animation: App-logo-spin infinite 20s linear; 17 | height: 80px; 18 | } 19 | 20 | .App-header { 21 | background-color: #222; 22 | height: 150px; 23 | padding: 20px; 24 | color: white; 25 | } 26 | 27 | .App-title { 28 | font-size: 1.5em; 29 | } 30 | 31 | .App-intro { 32 | font-size: large; 33 | } 34 | 35 | @keyframes App-logo-spin { 36 | from { transform: rotate(0deg); } 37 | to { transform: rotate(360deg); } 38 | } 39 | a { 40 | text-decoration: none; 41 | color: #333; 42 | a:hover { 43 | color: #333; 44 | } 45 | } 46 | li { 47 | list-style: none; 48 | } 49 | .tools { 50 | display: flex; 51 | justify-content: space-between; 52 | align-items: center; 53 | height: 2rem; 54 | color: #333; 55 | font-weight: bold; 56 | } 57 | h3 { 58 | text-align: left; 59 | } -------------------------------------------------------------------------------- /src/redux/actions/block.tsx: -------------------------------------------------------------------------------- 1 | import * as constants from '../../constants/block' 2 | import { blockItem } from '@/types/block' 3 | export interface DelBlock { 4 | type: constants.DEL_BLOCK 5 | payload: { 6 | id: number 7 | } 8 | } 9 | export interface CreateBlock { 10 | type: constants.CREATE_BLOCK 11 | payload: blockItem 12 | } 13 | export interface EditBlock { 14 | type: constants.EDIT_BLOCK 15 | payload: { 16 | width: number 17 | height: number, 18 | id: number, 19 | extra: Array 20 | } 21 | } 22 | export interface ImportBLOCK { 23 | type: constants.IMPORT_BLOCK; 24 | payload: Array 25 | } 26 | 27 | export type blockActions = DelBlock | CreateBlock | EditBlock | ImportBLOCK 28 | 29 | export function DelBlock(payload: { id: number }): DelBlock { 30 | return { 31 | type: constants.DEL_BLOCK, 32 | payload 33 | } 34 | } 35 | export function createBlock(payload: blockItem): CreateBlock { 36 | return { 37 | type: constants.CREATE_BLOCK, 38 | payload 39 | } 40 | } 41 | export function editBlock(payload: { 42 | width: number 43 | height: number, 44 | id: number, 45 | extra: Array 46 | }): EditBlock { 47 | return { 48 | type: constants.EDIT_BLOCK, 49 | payload 50 | } 51 | } 52 | export function importBLock(payload: Array): ImportBLOCK { 53 | return { 54 | type: constants.IMPORT_BLOCK, 55 | payload 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/mixins/templates.tsx: -------------------------------------------------------------------------------- 1 | // /* 2 | // * 混合 connect到各个组件。 3 | // * 如果想要调用store里面的数据必须要被connect 4 | // */ 5 | // import * as React from 'react'; 6 | // import { connect } from 'react-redux' 7 | // import { is, fromJS} from 'immutable'; 8 | // import *as action from '../Redux/Action/index'; 9 | 10 | // export const template = mySeting => { 11 | // let seting = { 12 | // id: '', //应用唯一id表示 13 | // url: '', //请求地址 14 | // data: {}, //发送给服务器的数据 15 | // component:
, //数据回调给的组件 16 | // }; 17 | // for(let key in mySeting){ 18 | // seting[key] = mySeting[key]; 19 | // } 20 | // class Index extends React.Component { 21 | // static defaultProps = { seting } 22 | 23 | // constructor (props, context){ 24 | // super(props, context) 25 | // } 26 | 27 | // render() { 28 | // return ; // 把immutabel类型再转为js类型 29 | // } 30 | // } 31 | // // Index其实是顶级组件,包括了其他模板组件,connect将其与其他路由组件链接起来 32 | 33 | // return connect(state => { 34 | 35 | // let {loginOrNot, mailList, unRead, mailWords} = state; 36 | // //console.log(loginOrNot,mailList,state) 37 | // console.log(loginOrNot,mailList,unRead) 38 | // return { 39 | // state: state['fetchData'], 40 | // loginOrNot, 41 | // mailList, 42 | // unRead, 43 | // mailWords 44 | // } 45 | // }, action)(Index); //连接redux 46 | // } 47 | 48 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2d游戏地图编辑器 2 | 3 | 去年(2018)在和小伙伴们开发一款横版滚轴平台游戏,用到了 `Tiled Map` 编辑器,但在使用的过程中遇到了很多坑,用过它的人都知道。 4 | 5 | Tiled 功能很强大,但我需要的功能好像不是很多,是否能造个轮子?来简单快速的进行地图编辑 6 | 7 | ## 导入demo 8 | 9 | 在左上角的导入按钮中,导入根目录中的 `Hello World.json` 地图资源文件,得益于 图片被转成了 `base 64` 字符串,你可以快捷的预览到地图效果,而不需要下载图片原资源 10 | ## 导出demo 11 | 12 | 现可以导出两种类型,一种编辑器可以解析的地图文件,一种游戏引擎解析的地图文件,点击左上角导出,可选择导出类型 13 | 14 | ## 游戏内应用 15 | 我写了一个简单的基于白鹭引擎的地图解析demo,你可以去这看它 [egret Map](https://github.com/checkmind/Map-editer-parse) 16 | 地图json格式为: 17 | ``` typescript 18 | 19 | export interface matrixItem { 20 | src: string | undefined; 21 | width: number; 22 | row: number; 23 | col: number; 24 | height: number; 25 | name: string; 26 | extra?: Array 27 | } 28 | 29 | export interface LayerItem { 30 | // 图层id 31 | id: number, 32 | // 图层名称 33 | name: string, 34 | // 是否显示 35 | show: boolean, 36 | matrix: Array> 37 | } 38 | export interface layer { 39 | layers: Array; 40 | // 表格横轴个数 41 | tableRow: number; 42 | // 表格纵轴个数 43 | tableCol: number; 44 | // 单元格宽度和高度 45 | boxWidth: number; 46 | boxHeight: number; 47 | name: string 48 | } 49 | ``` 50 | 51 | ## 编辑器 52 | 53 | ### 新建项目 54 | 55 | ![新建项目](https://i.loli.net/2019/04/21/5cbc45122f27d.png) 56 | 57 | ### 功能简介 58 | 59 | ![新建项目](https://i.loli.net/2019/04/21/5cbc45125c508.png) 60 | 61 | ### 制作地图 62 | 63 | ![新建项目](https://i.loli.net/2019/04/21/5cbc4512df1eb.png) 64 | 65 | ### 改变图块属性 66 | 67 | 每当图块改变时,之前已画在地图的图块不会随之改变,考虑到图块在使用时会有不同属性的需求,图块每次改变都是新的! 68 | 69 | ![新建项目](https://i.loli.net/2019/04/21/5cbc4512e7cbe.png) 70 | 71 | ### 关闭辅助线 72 | 73 | ![新建项目](https://i.loli.net/2019/04/21/5cbc45130c064.png) 74 | 75 | ## 现有功能 76 | 77 | - 图层:新建、上移、下移、重命名、删除 78 | - 图块:新建、删除、编辑大小、添加额外属性 79 | - 工具:橡皮擦、导入、导出、撤销、取消撤销、全局属性 80 | - 网格:是否显示网格 81 | 82 | ## TODO 83 | 没啥TODO了 84 | -------------------------------------------------------------------------------- /src/redux/reducers/block.tsx: -------------------------------------------------------------------------------- 1 | import { blockActions } from '../actions/block'; 2 | import { block, blockItem } from '@/types/block'; 3 | import { DEL_BLOCK, CREATE_BLOCK, EDIT_BLOCK , IMPORT_BLOCK} from '@/constants/block'; 4 | import { fromJS, Map, List } from 'immutable'; 5 | const initState= fromJS({ 6 | blockList: List([]) 7 | }); 8 | export function block(state: Map = initState, action: blockActions): Map> { 9 | switch (action.type) { 10 | case DEL_BLOCK: 11 | return delBlock(state, action.payload) 12 | case CREATE_BLOCK: 13 | return createBlock(state, action.payload) 14 | case EDIT_BLOCK: 15 | return editBlock(state, action.payload) 16 | case IMPORT_BLOCK: 17 | return importBlock(state, action.payload) 18 | } 19 | return state 20 | } 21 | function delBlock(state: Map, payload: {id: number}): Map> { 22 | const blocks = (state.get('blockList') as List).toJS() 23 | blocks.splice(payload.id, 1) 24 | return state.set('blockList', List(blocks)) 25 | } 26 | function createBlock(state: Map, payload: blockItem): Map> { 27 | const blocks = (state.get('blockList') as List).toJS() 28 | blocks.push(payload) 29 | return state.set('blockList', List(blocks)) 30 | } 31 | function editBlock(state: Map, payload: {width: number,height: number, id: number, 32 | extra: Array}) { 33 | const blocks = (state.get('blockList') as List).toJS() 34 | let curBlock = blocks.find(item => { 35 | return item.id === payload.id 36 | }) 37 | Object.assign(curBlock, { 38 | width: payload.width, 39 | height: payload.height, 40 | extra: payload.extra 41 | }) 42 | return state.set('blockList', List(blocks)) 43 | } 44 | function importBlock(state: Map, payload: Array) { 45 | console.log(payload) 46 | return state.set('blockList', List(payload)) 47 | } -------------------------------------------------------------------------------- /src/constants/layer.tsx: -------------------------------------------------------------------------------- 1 | import { matrixItem } from '@/types/index' 2 | export const CHANGE_LAYER_NAME = "CHANGE_LAYER_NAME"; 3 | export type CHANGE_LAYER_NAME = typeof CHANGE_LAYER_NAME; 4 | // 修改layer name 接口参数 5 | export interface RENAME_INTER { 6 | name: string; 7 | id: number; 8 | } 9 | // 新建图层 10 | export const CREATE_LAYER = "CREATE_LAYER"; 11 | export type CREATE_LAYER = typeof CREATE_LAYER; 12 | // 删除图层 13 | export const DEL_LAYER = "DEL_LAYER"; 14 | export type DEL_LAYER = typeof DEL_LAYER; 15 | // toggle 图层 16 | export const TOGGLE_LAYER = "TOGGLE_LAYER"; 17 | export type TOGGLE_LAYER = typeof TOGGLE_LAYER; 18 | // switch 图层位置 19 | 20 | export enum upDown { 21 | UP, 22 | DOWN 23 | } 24 | export interface SWITCH_LAYER_PAYLOAD { 25 | index: number; 26 | type: upDown; 27 | } 28 | export const SWITCH_LAYER = "SWITCH_LAYER"; 29 | export type SWITCH_LAYER = typeof SWITCH_LAYER; 30 | 31 | // 创建矩阵 32 | export const CREATE_MATRIX = "CREATE_MATRIX"; 33 | export type CREATE_MATRIX = typeof CREATE_MATRIX; 34 | export interface matrixInter { 35 | matrix: Array>, 36 | id: number 37 | } 38 | export const DRAW_MATRIX = "DRAW_MATRIX"; 39 | export type DRAW_MATRIX = typeof DRAW_MATRIX 40 | // 设置当前块 41 | export const SET_CUR_BLOCK = 'SET_CUR_BLOCK'; 42 | export type SET_CUR_BLOCK = typeof SET_CUR_BLOCK; 43 | // 设置当前图层 44 | export const SET_CUR_LAYER = 'SET_CUR_LAYER'; 45 | export type SET_CUR_LAYER = typeof SET_CUR_LAYER 46 | // 设置属性 47 | export const SET_GRID_INF = 'SET_GRID_INF'; 48 | export type SET_GRID_INF = typeof SET_GRID_INF 49 | export interface GRIDINF { 50 | tableCol: number; 51 | tableRow: number; 52 | boxWidth: number; 53 | boxHeight: number; 54 | name: string; 55 | } 56 | // 辅助线 57 | export const SHOW_LINE ='SHOW_LINE'; 58 | export type SHOW_LINE = typeof SHOW_LINE 59 | 60 | // 导入图层 61 | export const IMPORT_LAYER = 'IMPORT_LAYER'; 62 | export type IMPORT_LAYER = typeof IMPORT_LAYER 63 | 64 | // 切换橡皮擦 65 | export const SWITCH_ERSER = 'SWITCH_ERSER'; 66 | export type SWITCH_ERSER = typeof SWITCH_ERSER 67 | // 删除图层里的图块 68 | export const DEL_ERSER_BLOCK = 'DEL_ERSER_BLOCK'; 69 | export type DEL_ERSER_BLOCK = typeof DEL_ERSER_BLOCK 70 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right