feat autosave&optimize ide

This commit is contained in:
yehuozhili 2020-09-15 16:00:42 +08:00
parent 40b7cd5d13
commit c70f70e646
13 changed files with 466 additions and 784 deletions

View File

@ -50,20 +50,15 @@
},
"dependencies": {
"@ant-design/icons": "^4.2.1",
"@types/node": "^14.6.2",
"@umijs/plugin-sass": "^1.1.1",
"@umijs/preset-react": "1.x",
"@umijs/test": "^3.2.19",
"antd": "^4.2.3",
"antd-img-crop": "^3.10.0",
"axios": "^0.19.2",
"babel-plugin-import": "^1.13.0",
"chatbot-antd": "^0.6.0",
"codemirror": "^5.57.0",
"file-saver": "^2.0.2",
"lint-staged": "^10.0.7",
"node-sass": "^4.14.1",
"prettier": "^1.19.1",
"qrcode.react": "^1.0.0",
"react": "^16.12.0",
"react-codemirror2": "^7.2.1",
@ -73,9 +68,9 @@
"react-dom": "^16.12.0",
"react-draggable": "^4.4.3",
"react-grid-layout": "^1.0.0",
"react-hotkeys-hook": "^2.3.1",
"react-text-loop": "^2.3.0",
"redux-undo": "^1.0.1",
"sass-loader": "^9.0.3",
"socket.io-client": "^2.3.0",
"umi": "^3.2.19",
"video-react": "^0.14.1",
@ -85,11 +80,16 @@
"license": "MIT",
"devDependencies": {
"@types/classnames": "^2.2.10",
"@types/codemirror": "^0.0.98",
"@types/events": "^3.0.0",
"@types/file-saver": "^2.0.1",
"@types/node": "^14.6.2",
"@types/react-color": "^3.0.4",
"@types/redux-logger": "^3.0.8",
"@typescript-eslint/eslint-plugin": "2.x",
"@typescript-eslint/parser": "2.x",
"babel-eslint": "10.x",
"babel-plugin-import": "^1.13.0",
"eslint": "6.x",
"eslint-config-react-app": "^5.2.1",
"eslint-plugin-flowtype": "4.x",
@ -97,6 +97,15 @@
"eslint-plugin-jsx-a11y": "6.x",
"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "2.x",
"redux-logger": "^3.0.6"
"koa": "^2.13.0",
"koa-body": "^4.2.0",
"koa-logger": "^3.2.1",
"koa-static": "^5.0.0",
"koa2-cors": "^2.0.6",
"lint-staged": "^10.0.7",
"prettier": "^1.19.1",
"redux-logger": "^3.0.6",
"sass-loader": "^9.0.3",
"typescript": "^4.0.2"
}
}

View File

@ -4,7 +4,6 @@ const staticServer = require('koa-static');
const koaBody = require('koa-body');
const cors = require('koa2-cors');
const logger = require('koa-logger');
const fs = require('fs');
const app = new Koa();
@ -48,4 +47,4 @@ app.use(async (ctx, next) => {
}
});
app.listen(80);
app.listen(3000);

View File

@ -2,10 +2,11 @@ import { createLogger } from 'redux-logger';
import { message } from 'antd';
import undoable, { StateWithHistory } from 'redux-undo';
import { Reducer, AnyAction } from 'redux';
import { isDev } from './utils/tool';
export const dva = {
config: {
onAction: createLogger(),
onAction: isDev ? createLogger() : undefined,
onError(e: Error) {
message.error(e.message, 3);
},

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, memo, useMemo } from 'react';
import React, { useState, useEffect, useMemo } from 'react';
import { Slider, Result, Tabs, Alert } from 'antd';
import {
PieChartOutlined,
@ -91,6 +91,7 @@ const Container = props => {
if (window.innerWidth < 1024) {
props.history.push('/mobileTip');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const allType = useMemo(() => {

View File

@ -108,7 +108,7 @@ const HeaderComponent = memo(props => {
try {
localStorage.setItem('myH5', JSON.stringify(prev ? [...prev, pointData] : [pointData]));
} catch (err) {
throw error(err);
console.error(err);
}
clearData();
};

View File

@ -43,16 +43,16 @@
.codeWrap {
width: calc(100vw - 440px);
min-height: 560px;
height: 100%;
}
:global(.cm-s-material.CodeMirror) {
height: 694px;
overflow: auto;
height: 100%;
}
.previewWrap {
margin: 20px 30px;
margin: 0 30px;
margin-top: 30px;
width: 375px;
min-width: 375px;
min-height: 679px;
overflow: auto;
border: 12px solid #000;
border-radius: 20px;

View File

@ -1,26 +1,18 @@
import React from 'react';
import { UnControlled as CodeMirror } from 'react-codemirror2';
import { useState, useEffect } from 'react';
import { Button } from 'antd';
import React, { useMemo } from 'react';
import { Controlled } from 'react-codemirror2';
import { useState } from 'react';
import { Button, message } from 'antd';
import { saveAs } from 'file-saver';
import { history } from 'umi';
import Logo from '@/assets/logo.png';
import styles from './index.less';
import { isDev, useGetRect } from 'utils/tool';
import { SaveOutlined } from '@ant-design/icons';
import { useHotkeys } from 'react-hotkeys-hook';
require('codemirror/mode/xml/xml');
require('codemirror/mode/javascript/javascript');
interface CursorData {
line: number;
ch: number;
[property: string]: number;
}
const isDev = process.env.NODE_ENV === 'development';
const serverUrl = isDev ? 'http://localhost:3000' : 'http://localhost:3000';
let timer: any = null;
let html = `<!DOCTYPE html>
<html lang="en">
<head>
@ -58,38 +50,86 @@ let html = `<!DOCTYPE html>
export default function() {
const [isUpdate, setUpdate] = useState(false);
const [cursor, setCursor] = useState<CursorData>({ line: 1, ch: 1 });
const handleChange = (editor: any, data: any, value: string) => {
// codeStr = value
fetchPage(value);
const [cursor, setCursor] = useState<CodeMirror.Position>({ line: 1, ch: 1 });
const [data, setData] = useState<{ data: string }>({ data: html });
const handleChange = (
_editor: CodeMirror.Editor,
_data: CodeMirror.EditorChange,
value: string,
) => {
setData({ data: value });
};
const fetchPage = (v: string) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fetch(`${serverUrl}/dooring/render`, { method: 'POST', body: v }).then(res => {
html = v;
const fetchPage = useMemo(() => {
return (v?: string) => {
let res = v ?? data.data;
fetch(`${serverUrl}/dooring/render`, { method: 'POST', body: res }).then(() => {
html = res;
message.success('已保存');
setUpdate(prev => !prev);
});
}, 1000);
};
};
}, [data]);
const downLoadHtml = () => {
var file = new File([html], `${Date.now()}.html`, { type: 'text/html;charset=utf-8' });
var file = new File([data.data], `${Date.now()}.html`, { type: 'text/html;charset=utf-8' });
saveAs(file);
};
const onCursorChange = (editor: any, data: CursorData) => {
const { line, ch } = data;
const onCursorChange = (_editor: CodeMirror.Editor, data1: CodeMirror.Position) => {
const { line, ch } = data1;
setCursor({ line, ch });
};
useEffect(() => {
fetch(`${serverUrl}/dooring/render`, { method: 'POST', body: html }).then(res => {
setUpdate(prev => !prev);
});
useHotkeys<HTMLDivElement>(
'ctrl+s',
event => {
fetchPage();
event.preventDefault();
},
[data],
);
const editHotKey = useMemo(() => {
return (editor: CodeMirror.Editor, event: KeyboardEvent) => {
if (event.ctrlKey && event.key === 's') {
fetchPage(editor.getValue());
event.preventDefault();
}
};
}, [fetchPage]);
const CodeMirrorRender = useMemo(() => {
return (
<Controlled
className={styles.codeWrap}
value={data.data}
options={{
mode: 'xml',
theme: 'material',
lineNumbers: true,
}}
onBeforeChange={handleChange}
cursor={cursor}
onCursor={onCursorChange}
onKeyDown={editHotKey}
/>
);
}, [cursor, data.data, editHotKey]);
const rect = useGetRect();
const height = useMemo(() => {
let res = rect.height - 42 - 1; //-1防止差值产生滚动条
return res < 694 ? 694 : res;
}, [rect.height]);
const phoneHeight = useMemo(() => {
//let res = rect.height - 42 - 30 - 1; //30是其上边距
//return res < 694 ? 694 : res;
return 694; //大屏幕过长,维持高度,需要变高另外处理
}, []);
const iframeHeight = useMemo(() => {
return phoneHeight - 30 - 12 - 12; //上边距30 上下padding 12
}, [phoneHeight]);
return (
<div className={styles.wrap}>
<div className={styles.header}>
@ -102,6 +142,14 @@ export default function() {
&nbsp;&nbsp;| 线
</div>
<div className={styles.operationBar}>
<Button
type="primary"
title="保存ctrl+s"
onClick={() => fetchPage()}
style={{ marginRight: '10px' }}
>
<SaveOutlined />
</Button>
<Button type="primary" onClick={downLoadHtml} style={{ marginRight: '10px' }}>
</Button>
@ -110,26 +158,22 @@ export default function() {
</Button>
</div>
</div>
<div className={styles.contentWrap}>
<div className={styles.codeWrap}>
<CodeMirror
className={styles.codeWrap}
value={html}
options={{
mode: 'xml',
theme: 'material',
lineNumbers: true,
}}
onChange={handleChange}
cursor={cursor}
onCursor={onCursorChange}
/>
<div className={styles.contentWrap} style={{ height: `${height}px`, position: 'relative' }}>
<div className={styles.codeWrap} style={{ height: `${height}px`, position: 'relative' }}>
{CodeMirrorRender}
</div>
<div className={styles.previewWrap}>
<div className={styles.previewWrap} style={{ height: `${phoneHeight}px` }}>
<iframe
title="preview"
src={`${serverUrl}/html?flag=${isUpdate}`}
style={{ width: '100%', height: '100%', margin: 0, padding: 0, border: 'none' }}
style={{
width: '100%',
height: `${iframeHeight}px`,
margin: 0,
padding: 0,
border: 'none',
}}
></iframe>
</div>
</div>

View File

@ -1,3 +1,4 @@
import { useEffect, useState } from 'react';
import { RGBColor } from 'react-color';
// 生成uuid
@ -38,3 +39,16 @@ function rgba2Obj(rgba = '') {
}
export { uuid, rgba2Obj };
export const isDev = process.env.NODE_ENV === 'development';
export function useGetRect() {
const [rect, setRect] = useState({ width: 0, height: 0 });
useEffect(() => {
setRect({
width: window.innerWidth,
height: window.innerHeight,
});
}, []);
return rect;
}

View File

@ -1,10 +0,0 @@
html, body, #root {
height: 100%;
}
body {
margin: 0;
}
@import '~codemirror/lib/codemirror.css';
@import '~codemirror/theme/material.css';

View File

@ -1,24 +0,0 @@
.wrap {
display: flex;
}
.codeWrap {
width: calc(100vw - 440px);
min-height: 600px;
}
:global(.cm-s-material.CodeMirror) {
height: 694px;
overflow: auto;
}
.operationBar {
margin-top: 20px;
text-align: right;
}
.previewWrap {
margin: 20px 30px;
width: 375px;
min-width: 375px;
min-height: 679px;
overflow: auto;
border: 12px solid #000;
border-radius: 20px;
}

View File

@ -1,114 +0,0 @@
import { UnControlled as CodeMirror } from 'react-codemirror2';
import { useState, useEffect } from 'react';
import { Button } from 'antd';
import { saveAs } from 'file-saver';
import styles from './index.css';
require('codemirror/mode/xml/xml');
require('codemirror/mode/javascript/javascript');
let timer = null;
let html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html,body {
margin: 0;
padding: 0;
}
#root {
padding-top: 200px;
text-align: center;
}
p {
padding: 0 10px;
color: #06c;
line-height: 1.8;
font-size: 12px;
}
</style>
</head>
<body>
<div id="root">
<img src="http://io.nainor.com/uploads/logo_1747374040f.png" />
<p>
(H5编辑器)H5-Dooring是一款功能强大开源免费的H5可视化页面配置解决方案
致力于提供一套简单方便专业可靠无限可能的H5落地页最佳实践
</p>
</div>
</body>
</html>
`;
export default function() {
const [isUpdate, setUpdate] = useState(false);
const [cursor, setCursor] = useState({ line: 1, cn: 1 });
const handleChange = (editor, data, value) => {
// codeStr = value
fetchPage(value);
};
const fetchPage = v => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fetch('http://localhost:80/dooring/render', { method: 'POST', body: v }).then(res => {
html = v;
setUpdate(prev => !prev);
});
}, 1000);
};
const downLoadHtml = () => {
var file = new File([html], `${Date.now()}.html`, { type: 'text/html;charset=utf-8' });
saveAs(file);
};
const onCursorChange = (editor, data) => {
const { line, ch } = data;
setCursor({ line, ch });
};
useEffect(() => {
fetch('http://localhost:80/dooring/render', { method: 'POST', body: html }).then(res => {
setUpdate(prev => !prev);
});
}, []);
return (
<div className={styles.wrap}>
<div className={styles.codeWrap}>
<CodeMirror
className={styles.codeWrap}
value={html}
options={{
mode: 'xml',
theme: 'material',
lineNumbers: true,
}}
onChange={handleChange}
cursor={cursor}
onCursor={onCursorChange}
/>
<div className={styles.operationBar}>
<Button type="primary" onClick={downLoadHtml} style={{ marginRight: '10px' }}>
下载页面
</Button>
<Button danger onClick={downLoadHtml}>
一键部署
</Button>
</div>
</div>
<div className={styles.previewWrap}>
<iframe
src={`http://localhost:80/html?flag=${isUpdate}`}
style={{ width: '100%', height: '100%', margin: 0, padding: 0, border: 'none' }}
></iframe>
</div>
</div>
);
}

View File

@ -1,51 +0,0 @@
const Koa = require('koa');
const { resolve } = require('path');
const staticServer = require('koa-static');
const koaBody = require('koa-body');
const cors = require('koa2-cors');
const logger = require('koa-logger');
const fs = require('fs');
const app = new Koa();
app.use(staticServer(resolve(__dirname, './static')));
app.use(koaBody());
app.use(logger());
// 设置跨域
app.use(
cors({
origin: function(ctx) {
if (ctx.url.indexOf('/dooring') > -1) {
return '*'; // 允许来自所有域名请求
}
return '';
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization', 'x-test-code'],
maxAge: 5, // 该字段可选,用来指定本次预检请求的有效期,单位为秒
credentials: true,
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowHeaders: [
'Content-Type',
'Authorization',
'Accept',
'x-requested-with',
'Content-Encoding',
],
}),
);
let htmlStr = '';
app.use(async (ctx, next) => {
console.log(ctx.url);
if (ctx.url === '/dooring/render') {
htmlStr = ctx.request.body;
ctx.body = 'success';
} else if (ctx.url.indexOf('/html') === 0) {
ctx.type = 'html';
ctx.body = htmlStr;
}
});
app.listen(80);

847
yarn.lock

File diff suppressed because it is too large Load Diff