diff --git a/.gitignore b/.gitignore index 2d70b03c..e77c88ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -server/public/bundle.js +server/public/js server/public/index.html node_modules/ babel_cache/ diff --git a/client/components/editor.js b/client/components/editor.js index c73ded0f..39c969ec 100644 --- a/client/components/editor.js +++ b/client/components/editor.js @@ -6,7 +6,6 @@ import CodeMirror from 'codemirror/lib/codemirror'; // keybinding import 'codemirror/keymap/emacs.js'; - // search import 'codemirror/addon/search/searchcursor.js'; import 'codemirror/addon/search/search.js'; diff --git a/client/components/index.js b/client/components/index.js index de6d7238..3db3a3ef 100644 --- a/client/components/index.js +++ b/client/components/index.js @@ -1,4 +1,5 @@ export { BreadCrumb } from './breadcrumb'; -export { Editor } from './editor'; export { FileSystem } from './filesystem'; export { Connect } from './connect'; +// Those are commented because they will delivered as a separate chunk +// export { Editor } from './editor'; diff --git a/client/pages/index.js b/client/pages/index.js index aa98aa55..6fd94453 100644 --- a/client/pages/index.js +++ b/client/pages/index.js @@ -1,6 +1,7 @@ export { HomePage } from './homepage'; export { ConnectPage } from './connectpage'; export { LogoutPage } from './logout'; -export { FilesPage } from './filespage'; -export { ViewerPage } from './viewerpage'; export { NotFoundPage } from './notfound'; +// Those are commented because they will delivered as a separate chunk +// export { FilesPage } from './filespage'; +// export { ViewerPage } from './viewerpage'; diff --git a/client/pages/viewerpage.js b/client/pages/viewerpage.js index e18d1a3e..cbb766db 100644 --- a/client/pages/viewerpage.js +++ b/client/pages/viewerpage.js @@ -1,16 +1,26 @@ import React from 'react'; -import { BreadCrumb, Editor } from '../components/'; -import { debounce, throttle, NgIf, Loader, Error, Fab, Icon, Container } from '../utilities/'; +import { BreadCrumb } from '../components/'; +import { Bundle, debounce, NgIf, Loader, Error, Container } from '../utilities/'; import { Files, opener, EventReceiver, EventEmitter } from '../data/'; +import { AudioPlayer, FileDownloader, ImageViewer, PDFViewer } from './viewerpage/'; import Path from 'path'; -import { theme } from '../utilities'; -import WaveSurfer from 'wavesurfer.js'; -import videojs from 'video.js'; + + +const VideoPlayer = (props) => ( + + {(Comp) => } + +); +const IDE = (props) => ( + + {(Comp) => } + +); @EventReceiver export class ViewerPage extends React.Component { - constructor(props){ + constructor(props){ super(props); this.state = { path: props.match.url.replace('/view', ''), @@ -83,6 +93,7 @@ export class ViewerPage extends React.Component { Files.url(this.state.path).then((url) => { this.setState({data: url, loading: false, opener: app}); }).catch(err => { + console.log("ERROR", err) if(err && err.code === 'CANCELLED'){ return } this.setState({error: err}); }); @@ -139,285 +150,3 @@ export class ViewerPage extends React.Component { } } - -class IDE extends React.Component { - constructor(props){ - super(props); - this.state = { - contentToSave: null - } - } - - onContentUpdate(text){ - this.props.needSaving(true); - this.setState({contentToSave: text}) - } - - save(){ - let file, blob = new window.Blob([this.state.contentToSave], {type : 'text/plain'}); - try{ - file = new window.File([blob], 'test.txt'); - }catch(err){ - // for crappy browser: - // https://stackoverflow.com/questions/33821631/alternative-for-file-constructor-for-safari - file = blob; - } - this.props.onSave(file); - } - - render(){ - return ( -
- - - - - - - -
- ); - } -} - - -const ImageViewer = (props) => { - return ( -
- -
- -
-
- ) -} - - -const PDFViewer = (props) => { - return ( -
- -
- ); -} - -class AudioPlayer extends React.Component { - constructor(props){ - super(props); - this.state = { - wafesurfer: null, - loading: true, - isPlaying: true, - error: null - } - this.toggle = this.toggle.bind(this); - window.addEventListener('keypress', this.toggle); - } - - componentDidMount(){ - let $el = document.querySelector('.player'); - var wavesurfer = WaveSurfer.create({ - container: '#waveform', - waveColor: '#323639', - progressColor: '#6f6f6f', - cursorColor: '#323639', - cursorWidth: 2, - height: 250, - barWidth: 1 - }); - this.setState({wavesurfer: wavesurfer}); - wavesurfer.on('ready', () => { - this.setState({loading: false}); - if(this.state.isPlaying === true){ - wavesurfer.play(); - } - }); - wavesurfer.on('error', (err) => { - this.setState({error: err, loading: false}); - }); - wavesurfer.load(this.props.data); - } - - componentWillUnmount(){ - if(this.state.wavesurfer){ - this.state.wavesurfer.destroy(); - } - window.removeEventListener('keypress', this.toggle); - } - - toggle(){ - if(this.state.isPlaying === true){ - this.setState({isPlaying: false}); - this.state.wavesurfer.pause(); - }else{ - this.setState({isPlaying: true}); - this.state.wavesurfer.play(); - } - } - - onPlay(e){ - e.preventDefault(); - e.stopPropagation(); - this.setState({isPlaying: true}) - this.state.wavesurfer.play(); - } - - onPause(e){ - e.preventDefault(); - e.stopPropagation(); - this.setState({isPlaying: false}); - this.state.wavesurfer.pause(); - } - - render(){ - return ( -
- -
- - {this.state.error} - - - - - -
-
- - - - - - -
-
-
-
-
-
- ) - } -} - -class VideoPlayer extends React.Component { - constructor(props){ - super(props); - } - - componentDidMount(){ - this.player = videojs(this.videoNode, { - //autoplay: true, - controls: true, - aspectRatio: '16:9', - fluid: false, - sources: [{ - src: this.props.data - }] - }, function onPlayerReady() { - //console.log('onPlayerReady', this) - }); - } - - componentWillUnmount() { - if (this.player) { - this.player.dispose() - } - } - - render(){ - return ( -
- -
-
- -
-
-
- ) - } -} - -class MenuBar extends React.Component{ - constructor(props){ - super(props); - this.state = {loading: false, id: null} - } - - onDownloadRequest(){ - this.setState({ - loading: true, - id: window.setInterval(function(){ - if(document.cookie){ - this.setState({loading: false}) - window.clearInterval(this.state.id); - } - }.bind(this), 80) - }) - } - - componentWillUnmount(){ - window.clearInterval(this.state.id) - } - - - render(){ - return ( -
- - - - - - - - - - - - {this.props.title} - -
- ); - } -} - -class FileDownloader extends React.Component{ - constructor(props){ - super(props) - this.state = {loading: false, id: null}; - } - - onClick(){ - this.setState({ - loading: true, - id: window.setInterval(function(){ - if(document.cookie){ - this.setState({loading: false}) - window.clearInterval(this.state.id); - } - }.bind(this), 80) - }); - } - - componentWillUnmount(){ - window.clearInterval(this.state.id) - } - - render(){ - return ( -
-
- - - DOWNLOAD - - - - - -
-
- ); - } -} diff --git a/client/pages/viewerpage/audioplayer.js b/client/pages/viewerpage/audioplayer.js new file mode 100644 index 00000000..3d39fb14 --- /dev/null +++ b/client/pages/viewerpage/audioplayer.js @@ -0,0 +1,102 @@ +import React from 'react'; +import { MenuBar } from './menubar'; +import { NgIf, Icon, theme } from '../../utilities/' +import WaveSurfer from 'wavesurfer.js'; + +export class AudioPlayer extends React.Component { + constructor(props){ + super(props); + this.state = { + wafesurfer: null, + loading: true, + isPlaying: true, + error: null + } + this.toggle = this.toggle.bind(this); + window.addEventListener('keypress', this.toggle); + } + + componentDidMount(){ + let $el = document.querySelector('.player'); + var wavesurfer = WaveSurfer.create({ + container: '#waveform', + waveColor: '#323639', + progressColor: '#6f6f6f', + cursorColor: '#323639', + cursorWidth: 2, + height: 250, + barWidth: 1 + }); + this.setState({wavesurfer: wavesurfer}); + wavesurfer.on('ready', () => { + this.setState({loading: false}); + if(this.state.isPlaying === true){ + wavesurfer.play(); + } + }); + wavesurfer.on('error', (err) => { + this.setState({error: err, loading: false}); + }); + wavesurfer.load(this.props.data); + } + + componentWillUnmount(){ + if(this.state.wavesurfer){ + this.state.wavesurfer.destroy(); + } + window.removeEventListener('keypress', this.toggle); + } + + toggle(){ + if(this.state.isPlaying === true){ + this.setState({isPlaying: false}); + this.state.wavesurfer.pause(); + }else{ + this.setState({isPlaying: true}); + this.state.wavesurfer.play(); + } + } + + onPlay(e){ + e.preventDefault(); + e.stopPropagation(); + this.setState({isPlaying: true}) + this.state.wavesurfer.play(); + } + + onPause(e){ + e.preventDefault(); + e.stopPropagation(); + this.setState({isPlaying: false}); + this.state.wavesurfer.pause(); + } + + render(){ + return ( +
+ +
+ + {this.state.error} + + + + + +
+
+ + + + + + +
+
+
+
+
+
+ ) + } +} diff --git a/client/pages/viewerpage/filedownloader.js b/client/pages/viewerpage/filedownloader.js new file mode 100644 index 00000000..54beeeb0 --- /dev/null +++ b/client/pages/viewerpage/filedownloader.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { MenuBar } from './menubar'; +import { theme, NgIf, Icon } from '../../utilities/' + +export class FileDownloader extends React.Component{ + constructor(props){ + super(props) + this.state = {loading: false, id: null}; + } + + onClick(){ + this.setState({ + loading: true, + id: window.setInterval(function(){ + if(document.cookie){ + this.setState({loading: false}) + window.clearInterval(this.state.id); + } + }.bind(this), 80) + }); + } + + componentWillUnmount(){ + window.clearInterval(this.state.id) + } + + render(){ + return ( +
+
+ + + DOWNLOAD + + + + + +
+
+ ); + } +} diff --git a/client/pages/viewerpage/ide.js b/client/pages/viewerpage/ide.js new file mode 100644 index 00000000..0fa56000 --- /dev/null +++ b/client/pages/viewerpage/ide.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { Editor } from '../../components/editor'; +import { NgIf, Fab, Icon } from '../../utilities/' +import { MenuBar } from './menubar'; + + +export class IDE extends React.Component { + constructor(props){ + super(props); + this.state = { + contentToSave: null + } + } + + onContentUpdate(text){ + this.props.needSaving(true); + this.setState({contentToSave: text}) + } + + save(){ + let file, blob = new window.Blob([this.state.contentToSave], {type : 'text/plain'}); + try{ + file = new window.File([blob], 'test.txt'); + }catch(err){ + // for crappy browser: + // https://stackoverflow.com/questions/33821631/alternative-for-file-constructor-for-safari + file = blob; + } + this.props.onSave(file); + } + + render(){ + return ( +
+ + + + + + + +
+ ); + } +} diff --git a/client/pages/viewerpage/imageviewer.js b/client/pages/viewerpage/imageviewer.js new file mode 100644 index 00000000..ef48dcae --- /dev/null +++ b/client/pages/viewerpage/imageviewer.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { theme } from '../../utilities/'; +import { MenuBar } from './menubar'; + +export const ImageViewer = (props) => { + return ( +
+ +
+ +
+
+ ) +} diff --git a/client/pages/viewerpage/index.js b/client/pages/viewerpage/index.js index ff067500..e811af77 100644 --- a/client/pages/viewerpage/index.js +++ b/client/pages/viewerpage/index.js @@ -1,4 +1,7 @@ -module.exports = { - test: 'test' -} - +export { AudioPlayer } from './audioplayer'; +export { FileDownloader } from './filedownloader'; +export { ImageViewer } from './imageviewer'; +export { PDFViewer } from './pdfviewer'; +// Those are commented because they will delivered as a separate chunk +// export { VideoPlayer } from './videoplayer'; +// export { IDE } from './ide'; diff --git a/client/pages/viewerpage/menubar.js b/client/pages/viewerpage/menubar.js new file mode 100644 index 00000000..b83145fa --- /dev/null +++ b/client/pages/viewerpage/menubar.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { theme, Container, NgIf, Icon } from '../../utilities/'; + +export class MenuBar extends React.Component{ + constructor(props){ + super(props); + this.state = {loading: false, id: null} + } + + onDownloadRequest(){ + this.setState({ + loading: true, + id: window.setInterval(function(){ + if(document.cookie){ + this.setState({loading: false}) + window.clearInterval(this.state.id); + } + }.bind(this), 80) + }) + } + + componentWillUnmount(){ + window.clearInterval(this.state.id) + } + + + render(){ + return ( +
+ + + + + + + + + + + + {this.props.title} + +
+ ); + } +} diff --git a/client/pages/viewerpage/pdfviewer.js b/client/pages/viewerpage/pdfviewer.js new file mode 100644 index 00000000..8d1540a4 --- /dev/null +++ b/client/pages/viewerpage/pdfviewer.js @@ -0,0 +1,9 @@ +import React from 'react'; + +export const PDFViewer = (props) => { + return ( +
+ +
+ ); +} diff --git a/client/pages/viewerpage/videoplayer.js b/client/pages/viewerpage/videoplayer.js new file mode 100644 index 00000000..fe86eb00 --- /dev/null +++ b/client/pages/viewerpage/videoplayer.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { MenuBar } from './menubar'; +import { theme } from '../../utilities/'; +import videojs from 'video.js'; + +export class VideoPlayer extends React.Component { + constructor(props){ + super(props); + } + + componentDidMount(){ + this.player = videojs(this.videoNode, { + //autoplay: true, + controls: true, + aspectRatio: '16:9', + fluid: false, + sources: [{ + src: this.props.data + }] + }, function onPlayerReady() { + //console.log('onPlayerReady', this) + }); + } + + componentWillUnmount() { + if (this.player) { + this.player.dispose() + } + } + + render(){ + return ( +
+ +
+
+ +
+
+
+ ) + } +} diff --git a/client/router.js b/client/router.js index a705cb53..c606608d 100644 --- a/client/router.js +++ b/client/router.js @@ -1,24 +1,34 @@ import React from 'react' import { BrowserRouter, Route, IndexRoute } from 'react-router-dom' -import { NotFoundPage, ConnectPage, HomePage, FilesPage, ViewerPage, LogoutPage } from './pages/'; -import { URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT } from './utilities/'; +import { NotFoundPage, ConnectPage, HomePage, LogoutPage } from './pages/'; +import { Bundle, URL_HOME, URL_FILES, URL_VIEWER, URL_LOGIN, URL_LOGOUT } from './utilities/'; import { createBrowserHistory } from 'history' +const FilesPage = (props) => ( + + {(Comp) => } + +); +const ViewerPage = (props) => ( + + {(Comp) => } + +); export default class AppRouter extends React.Component { - render() { - return ( - -
- - - - - -
-
- ); - } + render() { + return ( + +
+ + + + + +
+
+ ); + } } diff --git a/client/utilities/bundle.js b/client/utilities/bundle.js new file mode 100644 index 00000000..90c45476 --- /dev/null +++ b/client/utilities/bundle.js @@ -0,0 +1,42 @@ +// taken from https://reacttraining.com/react-router/web/guides/code-splitting +import React from 'react'; +import Path from 'path'; + +export class Bundle extends React.Component { + state = { mod: null }; + + componentWillMount() { + this.load(this.props) + } + + componentWillReceiveProps(nextProps) { + if(nextProps.load !== this.props.load){ this.load(nextProps) } + } + + load(props) { + this.setState({ + mod: null + }); + props.loader + .then((_mod) => { + this.setState({ + mod: function(mod){ + if(mod['default']){ + return mod.default; + }else if(mod['__esModule'] === true){ + return mod[props.symbol] ? mod[props.symbol] : null; + }else{ + return mod; + } + }(_mod) + }) + }) + .catch((err) => { + console.warn(err) + }) + } + + render() { + return this.state.mod ? this.props.children(this.state.mod) : null + } +} diff --git a/client/utilities/index.js b/client/utilities/index.js index 5b7b1ae4..b34cde5d 100644 --- a/client/utilities/index.js +++ b/client/utilities/index.js @@ -16,3 +16,4 @@ export { Uploader } from './uploader'; export { theme, to_rgba } from './theme'; export { encrypt, decrypt } from './crypto' export { pathBuilder } from './path'; +export { Bundle } from './bundle'; diff --git a/docker/img/Dockerfile b/docker/img/Dockerfile index 61950330..10a582a3 100644 --- a/docker/img/Dockerfile +++ b/docker/img/Dockerfile @@ -6,13 +6,14 @@ WORKDIR "/app" RUN cd /app/ && \ # remove dev stuff - npm run clean && \ + npm run clear && \ # build apt-get update -y && \ apt install -y libssl-dev build-essential automake python libcurl3-dev && \ npm install nodegit --build-from-source && \ npm install && \ npm run build && \ + # prepare for production npm prune --production && \ apt-get -y clean && \ apt-get -y purge --auto-remove build-essential automake python && \ diff --git a/package.json b/package.json index 980bde2c..d43a9319 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "publish": "docker tag nuage machines/nuage && docker push machines/nuage", "start": "cd docker && docker-compose up -d", "stop": "cd docker && docker-compose down", - "clean": "rm -rf node_modules server/public/bundle.js server/public/index.html || true" + "clean": "rm -rf server/public/js server/public/*.html || true", + "clear": "npm run clean && rm -rf node_modules" }, "author": "", "license": "ISC", @@ -72,6 +73,7 @@ "html-webpack-plugin": "^2.28.0", "http-server": "^0.9.0", "nodemon": "^1.11.0", + "webpack-bundle-analyzer": "^2.8.2", "webpack-dev-server": "^2.4.5" } } diff --git a/webpack.config.js b/webpack.config.js index d9156ff3..7f95242e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,22 +1,25 @@ const webpack = require('webpack'); const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; let config = { - entry: [ - 'babel-polyfill', - path.join(__dirname, 'client', 'index.js') - ], + entry: { + polyfill: 'babel-polyfill', + app: path.join(__dirname, 'client', 'index.js') + }, output: { path: path.join(__dirname, 'server', 'public'), publicPath: '/', - filename: 'bundle.js' + filename: 'js/[name].js', + chunkFilename: "js/chunk.[name].[id].js" }, module: { loaders: [ { test: path.join(__dirname, 'client'), - loader: ['babel-loader'] + loader: ['babel-loader'], + exclude: /node_modules/ }, { test: /\.html$/, @@ -32,7 +35,8 @@ let config = { new HtmlWebpackPlugin({ template: path.join(__dirname, 'client', 'index.html'), inject:true - }) + }), + new webpack.optimize.CommonsChunkPlugin({name: "polyfill", filename: "js/polyfill.js"}), ] }; @@ -59,5 +63,6 @@ if(process.env.NODE_ENV === 'production'){ } +//config.plugins.push(new BundleAnalyzerPlugin()) module.exports = config;