mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-11 11:51:27 +00:00
71
frontend/src/css/umind.css
Normal file
71
frontend/src/css/umind.css
Normal file
@@ -0,0 +1,71 @@
|
||||
#wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.umind-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.umind-container .umind-header {
|
||||
padding: 8px;
|
||||
border: 1px solid #E6E9ED;
|
||||
}
|
||||
|
||||
.umind-container .umind-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toolbar-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.toolbar-container .custom-toolbar {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.toolbar-container .common-toolbar {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.umind-body .umind-editor-content,
|
||||
.umind-body .umind-editor-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.umind-editor-content .umind-editor {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.umind-editor-sidebar {
|
||||
background: #FAFAFA;
|
||||
}
|
||||
|
||||
.umind-editor-sidebar:first-child {
|
||||
border-right: 1px solid #E6E9ED;
|
||||
}
|
||||
|
||||
.umind-editor-sidebar:last-child {
|
||||
border-left: 1px solid #E6E9ED;
|
||||
}
|
||||
|
||||
.umind-editor-sidebar .detail-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
background: #FAFAFA;
|
||||
}
|
||||
|
||||
.detail-panel .node-detail {
|
||||
flex: 1;
|
||||
background: #FAFAFA;
|
||||
}
|
76
frontend/src/umind/index.js
Normal file
76
frontend/src/umind/index.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Row, Col } from 'antd';
|
||||
import GGEditor, { Mind } from 'gg-editor';
|
||||
import { seafileAPI } from '../utils/seafile-api';
|
||||
import UMindToolbar from './umind-toolbar/umind-toolbar';
|
||||
import UMindDetailPanel from './umind-detail-panel';
|
||||
import UMindEditorMinimap from './umind-editor/umind-editor-minimap';
|
||||
import UMindContextMenu from './umind-editor/umind-context-menu';
|
||||
import Loading from '../components/loading';
|
||||
|
||||
// import data from './mock.js';
|
||||
import 'antd/dist/antd.css';
|
||||
import './theme/iconfont.css'
|
||||
import '../css/umind.css';
|
||||
|
||||
const propTypes = {
|
||||
|
||||
};
|
||||
|
||||
const { repoID, fileName, filePath } = window.app.pageOptions;
|
||||
|
||||
class UMind extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true,
|
||||
data: ''
|
||||
};
|
||||
this.umindContent = '';
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.getFileDownloadLink(repoID, filePath).then(res => {
|
||||
let url = res.data;
|
||||
seafileAPI.getFileContent(url).then(res => {
|
||||
let data = res.data;
|
||||
this.umindContent = data;
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
data: data
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<GGEditor className="umind-container">
|
||||
<Row type="flex" className="umind-header">
|
||||
<Col span={24} className="toolbar-container">
|
||||
<UMindToolbar />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type="flex" className="umind-body">
|
||||
<Col span={20} className="umind-editor-content">
|
||||
{this.state.isLoading && <Loading />}
|
||||
{!this.state.isLoading && (
|
||||
<Mind data={this.umindContent} className="umind-editor" />
|
||||
)}
|
||||
</Col>
|
||||
<Col span={4} className="umind-editor-sidebar">
|
||||
<UMindDetailPanel />
|
||||
<UMindEditorMinimap />
|
||||
</Col>
|
||||
</Row>
|
||||
<UMindContextMenu />
|
||||
</GGEditor>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UMind.propTypes = propTypes;
|
||||
|
||||
export default UMind;
|
114
frontend/src/umind/theme/iconfont.css
Normal file
114
frontend/src/umind/theme/iconfont.css
Normal file
@@ -0,0 +1,114 @@
|
||||
|
||||
@font-face {
|
||||
font-family: "iconfont";
|
||||
src: url('//at.alicdn.com/t/font_598462_3xve1872wizzolxr.eot?t=1522149591264');
|
||||
src: url('//at.alicdn.com/t/font_598462_3xve1872wizzolxr.eot?t=1522149591264#iefix') format('embedded-opentype'),
|
||||
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAxkAAsAAAAAFhgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kqhY21hcAAAAYAAAACyAAACShfSB3RnbHlmAAACNAAAB60AAA5gHgLkoWhlYWQAAAnkAAAALwAAADYQ7NeLaGhlYQAAChQAAAAgAAAAJAfsA4tobXR4AAAKNAAAABgAAABEQ+8AAGxvY2EAAApMAAAAJAAAACQZwB0obWF4cAAACnAAAAAfAAAAIAE3AMhuYW1lAAAKkAAAAUUAAAJtPlT+fXBvc3QAAAvYAAAAigAAAL30adEGeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/s84gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVLzYwNzwv4EhhrmBoQEozAiSAwAy2Q0leJzFkdkRwjAMBdcQwn0UQiEUxEeGcrhvKIUyXhfwbPFDBWhmrZFsJx4t0AHaZm4qSCsSOZbuptJvMyj9ioXr2jnRotHz/XbdaF2yj3ivLrnlO5W/XNOlR9/3h4wYM2HKzAdq/hbpf7/+jVFZX9/KU6H54idqHXiSaBNkS9oG2ZR2QTaofZDnr0Pg2aNjYAvoFNgHOgfZrC6BHaFrkF+nW2Bv6B7YIHoEdomeAbMPnYI8RwAAeJyNV11sHFcVPufO3Bnv2Lvr8f5M7GRt7453xsVrp9lfsvHPIMImKXGBphWkULIRIjw0oYCRojpUHYFIg0gEUlKBXBAhqVIJHgAlEtRUYp5Mxc8TT414KALaJx5QH5BCZ8y5M7vO2l4Vr/f+nTn33nPOfOd8a+AAm3+T3pD2QQqm4RAchU8CoDKDhQTLYd6uzrEZzOR5xkgnJNu086pZmJMW0Cgo6Wy5XrUMRVWSmMBxrOTLdXuO2VirLrIjWM7mEEf3j50aKR4Ykb6P2j57/NvBY+wWZibMA8nF2eBEaSldnkwNXBwaGRkdGbk6oHA+wJicTOAFIxvjMU0JXuXJscwbE4+wCRwatcdOno5P7h85e6X65VzRiCG6Lqb2TyZeW9LHdPp+YyybGhlVh+MD+8bi5lQaL/5jcF9qKGf9Hegjk68b0obkwEE40vF0HjN1y46RA8Y4GqpF3qSNbLmxiHbDULJiSs/nULUXsWHVUSnYVrVRLxvZdKpezqaVglXl2bTalUvtQo4Ha8EdeTRn3HjKWfrqo83m/CvzM5lU61g6OzO/tnBYaZZWHad53maz/n/NtlkoiO6Lyelcbnocr0RL6th1lOTHzrJgDU/j0x975EdPOnnjEB3WbJaerbda9eem6eyX50uZvNNcKQ0c9u/StjNThcLUGVxIjIvzfjZ1xoyEof8AkiNRByoMQpL8r+hm1DJmhhqC4zBqm9Qc0vRBNOY4ngMgsOLKKsUvDsOQBRs+RMJ8wbJ1Ck7e0BMoiUNqeT2P1DL5mp0vKBk9nTXyKcOU3vEfaElE0mt7XP+l620Cer7o6c8Lbib1YQ0tx3XdYY21SNULTvM4W3IdLyANRlZ4w5q/Sk/Yqhva40nr5M9+mACT7Jkhj/S0YpJNNd2s1o8QFusRFCvlrFEkHw0yrGjqFaQmubmcv54zHTPn0TdnrucKGGxsbHj+OmsFbrMZxsPx6ZGZYw5pigmpuw4JPQRhurCDQupSXNIUkSV4Cs6SHXRz5960SCfLNqtWsaDk0BSpQ0bVG5VFXMCqJSSqmcAcipRaoC2UV9wy5pD2hbqZ6EHVSiVQJZxSuOeQufq4TjHTkx6PcVUR8Xu4lJnODnVnkZ7/R03TBrkndG6STLlTP4F4oh72rL0lZ7I2+M/ulGvcS6V8L5oldD2xRvK4pvGBnuux3D2HehGOsRArhLYtrKXA6HlPB6ECDcrCJfgoHIOPwyfgs3AGvgArcBFeAkh18Njb9FolU+nTGn10cYdOUYz0yKyZNZXmEmGzmBfvJ0ILxbbeT+b8v48PnQk6hFvx7aBCAGVT9Ld3rJm3tZm5/Q/dhK1pdKbQFR1hLwRgzgy8bUuKMWx+U1qleA9QZk5RLa/DYYouoFmrCJ9MAfia2UnHCuZrlRrBqEJoylRsChrqD5M1W5fMWkqkcEVSWue04ab/oCXGYa2Nl+lunBxtjU5iy221cHU9ylwGCX3JvRw0UfHY5Sap+vfYVRr9ZpSwYofY2Gq9fx/viExepwdyHF+95r/H4hsCNxjm0jpzIEeLObQJ8GSmqM1UmCOSaehV4YfkHLn37E/fnTr0m6987sfN85zLsoziqjRXcW35Myj97ofPPIf4+NELXNdkltSwmWJyxAObcpytQw0WCX1PhDeZtij6PSMlq8oTmKFLDXX7yMkIXMSiUa4vkWVFXmxE04ZhRgbXTAn4oHZuhSsyH4qJkctDseAqT/LA4wMY5yvP9w74Ej3BSmrAf+tPSpJffvJB4GEr8C78mSeVy6f+da2ttJetErZxNqbFz/FhvjU0GX8nnIyd7h3aTKZzh5+oMv780P3l904uV5i8Gn/33qev2LUXjg91Yv37KNapKMpRxIndyFMzLO1Vgk/G2x3qYGMPoabsh82/Sr+SmnCAMl7gUVTDjODNBap7VqdGdyolKtZBomKTrIjMSStki1IM650d1j6qesk2VZ62qDydCU4mJwdfL32peeNNWX7zBvUl+3WNBeVLtyTp1qWwx7eT2/aIif+fGEv8pTAV7aH+6Mqjbynjf+juoV6EaCjkmbeldljFRP2qwDwch0/BM3AOvgaXyKu8qOzprICnqNNqvkb0t1uGPfVbFd4LduA97jU6vynqfTWL/YTMC1xRBjDsAxfdnWvfm24gNqajnnwf13X2790yP7k3PeY+rDxU8HoXbHJLd7rxolAe153dIr+9W+buFvXiZ4gifzDEj8BltU5h3SteNkG8b4S9IyWMxl7QcYDsuxliQwoZbjc2XoRvEaN9F74H1+EH8Ar8BG7Da/Bz+AXchV/Db8mjPryT6sdPW14m0BY/EcRvA95PiHvX3MGTfCeP7ljzHfroCLR1kRahbtv69m70SH1Q1g95ffVe7vLuWpchpS2y9O90efUDuRJ7Ibq0G6Id0fventQeXt8lc+wS/BbRQ+e3d8g5VfgwfARO0nsPiaZqmd2xSzhUMgTRpLMTuJ1v7LqFXC1OYJd2KqpdqVUtm2B+vy0oJql1huA612RBNAl+YZWrceV8h2Zc4gacpQoe48F3cPZBsPH1Lt9sSMfa1661S/bjn0fnAyhGi2OHYp5mnM5M0r9t2CrdXz7VoZrm3bvx4y/Upq/C/wByZ6fCAAAAeJxjYGRgYADi3iOB+vH8Nl8ZuFkYQODaA8/rCPp/AwsvcwKQy8HABBIFAD1+CysAeJxjYGRgYG7438AQw8LGwPD/MwsvA1AEBQgCAHJ9BH94nGNhYGBgfsnAwMIAxWxIbCIxAEQdATMAAAAAAHYA9gEWAV4BogIsAuQDSAN6A/4EMASSBVAFoAa0BzB4nGNgZGBgEGTYwyDDAAJMQMwFhAwM/8F8BgAe9QIAAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nG2LXQ6CMBAG+/FbUES8xx6qlmKI2CVlmyint8FX520mGZWpH636z4AMOQqUqFBDo0GLE87ocEGPKwbcFN65jdIZK9EstM27I64srx9iPbrFSfJ8mqXakljRq9nEBeIiuJG1ME2BvRTRj9zszC+aPXEd/SNwXNujcBTi8gh1Gu7GPpX6AjVyKhcAAA==') format('woff'),
|
||||
url('//at.alicdn.com/t/font_598462_3xve1872wizzolxr.ttf?t=1522149591264') format('truetype'),
|
||||
url('//at.alicdn.com/t/font_598462_3xve1872wizzolxr.svg?t=1522149591264#iconfont') format('svg');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "bi-icon";
|
||||
src: url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.eot');
|
||||
src: url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.eot?#iefix') format('embedded-opentype'),
|
||||
url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.woff') format('woff'),
|
||||
url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.ttf') format('truetype'),
|
||||
url('//at.alicdn.com/t/font_538964_lt8h7c2h3hfo5hfr.svg#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family:"iconfont" !important;
|
||||
font-size:16px;
|
||||
font-style:normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.bi-icon {
|
||||
font-family:"bi-icon" !important;
|
||||
font-size:16px;
|
||||
font-style:normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-save:before {
|
||||
content: "\e669";
|
||||
}
|
||||
|
||||
.icon-undo:before {
|
||||
content: "\e8ab";
|
||||
}
|
||||
|
||||
.icon-redo:before {
|
||||
content: "\e8a9";
|
||||
}
|
||||
|
||||
.icon-cut:before {
|
||||
content: "\e8a2";
|
||||
}
|
||||
|
||||
.icon-copy-o:before {
|
||||
content: "\e8a4";
|
||||
}
|
||||
|
||||
.icon-paster-o:before {
|
||||
content: "\e8a8";
|
||||
}
|
||||
|
||||
.icon-delete-o:before {
|
||||
content: "\e8a5";
|
||||
}
|
||||
|
||||
.icon-zoom-in-o:before {
|
||||
content: "\e8ac";
|
||||
}
|
||||
|
||||
.icon-zoom-out-o:before {
|
||||
content: "\e8ae";
|
||||
}
|
||||
|
||||
.icon-fit:before {
|
||||
content: "\e8a6";
|
||||
}
|
||||
|
||||
.icon-actual-size-o:before {
|
||||
content: "\e8a3";
|
||||
}
|
||||
|
||||
.icon-to-back:before {
|
||||
content: "\e8b0";
|
||||
}
|
||||
|
||||
.icon-to-front:before {
|
||||
content: "\e8aa";
|
||||
}
|
||||
|
||||
.icon-select:before {
|
||||
content: "\e8a7";
|
||||
}
|
||||
|
||||
.icon-group:before {
|
||||
content: "\e8af";
|
||||
}
|
||||
|
||||
.icon-ungroup:before {
|
||||
content: "\e8ad";
|
||||
}
|
||||
|
||||
.icon-insert-sibling:before {
|
||||
content: "\e8af";
|
||||
}
|
||||
|
||||
.icon-insert-child:before {
|
||||
content: "\e8ae";
|
||||
}
|
||||
|
||||
.icon-collapse-subtree:before {
|
||||
content: "\e8b3";
|
||||
}
|
||||
|
||||
.icon-expand-subtree:before {
|
||||
content: "\e8b4";
|
||||
}
|
22
frontend/src/umind/umind-detail-panel/index.js
Normal file
22
frontend/src/umind/umind-detail-panel/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Card } from 'antd';
|
||||
import { NodePanel, CanvasPanel, DetailPanel } from 'gg-editor';
|
||||
import NodeDetail from './node-detail';
|
||||
|
||||
class UMindDetails extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<DetailPanel className="detail-panel">
|
||||
<NodePanel className="node-detail">
|
||||
<NodeDetail />
|
||||
</NodePanel>
|
||||
<CanvasPanel className="node-detail">
|
||||
<Card type="inner" title="画布属性" bordered={true} />
|
||||
</CanvasPanel>
|
||||
</DetailPanel>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UMindDetails;
|
74
frontend/src/umind/umind-detail-panel/node-detail.js
Normal file
74
frontend/src/umind/umind-detail-panel/node-detail.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { Card, Form, Input } from 'antd';
|
||||
import { withPropsAPI } from 'gg-editor';
|
||||
|
||||
const { Item } = Form;
|
||||
|
||||
const inlineFormItemLayout = {
|
||||
labelCol: {
|
||||
sm: { span: 6 },
|
||||
},
|
||||
wrapperCol: {
|
||||
sm: { span: 18 },
|
||||
},
|
||||
};
|
||||
|
||||
class NodeDetail extends React.Component {
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { form, propsAPI } = this.props;
|
||||
const { getSelected, executeCommand, update } = propsAPI;
|
||||
|
||||
form.validateFieldsAndScroll((err, values) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = getSelected()[0];
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
executeCommand(() => {
|
||||
update(item, {
|
||||
...values,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { form, propsAPI } = this.props;
|
||||
const { getFieldDecorator } = form;
|
||||
const { getSelected } = propsAPI;
|
||||
|
||||
const item = getSelected()[0];
|
||||
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { label } = item.getModel();
|
||||
|
||||
return (
|
||||
<Card type="inner" title="节点属性" bordered={false}>
|
||||
<Form onSubmit={this.handleSubmit}>
|
||||
<Item
|
||||
label="标签"
|
||||
{...inlineFormItemLayout}
|
||||
>
|
||||
{
|
||||
getFieldDecorator('label', {
|
||||
initialValue: label,
|
||||
})(<Input onBlur={this.handleSubmit} />)
|
||||
}
|
||||
</Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Form.create()(withPropsAPI(NodeDetail));
|
33
frontend/src/umind/umind-editor/context-menu.css
Normal file
33
frontend/src/umind/umind-editor/context-menu.css
Normal file
@@ -0,0 +1,33 @@
|
||||
.context-menu {
|
||||
display: none;
|
||||
background: #FFFFFF;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.context-menu .item {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 12px;
|
||||
transition: all .3s;
|
||||
}
|
||||
|
||||
.context-menu .item:hover {
|
||||
background: #E6F7FF;
|
||||
}
|
||||
|
||||
.context-menu .item i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.context-menu .disable .item {
|
||||
cursor: auto;
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.context-menu .disable .item:hover {
|
||||
background: #FFFFFF;
|
||||
}
|
61
frontend/src/umind/umind-editor/umind-context-menu.js
Normal file
61
frontend/src/umind/umind-editor/umind-context-menu.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { Command, NodeMenu, CanvasMenu, ContextMenu } from 'gg-editor';
|
||||
|
||||
import './context-menu.css';
|
||||
|
||||
class UMindContextMenu extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu className="context-menu">
|
||||
<NodeMenu>
|
||||
<Command name="append">
|
||||
<div className="item">
|
||||
<i className="bi-icon icon-insert-sibling" />
|
||||
<span>插入同级</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="appendChild">
|
||||
<div className="item">
|
||||
<i className="bi-icon icon-insert-child" />
|
||||
<span>插入子级</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="collapse">
|
||||
<div className="item">
|
||||
<i className="bi-icon icon-collapse-subtree" />
|
||||
<span>折叠</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="expand">
|
||||
<div className="item">
|
||||
<i className="bi-icon icon-expand-subtree" />
|
||||
<span>展开</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="delete">
|
||||
<div className="item">
|
||||
<i className="iconfont icon-delete-o" />
|
||||
<span>删除</span>
|
||||
</div>
|
||||
</Command>
|
||||
</NodeMenu>
|
||||
<CanvasMenu>
|
||||
<Command name="undo">
|
||||
<div className="item">
|
||||
<i className="iconfont icon-undo" />
|
||||
<span>撤销</span>
|
||||
</div>
|
||||
</Command>
|
||||
<Command name="redo">
|
||||
<div className="item">
|
||||
<i className="iconfont icon-redo" />
|
||||
<span>重做</span>
|
||||
</div>
|
||||
</Command>
|
||||
</CanvasMenu>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UMindContextMenu;
|
15
frontend/src/umind/umind-editor/umind-editor-minimap.js
Normal file
15
frontend/src/umind/umind-editor/umind-editor-minimap.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Card } from 'antd';
|
||||
import { Minimap } from 'gg-editor';
|
||||
|
||||
class UMindEditorMinimap extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Card type="inner" title="缩略图" bordered={false}>
|
||||
<Minimap height={200} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UMindEditorMinimap;
|
38
frontend/src/umind/umind-toolbar/toolbar.css
Normal file
38
frontend/src/umind/umind-toolbar/toolbar.css
Normal file
@@ -0,0 +1,38 @@
|
||||
.common-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.common-toolbar .command i {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin: 0 6px;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
text-align: center;
|
||||
border: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.common-toolbar .command i:hover {
|
||||
border: 1px solid #E6E9ED;
|
||||
}
|
||||
|
||||
.common-toolbar .disable i {
|
||||
cursor: auto;
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.common-toolbar .disable i:hover {
|
||||
border: 1px solid #FFFFFF;
|
||||
}
|
||||
|
||||
.tooltip .ant-tooltip-inner {
|
||||
font-size: 12px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.common-toolbar .anticon.custom-save {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
102
frontend/src/umind/umind-toolbar/umind-toolbar.js
Normal file
102
frontend/src/umind/umind-toolbar/umind-toolbar.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Tooltip, Divider, Icon } from 'antd';
|
||||
import { Toolbar, Command } from 'gg-editor';
|
||||
import withGGEditorContext from 'gg-editor/es/common/context/GGEditorContext/withGGEditorContext';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
|
||||
import './toolbar.css';
|
||||
|
||||
const { repoID, filePath, fileName } = window.app.pageOptions;
|
||||
class UMindToolbar extends React.Component {
|
||||
|
||||
onSaveClick = (e) => {
|
||||
e.preventDefault();
|
||||
let { editor } = this.props;
|
||||
let page = editor.getCurrentPage();
|
||||
let { data, defaultData } = page._cfg;
|
||||
let dirPath = Utils.getDirName(filePath);
|
||||
seafileAPI.getUpdateLink(repoID, dirPath).then(res => {
|
||||
let updateLink = res.data;
|
||||
// need optimized
|
||||
let updateData = data ? JSON.stringify(data) : JSON.stringify(defaultData);
|
||||
seafileAPI.updateFile(updateLink, filePath, fileName, updateData).then(res => {
|
||||
toaster.success(gettext('File saved.'));
|
||||
}).catch(() => {
|
||||
toaster.success(gettext('File save failed.'));
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="custom-toolbar" onClick={this.onSaveClick}>
|
||||
<Tooltip title="保存" placement="bottom" overlayClassName="tooltip">
|
||||
<Icon type="save" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Toolbar className="common-toolbar">
|
||||
<Divider type="vertical" />
|
||||
<Command name="undo">
|
||||
<Tooltip title="撤销" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-undo" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="redo">
|
||||
<Tooltip title="重做" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-redo" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Divider type="vertical" />
|
||||
<Command name="zoomIn">
|
||||
<Tooltip title="放大" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-zoom-in-o" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="zoomOut">
|
||||
<Tooltip title="缩小" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-zoom-out-o" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="autoZoom">
|
||||
<Tooltip title="适应画布" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-fit" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="resetZoom">
|
||||
<Tooltip title="实际尺寸" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="iconfont icon-actual-size-o" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Divider type="vertical" />
|
||||
<Command name="append">
|
||||
<Tooltip title="插入同级" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="bi-icon icon-insert-sibling" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="appendChild">
|
||||
<Tooltip title="插入子级" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="bi-icon icon-insert-child" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Divider type="vertical" />
|
||||
<Command name="collapse">
|
||||
<Tooltip title="折叠" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="bi-icon icon-collapse-subtree" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
<Command name="expand">
|
||||
<Tooltip title="展开" placement="bottom" overlayClassName="tooltip">
|
||||
<i className="bi-icon icon-expand-subtree" />
|
||||
</Tooltip>
|
||||
</Command>
|
||||
</Toolbar>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withGGEditorContext(UMindToolbar);
|
@@ -1,90 +1,17 @@
|
||||
import { seafileAPI } from './utils/seafile-api';
|
||||
import { Utils } from './utils/utils';
|
||||
import { gettext, lang } from './utils/constants';
|
||||
import toaster from './components/toast';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import UMind from './umind';
|
||||
|
||||
const { UMind, React, ReactDOM } = window;
|
||||
const { repoID, filePath, fileName, username } = window.app.pageOptions;
|
||||
|
||||
const DEFAULT_DATA = {
|
||||
roots: [{
|
||||
label: '中心主题',
|
||||
children: [{
|
||||
label: '分支主题 1',
|
||||
}, {
|
||||
label: '分支主题 2',
|
||||
}, {
|
||||
label: '分支主题 3',
|
||||
}],
|
||||
}],
|
||||
};
|
||||
|
||||
class ViewFileUmind extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: DEFAULT_DATA,
|
||||
isDataLoading: true
|
||||
};
|
||||
this.locale = 'zh-CN';
|
||||
this.config = {
|
||||
file: {
|
||||
id: repoID + filePath,
|
||||
},
|
||||
user: {
|
||||
id: username,
|
||||
},
|
||||
socket: {
|
||||
url: 'https://umind.alibaba-inc.com',
|
||||
events: {
|
||||
user: 'user',
|
||||
operation: 'operation',
|
||||
},
|
||||
onUserListChange: () => {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.getFileDownloadLink(repoID, filePath).then(res => {
|
||||
let url = res.data;
|
||||
seafileAPI.getFileContent(url).then(res => {
|
||||
if (res.data) {
|
||||
this.setState({
|
||||
isDataLoading: false,
|
||||
data: res.data
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
isDataLoading: false,
|
||||
data: DEFAULT_DATA
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
// toaster.success(gettext('file loading error'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
handleSave = (data) => {
|
||||
let dirPath = Utils.getDirName(filePath);
|
||||
seafileAPI.getUpdateLink(repoID, dirPath).then(res => {
|
||||
let updateLink = res.data;
|
||||
let updateData = JSON.stringify(data);
|
||||
seafileAPI.updateFile(updateLink, filePath, fileName, updateData).then(res => {
|
||||
// toaster.success(gettext('File saved.'));
|
||||
}).catch(() => {
|
||||
// toaster.success(gettext('File saved failed.'));
|
||||
});
|
||||
});
|
||||
}
|
||||
class ViewFileUMind extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<UMind.default locale={this.locale} config={this.config} data={this.state.data} save={this.handleSave} />
|
||||
<UMind />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<ViewFileUmind />, document.getElementById('root'));
|
||||
ReactDOM.render(
|
||||
<ViewFileUMind />,
|
||||
document.getElementById('wrapper')
|
||||
);
|
Reference in New Issue
Block a user