Skip to content

Commit 32301c8

Browse files
committed
Initial public version
0 parents  commit 32301c8

12 files changed

+14375
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
dist/

.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v16.7.0

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Getting started
2+
3+
CollabUML is a PlantUML based app for online collaborative design on UML
4+
5+
6+
## How it works?
7+
8+
CollabUML is a super-simple client only app that wires existing technology, in this case, it uses an https://etherpad.org to handle the collaborative text editor and https://plantuml.com/ public server to render the editor's content.
9+
10+
There is a timer that takes the etherpad content to render it with plantuml on every tick.
11+
12+
13+
## Run it
14+
15+
An Etherpad instance is required, host/apikey must be configured at [config.js](./src/config.js)
16+
17+
Then, run `npm install` to download the js dependencies, then, `npm run start` will allow you to use the app at `localhost:8080`

package-lock.json

+14,053
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "collabuml-web",
3+
"version": "1.0.0",
4+
"description": "",
5+
"private": true,
6+
"scripts": {
7+
"build": "webpack --mode=development",
8+
"watch": "webpack --watch",
9+
"start": "webpack-dev-server --history-api-fallback",
10+
"dist": "webpack --mode=production",
11+
"test": "echo \"Error: no test specified\" && exit 1"
12+
},
13+
"author": "",
14+
"license": "ISC",
15+
"dependencies": {
16+
"bootswatch": "^4.4.1",
17+
"jquery": "^3.5.0",
18+
"lodash": "^4.17.15",
19+
"pako": "^1.0.11",
20+
"plantuml-encoder": "^1.4.0"
21+
},
22+
"devDependencies": {
23+
"clean-webpack-plugin": "^3.0.0",
24+
"css-loader": "^3.5.3",
25+
"file-loader": "^6.0.0",
26+
"html-webpack-plugin": "^4.2.0",
27+
"style-loader": "^1.2.0",
28+
"webpack": "^4.43.0",
29+
"webpack-cli": "^3.3.11",
30+
"webpack-dev-server": "^3.10.3"
31+
}
32+
}

src/config.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
function promptForSessionName() {
2+
var input = prompt('Choose the session name, your peers need to use this to collaborate with you, or ignore this to go to the test session') || '';
3+
input = input.trim();
4+
if (input.length == 0) {
5+
return 'test';
6+
} else {
7+
return input;
8+
}
9+
}
10+
11+
function getSessionName() {
12+
var currentPath = window.location.pathname;
13+
if (currentPath.startsWith('/')) {
14+
var session = currentPath.substr(1).trim();
15+
if (session !== '') {
16+
return session;
17+
}
18+
}
19+
20+
var session = promptForSessionName();
21+
var newPath = '/' + session;
22+
window.location.pathname = newPath;
23+
}
24+
25+
function getUserName() {
26+
return 'unnamed';
27+
}
28+
29+
export const collabuml = {
30+
padId: getSessionName(),
31+
userName: getUserName(),
32+
host: 'https://pad.collabuml.com',
33+
apikey: 'd73bfbe04a31195082be48dfd9dc22cb17bcedeedea0be2404be6cbe290440e6'
34+
};

src/etherpad-editor.js

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import jQuery from 'jquery';
2+
import { collabuml } from './config';
3+
4+
export const getText = (callback) => {
5+
jQuery.ajax({
6+
'url': collabuml.host + '/api/1/getText?apikey=' + collabuml.apikey + '&padID=' + collabuml.padId + '&jsonp=?',
7+
'type': 'GET',
8+
'dataType': "jsonp",
9+
'success': function (response) {
10+
callback(response.data.text);
11+
}
12+
});
13+
};
14+
15+
export const renderPad = (selector, padId) => {
16+
jQuery(selector).pad({ 'padId': padId, 'showChat': 'false' });
17+
};
18+
19+
(function (jQuery) {
20+
jQuery.fn.pad = function (options) {
21+
var settings = {
22+
'host': collabuml.host,
23+
'baseUrl': '/p/',
24+
'showControls': false,
25+
'showChat': false,
26+
'showLineNumbers': true,
27+
'userName': collabuml.userName,
28+
'lang': '',
29+
'useMonospaceFont': false,
30+
'noColors': false,
31+
'userColor': false,
32+
'hideQRCode': false,
33+
'alwaysShowChat': false,
34+
'width': 100,
35+
'height': 300,
36+
'border': 1,
37+
'borderStyle': 'solid',
38+
'toggleTextOn': 'Disable Rich-text',
39+
'toggleTextOff': 'Enable Rich-text',
40+
'plugins': {},
41+
'rtl': false
42+
};
43+
var $self = this;
44+
if (!$self.length) return;
45+
if (!$self.attr('id')) throw new Error('No "id" attribute');
46+
47+
var useValue = $self[0].tagName.toLowerCase() == 'textarea';
48+
var selfId = $self.attr('id');
49+
var epframeId = 'epframe' + selfId;
50+
// This writes a new frame if required
51+
if (options) {
52+
jQuery.extend(settings, options);
53+
}
54+
55+
var pluginParams = '';
56+
for (var option in settings.plugins) {
57+
pluginParams += '&' + option + '=' + settings.plugins[option]
58+
}
59+
60+
var iFrameLink = '<iframe id="' + epframeId;
61+
iFrameLink = iFrameLink + '" name="' + epframeId;
62+
iFrameLink = iFrameLink + '" src="' + settings.host + settings.baseUrl + settings.padId;
63+
iFrameLink = iFrameLink + '?showControls=' + settings.showControls;
64+
iFrameLink = iFrameLink + '&showChat=' + settings.showChat;
65+
iFrameLink = iFrameLink + '&showLineNumbers=' + settings.showLineNumbers;
66+
iFrameLink = iFrameLink + '&useMonospaceFont=' + settings.useMonospaceFont;
67+
iFrameLink = iFrameLink + '&userName=' + settings.userName;
68+
if (settings.lang) {
69+
iFrameLink = iFrameLink + '&lang=' + settings.lang;
70+
}
71+
iFrameLink = iFrameLink + '&noColors=' + settings.noColors;
72+
iFrameLink = iFrameLink + '&userColor=' + settings.userColor;
73+
iFrameLink = iFrameLink + '&hideQRCode=' + settings.hideQRCode;
74+
iFrameLink = iFrameLink + '&alwaysShowChat=' + settings.alwaysShowChat;
75+
iFrameLink = iFrameLink + '&rtl=' + settings.rtl;
76+
iFrameLink = iFrameLink + pluginParams;
77+
iFrameLink = iFrameLink + '" style="border:' + settings.border;
78+
iFrameLink = iFrameLink + '; border-style:' + settings.borderStyle;
79+
iFrameLink = iFrameLink + ';" width="' + '100%';//settings.width;
80+
iFrameLink = iFrameLink + '" height="' + settings.height;
81+
iFrameLink = iFrameLink + '"></iframe>';
82+
83+
84+
var $iFrameLink = jQuery(iFrameLink);
85+
86+
if (useValue) {
87+
var $toggleLink = jQuery('<a href="#' + selfId + '">' + settings.toggleTextOn + '</a>').click(function () {
88+
var $this = jQuery(this);
89+
$this.toggleClass('active');
90+
if ($this.hasClass('active')) $this.text(settings.toggleTextOff);
91+
$self.pad({ getContents: true });
92+
return false;
93+
});
94+
$self
95+
.hide()
96+
.after($toggleLink)
97+
.after($iFrameLink)
98+
;
99+
}
100+
else {
101+
$self.html(iFrameLink);
102+
}
103+
104+
return $self;
105+
};
106+
})(jQuery);
107+

src/index.html

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head lang="en">
5+
<meta charset="utf-8">
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<title>CollabUML | Collaborative UML powered by PlantUML</title>
9+
<meta property="og:title" content="CollabUML" />
10+
<meta property="og:locale" content="en_US" />
11+
<meta name="description" content="A collaborative UML editor powered by PlantUML" />
12+
<meta property="og:description" content="A collaborative UML editor powered by PlantUML" />
13+
<link rel="canonical" href="https://collabuml.com/" />
14+
<meta property="og:url" content="https://collabuml.com/" />
15+
<meta property="og:site_name" content="CollabUML" />
16+
<script type="application/ld+json">
17+
{"@type":"WebSite","headline":"CollabUML","url":"https://collabuml.com","name":"CollabUML","description":"A collaborative UML editor powered by PlantUML","@context":"https://schema.org"}
18+
</script>
19+
<!-- Global site tag (gtag.js) - Google Analytics -->
20+
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-65008315-6"></script>
21+
<script>
22+
window.dataLayer = window.dataLayer || [];
23+
function gtag() { dataLayer.push(arguments); }
24+
gtag('js', new Date());
25+
26+
gtag('config', 'UA-65008315-6');
27+
</script>
28+
</head>
29+
30+
<body>
31+
<div class="container">
32+
<h1>CollabUML</h1>
33+
<h6>Be sure to check the official &nbsp;<a href="https://plantuml.com/">PlantUML docs</a>&nbsp; to check what
34+
you can
35+
do, and choose a unique session name to avoid unwanted collaborators
36+
</h6>
37+
<h4>Just share the URL with the people you want to collaborate with.</h4>
38+
<h4 id="session-name"></h4>
39+
<div class="row">
40+
<div id="etherpad-input" class="col-xs-6 col-md-6"></div>
41+
<div id="diagram-view" class="col-xs-6 col-md-6">
42+
</div>
43+
</div>
44+
<hr>
45+
<h6>Be aware that anyone can go to any board, hence, <strong>avoid placing private information</strong>.</h6>
46+
<hr>
47+
<p>If you liked this app, you can support me by purchasing/sharing <a href="https://codepreview.io">codepreview.io</a></p>
48+
</body>
49+
50+
</html>

src/index.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import _ from 'lodash';
2+
import './style.css';
3+
import '../node_modules/bootswatch/dist/flatly/bootstrap.min.css';
4+
import jQuery from 'jquery';
5+
import { collabuml } from './config';
6+
import { getText, renderPad } from './etherpad-editor';
7+
import { scheduledRendering } from './plantuml-rendered';
8+
9+
jQuery(function () {
10+
renderPad('#etherpad-input', collabuml.padId);
11+
jQuery('#session-name').append('Session: ' + collabuml.padId);
12+
scheduledRendering(2000, getText);
13+
});

src/plantuml-rendered.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import jQuery from 'jquery';
2+
3+
const plantumlEncoder = require('plantuml-encoder')
4+
5+
function renderDiagram(ms, getText) {
6+
scheduledRendering(ms, getText);
7+
getText(function (text) {
8+
var encoded = plantumlEncoder.encode(text);
9+
var div = jQuery('#diagram-view')
10+
var sourceUrl = "https://www.plantuml.com/plantuml/img/" + encoded;
11+
var item = '<img src="' + sourceUrl + '">'
12+
div.html('');
13+
div.append(item);
14+
});
15+
}
16+
17+
var scheduled = null;
18+
export const scheduledRendering = (ms, getText) => {
19+
if (scheduled) {
20+
clearTimeout(scheduled);
21+
}
22+
scheduled = setTimeout(() => renderDiagram(ms, getText), ms);
23+
};

src/style.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.hello {
2+
color: red;
3+
}

webpack.config.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const path = require('path');
2+
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
3+
const HtmlWebpackPlugin = require('html-webpack-plugin');
4+
5+
module.exports = {
6+
entry: './src/index.js',
7+
mode: 'development',
8+
devtool: 'inline-source-map',
9+
devServer: {
10+
contentBase: './dist',
11+
historyApiFallback: true,
12+
},
13+
output: {
14+
filename: 'bundle.js',
15+
path: path.resolve(__dirname, 'dist'),
16+
},
17+
plugins: [
18+
new CleanWebpackPlugin(),
19+
new HtmlWebpackPlugin({
20+
template: 'src/index.html',
21+
}),
22+
],
23+
module: {
24+
rules: [
25+
{
26+
test: /\.css$/,
27+
use: [
28+
'style-loader',
29+
'css-loader',
30+
],
31+
},
32+
{
33+
test: /\.(png|svg|jpg|gif)$/,
34+
use: [
35+
'file-loader',
36+
],
37+
},
38+
],
39+
},
40+
};

0 commit comments

Comments
 (0)