Skip to content
This repository was archived by the owner on Apr 18, 2022. It is now read-only.

Commit 17f6296

Browse files
author
Jason
committed
First commit.
0 parents  commit 17f6296

File tree

111 files changed

+7430
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+7430
-0
lines changed

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
.cache
3+
.vscode
4+
Thumbs.db
5+
node_modules
6+
dist
7+
package-lock.json
8+
test

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 XMind Ltd.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# XMind Viewer
2+
3+
XMind Viewer, is a lightweight library to parse a `.xmind` file and render it in `SVG`.
4+
5+
It's very useful for showing a mind map on any web pages. Like a blog post, a book introduction, a page navigation, etc. All versions of XMind files are compatiable, which means you can generate an XMind file with `XMind SDK` and then render it with `XMind Viewer`. Cool?
6+
7+
XMind Viewer is an official project, made by XMind team, and written in TypeScript.
8+
9+
10+
## Usage and Getting Started
11+
12+
### Simple Usage
13+
14+
```ts
15+
import JSZip from 'jszip'
16+
import { loadFromXMind, SnowbrushRenderer } from 'xmind-viewer'
17+
18+
new JSZip().loadAsync(zipFile).then(zip => {
19+
loadFromXMind(zip).then(data => {
20+
const renderer = new SnowbrushRenderer(data.sheets)
21+
return renderer.render({ sheetIndex: 0 })
22+
}).then(svg => {
23+
// document.body.appendChild(svg)
24+
})
25+
})
26+
```
27+
28+
### loadFromXMind()
29+
30+
`loadFromXMind()` can be used to convert `.xmind` files into `sheets` that can be read by Snowbrush Render
31+
32+
### Snowbrush Renderer
33+
34+
Snowbrush Renderer accepts an array of sheets as a constructor argument, and SVG will be generated based on the content of the sheet after using `render()`.
35+
36+
> P.S. Snowbrush is an internal code name for render engine.
37+
38+
#### Methods
39+
40+
##### .render(options: RenderOptions) => Svg
41+
42+
SVG will be generated based on the content of the sheet after using `render()`.
43+
You can use options to determine which Sheet you want to display.
44+
45+
###### RenderOptions
46+
47+
`sheetIndex`: Select which sheet you want to render.
48+
49+
##### .svg => Svg
50+
51+
Get the generated SVG, it must be called after `render()`.
52+
53+
##### .transform(x, y)
54+
55+
Move the center point of the mindmap to the location (x, y).
56+
57+
## Notice
58+
59+
In addition to these exposed API, the rest of the code can change at any time.
60+
61+
## License
62+
See the [MIT License](LICENSE).

example/content.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"id":"859ab2acfc6af8480a310977c7","class":"sheet","title":"Map 1","rootTopic":{"id":"b9aa22deba98b3b20c7ac8aca2","class":"topic","title":"Central Topic","structureClass":"org.xmind.ui.map.unbalanced","titleUnedited":true,"children":{"attached":[{"id":"b58888b5ceebbf0e68dada0656","title":"Main Topic 1","titleUnedited":true},{"id":"193b56735e689ae86a01d91513","title":"Main Topic 2","titleUnedited":true},{"id":"67ddbcb1-85c9-4478-a0aa-580e9fdcd971","title":"Main Topic 3","titleUnedited":true}]},"extensions":[{"content":[{"content":"3","name":"right-number"}],"provider":"org.xmind.ui.map.unbalanced"}]},"theme":{"id":"6518e97a4149b5f96691ab3b5d","importantTopic":{"type":"topic","properties":{"fo:font-weight":"bold","fo:color":"#333333","svg:fill":"#FFFF00"}},"minorTopic":{"type":"topic","properties":{"fo:font-weight":"bold","fo:color":"#333333","svg:fill":"#FFCB88"}},"expiredTopic":{"type":"topic","properties":{"fo:font-style":"italic","fo:text-decoration":" line-through"}},"centralTopic":{"properties":{"fo:font-family":"NeverMind","fo:font-weight":"600","fo:font-style":"normal","fo:font-size":28,"shape-class":"org.xmind.topicShape.roundedRect","svg:fill":"#0288D1","line-class":"org.xmind.branchConnection.curve","line-width":"2","line-color":"#333333","border-line-width":"0"},"styleId":"9a94e1a0-7e67-48df-a231-7fa0c60b7b97","type":"topic"},"boundary":{"properties":{"fo:font-family":"NeverMind","fo:color":"#FFFFFF","fo:font-weight":"500","svg:fill":"#D5E9FC","shape-class":"org.xmind.boundaryShape.roundedRect","line-pattern":"dash","line-color":"#0288D1","fo:font-style":"normal","fo:font-size":13},"styleId":"94a6c549-690c-4f1e-b18f-457f114abcb0","type":"boundary"},"floatingTopic":{"properties":{"fo:font-family":"NeverMind","fo:font-weight":"600","fo:font-size":13,"shape-class":"org.xmind.topicShape.roundedRect","svg:fill":"#333333","fo:font-style":"normal","fo:color":"#FFFFFF","border-line-width":"0","border-line-color":"none","line-class":"org.xmind.branchConnection.roundedElbow","line-width":"1","line-color":"#333333"},"styleId":"8286b472-0630-4970-8bf1-510a831dc2b9","type":"topic"},"subTopic":{"properties":{"fo:font-weight":"500","fo:text-align":"left","fo:font-family":"NeverMind","line-class":"org.xmind.branchConnection.roundedElbow","fo:font-style":"normal","fo:font-size":13,"fo:color":"#333333","svg:fill":"none","shape-class":"org.xmind.topicShape.underline"},"styleId":"ddd92ae3-f2b4-48ee-9e47-2dacee6acac4","type":"topic"},"mainTopic":{"properties":{"fo:font-weight":"600","fo:font-family":"NeverMind","fo:font-style":"normal","fo:font-size":20,"line-class":"org.xmind.branchConnection.roundedElbow","line-width":"1","line-color":"#333333","border-line-width":"2","border-line-color":"#333333"},"styleId":"7565fe37-2200-4483-b343-91bdf53e563e","type":"topic"},"calloutTopic":{"properties":{"fo:font-family":"NeverMind","fo:font-weight":"600","fo:font-style":"normal","callout-shape-class":"org.xmind.calloutTopicShape.balloon.roundedRect","fo:font-size":13,"fo:color":"#FFFFFF"},"styleId":"9bffcc6a-0526-4db5-b304-ea3830a42846","type":"topic"},"summaryTopic":{"properties":{"fo:font-family":"NeverMind","fo:font-weight":"600","fo:font-size":13,"shape-class":"org.xmind.topicShape.roundedRect","svg:fill":"#333333","border-line-width":"1","border-line-color":"none","fo:font-style":"normal","line-class":"org.xmind.branchConnection.roundedElbow","line-width":"1","line-color":"#333333"},"styleId":"a82dc72d-29bc-4d81-bd44-3f1bee0ed35c","type":"topic"},"relationship":{"properties":{"fo:font-family":"NeverMind","fo:font-weight":"normal","line-color":"#0288D1","shape-class":"org.xmind.relationshipShape.curved","line-width":"2","fo:font-size":13,"fo:color":"#333333"},"styleId":"1611e291-8cc1-4500-93a5-69281d5bedf0","type":"relationship"},"map":{"type":"map","styleId":"7e90467a-d643-4fa9-84f2-f8f0166e5afb","properties":{}},"summary":{"type":"summary","styleId":"8aa88864-8667-4627-a763-a84973e0109b","properties":{"line-color":"#0288D1","shape-class":"org.xmind.summaryShape.round","line-width":"2"}}},"topicPositioning":"fixed"}]

example/index.html

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<html>
2+
3+
<head>
4+
<meta charset="UTF-8">
5+
<link rel="stylesheet" type="text/css" href="/ui/css/body.css">
6+
7+
<title>XMind Viewer v1.0.0</title>
8+
<style type="text/css">
9+
html,
10+
body {
11+
width: 100%;
12+
height: 100%;
13+
margin: 0;
14+
}
15+
16+
#page-content {
17+
position: absolute;
18+
width: 100%;
19+
height: 100%;
20+
top: 0;
21+
left: 0;
22+
right: 0;
23+
bottom: 0;
24+
margin: auto;
25+
margin-top: 40px;
26+
overflow: scroll;
27+
}
28+
</style>
29+
</head>
30+
31+
<body>
32+
<header>
33+
<div id="logo">
34+
<img width="32px" height="32px" src="/ui/assets/icon.png">
35+
</div>
36+
<button type="button" id="open-file">Open File</button>
37+
<input id="input-dialog" type="file" style="display: none" />
38+
</header>
39+
<div id="page-content"></div>
40+
41+
<script src="./index.ts"></script>
42+
</body>
43+
44+
</html>

example/index.ts

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import contentData from './content.json'
2+
import JSZip from 'jszip'
3+
import { SheetData } from 'model/sheet'
4+
import { loadFromXMind, SnowbrushRenderer } from '../src/index'
5+
6+
load(contentData)
7+
8+
document.getElementById('input-dialog').addEventListener('input', function() {
9+
const inputEle = this as HTMLInputElement
10+
if (inputEle.files.length === 0) {
11+
return
12+
}
13+
14+
const file = inputEle.files[0]
15+
const fileName = inputEle.value
16+
const reader = new FileReader()
17+
reader.onload = e => {
18+
const jszip = new JSZip()
19+
return Promise.all([
20+
Promise.resolve(fileName),
21+
jszip.loadAsync(e.target.result).then(zip => {
22+
loadFromXMind(zip).then((result: any) => {
23+
load(result.sheets)
24+
})
25+
})
26+
])
27+
}
28+
29+
reader.readAsArrayBuffer(file)
30+
})
31+
32+
document.getElementById('open-file').addEventListener('click', function(){
33+
const input = document.getElementById('input-dialog')
34+
input.click()
35+
})
36+
37+
function load(data: SheetData[]) {
38+
const container = document.getElementById('page-content')
39+
if (container.children.length > 0) {
40+
container.innerHTML = ''
41+
}
42+
43+
const renderer = new SnowbrushRenderer(data)
44+
renderer.render()
45+
const rendererBounds = renderer.bounds
46+
47+
const clientWidth = container.clientWidth
48+
const clientHeight = container.clientHeight
49+
const width = Math.max(clientWidth, rendererBounds.width)
50+
const height = Math.max(clientHeight, rendererBounds.height)
51+
52+
const rendererContainer = document.createElement('div')
53+
rendererContainer.setAttribute('style', `width: ${width * 2}; height: ${height * 2}; position: relative;`)
54+
rendererContainer.className = 'sheet-container'
55+
renderer.svg.addTo(rendererContainer)
56+
rendererContainer.style.backgroundColor = renderer.svg.node.style.backgroundColor
57+
container.append(rendererContainer)
58+
59+
renderer.transform(width + rendererBounds.x, height + rendererBounds.y)
60+
container.scrollTo(width - clientWidth / 2, height - clientHeight / 2)
61+
}

example/ui/assets/icon.png

52.2 KB
Loading

example/ui/css/body.css

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
header {
2+
display: flex;
3+
height: 40px;
4+
background-color: #2e2e2e;
5+
padding: 0 16px;
6+
}
7+
8+
#logo {
9+
display: flex;
10+
height: 100%;
11+
align-items: center;
12+
}
13+
14+
button {
15+
border: none;
16+
background: none;
17+
color: #ffffff;
18+
font-size: 12px;
19+
font-weight: 600;
20+
margin: 4px 6px;
21+
border-radius: 4px;
22+
}
23+
24+
button:hover {
25+
background-color: rgba(223,223,223,0.2);
26+
}
27+
28+
button:focus {
29+
outline:0;
30+
}

package.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "xmind-viewer",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": {
10+
"name": "XMind Ltd.",
11+
"email": "[email protected]",
12+
"url": "https://www.xmind.net"
13+
},
14+
"license": "MIT",
15+
"devDependencies": {
16+
"typescript": "^3.7.3"
17+
},
18+
"dependencies": {
19+
"@svgdotjs/svg.js": "^3.0.16",
20+
"@types/jszip": "^3.1.6",
21+
"jszip": "^3.2.2"
22+
},
23+
"alias": {
24+
"common": "./src/common",
25+
"model": "./src/model",
26+
"utils": "./src/utils",
27+
"view": "./src/view",
28+
"viewController": "./src/viewController",
29+
"structure": "./src/structure"
30+
}
31+
}

src/common/constants/extensions.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export enum ExtensionProvider {
2+
UNBALANCED_MAP = 'org.xmind.ui.map.unbalanced',
3+
}
4+
5+
export enum ExtensionContentName {
6+
RIGHT_NUMBER = 'right-number',
7+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const BOUNDARY_GAP = 10
2+
3+
export const LINE_CONTROL_OFFSET = 13
4+
5+
export const PADDING = 20
6+
7+
export const TOPIC_SHAPE_STACK_GAP = 5
8+
9+
export const ROTATED_COS = Math.cos(Math.PI / 9)
10+
export const ROTATED_SIN = Math.sin(Math.PI / 9)
11+
export const ROTATED_TAN = Math.tan(Math.PI / 9)
12+
13+
export const COLLAPSE_GAP = 13

src/common/constants/models.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
enum TopicType {
2+
ATTACHED = 'attached',
3+
DETACHED = 'detached',
4+
}
5+
6+
enum Direction {
7+
UP = 'up',
8+
DOWN = 'down',
9+
LEFT = 'left',
10+
RIGHT = 'right',
11+
NONE = 'none'
12+
}
13+
14+
export {
15+
TopicType,
16+
Direction,
17+
}

src/common/constants/structures.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
enum StructureClass {
2+
MAP = 'org.xmind.ui.map',
3+
MAP_UNBALANCED = 'org.xmind.ui.map.unbalanced',
4+
MAP_CLOCK_WISE = 'org.xmind.ui.map.clockwise',
5+
LOGIC_LEFT = 'org.xmind.ui.logic.left',
6+
LOGIC_RIGHT = 'org.xmind.ui.logic.right',
7+
ORG_CHART_UP = 'org.xmind.ui.org-chart.up',
8+
ORG_CHART_DOWN = 'org.xmind.ui.org-chart.down',
9+
TREE_RIGHT = 'org.xmind.ui.tree.right',
10+
TREE_LEFT = 'org.xmind.ui.tree.left',
11+
TIMELINE_VERTICAL = 'org.xmind.ui.timeline.vertical',
12+
TIMELINE_HORIZONTAL = 'org.xmind.ui.timeline.horizontal',
13+
TIMELINE_HORIZONTAL_UP = 'org.xmind.ui.timeline.horizontal.up',
14+
TIMELINE_HORIZONTAL_DOWN = 'org.xmind.ui.timeline.horizontal.down',
15+
FISHBONE_LEFT_HEADED = 'org.xmind.ui.fishbone.leftHeaded',
16+
FISHBONE_RIGHT_HEADED = 'org.xmind.ui.fishbone.rightHeaded',
17+
FISHBONE_NE_NORMAL = 'org.xmind.ui.fishbone.structure.NE.normal',
18+
FISHBONE_SE_NORMAL = 'org.xmind.ui.fishbone.structure.SE.normal',
19+
FISHBONE_NW_NORMAL = 'org.xmind.ui.fishbone.structure.NW.normal',
20+
FISHBONE_SW_NORMAL = 'org.xmind.ui.fishbone.structure.SW.normal',
21+
}
22+
23+
export default StructureClass
24+
25+
export const EXPOSED_STRUCTURES = [
26+
StructureClass.MAP,
27+
StructureClass.MAP_UNBALANCED,
28+
StructureClass.LOGIC_LEFT,
29+
StructureClass.LOGIC_RIGHT,
30+
StructureClass.ORG_CHART_UP,
31+
StructureClass.ORG_CHART_DOWN,
32+
StructureClass.TREE_LEFT,
33+
StructureClass.TREE_RIGHT,
34+
StructureClass.TIMELINE_VERTICAL,
35+
StructureClass.TIMELINE_HORIZONTAL,
36+
StructureClass.FISHBONE_LEFT_HEADED,
37+
StructureClass.FISHBONE_RIGHT_HEADED,
38+
]
39+
40+
export const EXPOSED_ATTACHED_STRUCTURES = EXPOSED_STRUCTURES.filter(item => {
41+
return item.indexOf('org.xmind.ui.map') !== 0
42+
})
43+
44+
export const EXPOSED_ATTACHED_RIGHT_STRUCTURES = EXPOSED_ATTACHED_STRUCTURES.filter(item => {
45+
return item.indexOf(StructureClass.LOGIC_LEFT) !== 0
46+
})
47+
48+
export const EXPOSED_ATTACHED_LEFT_STRUCTURES = EXPOSED_ATTACHED_STRUCTURES.filter(item => {
49+
return item.indexOf(StructureClass.LOGIC_RIGHT) !== 0
50+
})
51+
52+
export const FISHBONE_NORMAL_STRUCTURES = [
53+
StructureClass.FISHBONE_NE_NORMAL,
54+
StructureClass.FISHBONE_SE_NORMAL,
55+
StructureClass.FISHBONE_NW_NORMAL,
56+
StructureClass.FISHBONE_SW_NORMAL
57+
]

0 commit comments

Comments
 (0)