├── dist ├── images │ ├── dock_fill.png │ ├── dock_left.png │ ├── dock_top.png │ ├── dock_bottom.png │ ├── dock_right.png │ ├── dock_top_sel.png │ ├── dock_fill_sel.png │ ├── dock_left_sel.png │ ├── dock_right_sel.png │ └── dock_bottom_sel.png ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── proximanova-webfont.eot │ └── proximanova-webfont.ttf ├── demos │ └── ide │ │ ├── infovis │ │ ├── jit-base.css │ │ ├── dock_tree_vis.html │ │ ├── Spacetree.css │ │ └── dock_tree_vis.js │ │ ├── demo.css │ │ └── demo.html └── css │ ├── dock-manager.css │ └── font-awesome.css ├── dockspawn.sublime-project ├── src ├── utils │ ├── Point.js │ ├── Rectangle.js │ ├── EventHandler.js │ ├── utils.js │ └── UndockInitiator.js ├── dock │ ├── DockModel.js │ ├── DockManagerContext.js │ ├── DockWheelItem.js │ ├── DockNode.js │ ├── DockWheel.js │ ├── DockLayoutEngine.js │ └── DockManager.js ├── containers │ ├── VerticalDockContainer.js │ ├── HorizontalDockContainer.js │ ├── DocumentTabPage.js │ ├── DocumentManagerContainer.js │ ├── SplitterDockContainer.js │ ├── FillDockContainer.js │ └── PanelContainer.js ├── serialization │ ├── DockGraphSerializer.js │ └── DockGraphDeserializer.js ├── tab │ ├── TabPage.js │ ├── TabHandle.js │ └── TabHost.js ├── index.js ├── dialog │ └── Dialog.js ├── splitter │ ├── SplitterBar.js │ └── SplitterPanel.js └── decorators │ ├── DraggableContainer.js │ └── ResizableContainer.js ├── AUTHORS ├── README.md ├── .gitignore ├── .editorconfig ├── package.json ├── MIT-LICENSE.txt ├── gulpfile.js └── .jshintrc /dist/images/dock_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_fill.png -------------------------------------------------------------------------------- /dist/images/dock_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_left.png -------------------------------------------------------------------------------- /dist/images/dock_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_top.png -------------------------------------------------------------------------------- /dist/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /dist/images/dock_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_bottom.png -------------------------------------------------------------------------------- /dist/images/dock_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_right.png -------------------------------------------------------------------------------- /dist/images/dock_top_sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_top_sel.png -------------------------------------------------------------------------------- /dist/images/dock_fill_sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_fill_sel.png -------------------------------------------------------------------------------- /dist/images/dock_left_sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_left_sel.png -------------------------------------------------------------------------------- /dist/images/dock_right_sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_right_sel.png -------------------------------------------------------------------------------- /dist/images/dock_bottom_sel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/images/dock_bottom_sel.png -------------------------------------------------------------------------------- /dist/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /dist/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /dist/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /dist/fonts/proximanova-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/fonts/proximanova-webfont.eot -------------------------------------------------------------------------------- /dist/fonts/proximanova-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/englercj/dock-spawn/master/dist/fonts/proximanova-webfont.ttf -------------------------------------------------------------------------------- /dockspawn.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "E:/Projects/DockSpawn/js" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/Point.js: -------------------------------------------------------------------------------- 1 | function Point(x, y) { 2 | this.x = x; 3 | this.y = y; 4 | } 5 | 6 | module.exports = Point; 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Dock Spawn Authors 2 | 3 | Ali Akbar 4 | Marius Volkhart 5 | Fil Mackay 6 | -------------------------------------------------------------------------------- /dist/demos/ide/infovis/jit-base.css: -------------------------------------------------------------------------------- 1 | #infovis { 2 | position:relative; 3 | width:500px; 4 | height:500px; 5 | overflow:hidden; 6 | background-color:#1a1a1a; 7 | color:#ccc; 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/Rectangle.js: -------------------------------------------------------------------------------- 1 | function Rectangle(x, y, width, height) { 2 | this.x = x || 0; 3 | this.y = y || 0; 4 | this.width = width || 0; 5 | this.height = height || 0; 6 | } 7 | 8 | module.exports = Rectangle; 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dock Spawn 2 | 3 | Panel docking library that provides panel docking similar to how Visual Studio handles dockable panels. 4 | 5 | This project is originally forked from: https://github.com/northerneyes/dock-spawn. 6 | 7 | You can find everything you need in the `dist/` folder. 8 | -------------------------------------------------------------------------------- /src/dock/DockModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The Dock Model contains the tree hierarchy that represents the state of the 3 | * panel placement within the dock manager. 4 | */ 5 | function DockModel() 6 | { 7 | this.rootNode = this.documentManagerNode = undefined; 8 | } 9 | 10 | module.exports = DockModel; 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # sublime text 2 files 2 | *.sublime* 3 | *.*~*.TMP 4 | 5 | # temp files 6 | .DS_Store 7 | Thumbs.db 8 | Desktop.ini 9 | npm-debug.log 10 | 11 | # vim swap files 12 | *.sw* 13 | 14 | # emacs temp files 15 | *~ 16 | \#*# 17 | 18 | # project ignores 19 | !.gitkeep 20 | *__temp 21 | node_modules 22 | build 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Unix-style newlines with a newline ending every file 2 | [*] 3 | end_of_line = lf 4 | insert_final_newline = true 5 | trim_trailing_whitespace = true 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [*.md] 10 | trim_trailing_whitespace = false 11 | 12 | [{package.json,.travis.yml}] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /src/dock/DockManagerContext.js: -------------------------------------------------------------------------------- 1 | var DockModel = require('./DockModel'), 2 | DocumentManagerContainer = require('../containers/DocumentManagerContainer'); 3 | 4 | function DockManagerContext(dockManager) 5 | { 6 | this.dockManager = dockManager; 7 | this.model = new DockModel(); 8 | this.documentManagerView = new DocumentManagerContainer(this.dockManager); 9 | } 10 | 11 | module.exports = DockManagerContext; 12 | -------------------------------------------------------------------------------- /src/utils/EventHandler.js: -------------------------------------------------------------------------------- 1 | function EventHandler(source, eventName, target) { 2 | // wrap the target 3 | this.target = target; 4 | this.eventName = eventName; 5 | this.source = source; 6 | 7 | this.source.addEventListener(eventName, this.target); 8 | } 9 | 10 | module.exports = EventHandler; 11 | 12 | EventHandler.prototype.cancel = function() { 13 | this.source.removeEventListener(this.eventName, this.target); 14 | }; 15 | -------------------------------------------------------------------------------- /src/containers/VerticalDockContainer.js: -------------------------------------------------------------------------------- 1 | var SplitterDockContainer = require('./SplitterDockContainer'), 2 | utils = require('../utils/utils'); 3 | 4 | function VerticalDockContainer(dockManager, childContainers) 5 | { 6 | this.stackedVertical = true; 7 | SplitterDockContainer.call(this, utils.getNextId('vertical_splitter_'), dockManager, childContainers); 8 | this.containerType = 'vertical'; 9 | } 10 | 11 | VerticalDockContainer.prototype = Object.create(SplitterDockContainer.prototype); 12 | VerticalDockContainer.prototype.constructor = VerticalDockContainer; 13 | module.exports = VerticalDockContainer; 14 | -------------------------------------------------------------------------------- /src/containers/HorizontalDockContainer.js: -------------------------------------------------------------------------------- 1 | var SplitterDockContainer = require('./SplitterDockContainer'), 2 | utils = require('../utils/utils'); 3 | 4 | function HorizontalDockContainer(dockManager, childContainers) 5 | { 6 | this.stackedVertical = false; 7 | SplitterDockContainer.call(this, utils.getNextId('horizontal_splitter_'), dockManager, childContainers); 8 | this.containerType = 'horizontal'; 9 | } 10 | 11 | HorizontalDockContainer.prototype = Object.create(SplitterDockContainer.prototype); 12 | HorizontalDockContainer.prototype.constructor = HorizontalDockContainer; 13 | module.exports = HorizontalDockContainer; 14 | -------------------------------------------------------------------------------- /dist/demos/ide/infovis/dock_tree_vis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spacetree - Tree Animation 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dock-spawn", 3 | "version": "1.0.0", 4 | "description": "Panel docking library similar to Visual Studio docking.", 5 | "author": "Chad Engler ", 6 | "homepage": "https://github.com/englercj/dock-spawn", 7 | "main": "./src/index", 8 | "scripts": { 9 | "build": "gulp build" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/englercj/dock-spawn.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/englercj/dock-spawn/issues" 17 | }, 18 | "dependencies": { 19 | }, 20 | "devDependencies": { 21 | "browserify": "^6.0.3", 22 | "gulp": "^3.8.8", 23 | "gulp-jshint": "^1.8.5", 24 | "gulp-util": "^3.0.1", 25 | "jshint-summary": "^0.4.0", 26 | "vinyl-source-stream": "^1.0.0", 27 | "watchify": "^2.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 CodeRespawn.com and other contributors 2 | 3 | Permission is hereby granted, free 4 | of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, modify, 7 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and 8 | to permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions of the Software. 13 | 14 | THE 15 | SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | var counter = 0; 2 | 3 | module.exports = { 4 | getPixels: function (pixels) { 5 | if (pixels === null) { 6 | return 0; 7 | } 8 | 9 | return parseInt(pixels.replace('px', '')); 10 | }, 11 | 12 | disableGlobalTextSelection: function () { 13 | document.body.classList.add('disable-selection'); 14 | }, 15 | 16 | enableGlobalTextSelection: function () { 17 | document.body.classList.remove('disable-selection'); 18 | }, 19 | 20 | isPointInsideNode: function (px, py, node) { 21 | var element = node.container.containerElement; 22 | 23 | return ( 24 | px >= element.offsetLeft && 25 | px <= element.offsetLeft + element.clientWidth && 26 | py >= element.offsetTop && 27 | py <= element.offsetTop + element.clientHeight 28 | ); 29 | }, 30 | 31 | getNextId: function (prefix) { 32 | return prefix + counter++; 33 | }, 34 | 35 | removeNode: function (node) { 36 | if (node.parentNode === null) { 37 | return false; 38 | } 39 | 40 | node.parentNode.removeChild(node); 41 | 42 | return true; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/dock/DockWheelItem.js: -------------------------------------------------------------------------------- 1 | var EventHandler = require('../utils/EventHandler'); 2 | 3 | function DockWheelItem(wheel, id) 4 | { 5 | this.wheel = wheel; 6 | this.id = id; 7 | var wheelType = id.replace('-s', ''); 8 | this.element = document.createElement('div'); 9 | this.element.classList.add('dock-wheel-item'); 10 | this.element.classList.add('disable-selection'); 11 | this.element.classList.add('dock-wheel-' + wheelType); 12 | this.element.classList.add('dock-wheel-' + wheelType + '-icon'); 13 | this.hoverIconClass = 'dock-wheel-' + wheelType + '-icon-hover'; 14 | this.mouseOverHandler = new EventHandler(this.element, 'mouseover', this.onMouseMoved.bind(this)); 15 | this.mouseOutHandler = new EventHandler(this.element, 'mouseout', this.onMouseOut.bind(this)); 16 | this.active = false; // Becomes active when the mouse is hovered over it 17 | } 18 | 19 | module.exports = DockWheelItem; 20 | 21 | DockWheelItem.prototype.onMouseMoved = function(e) 22 | { 23 | this.active = true; 24 | this.element.classList.add(this.hoverIconClass); 25 | this.wheel.onMouseOver(this, e); 26 | }; 27 | 28 | DockWheelItem.prototype.onMouseOut = function(e) 29 | { 30 | this.active = false; 31 | this.element.classList.remove(this.hoverIconClass); 32 | this.wheel.onMouseOut(this, e); 33 | }; 34 | -------------------------------------------------------------------------------- /src/containers/DocumentTabPage.js: -------------------------------------------------------------------------------- 1 | var TabPage = require('../tab/TabPage'), 2 | utils = require('../utils/utils'); 3 | 4 | /** 5 | * Specialized tab page that doesn't display the panel's frame when docked in a tab page 6 | */ 7 | function DocumentTabPage(host, container) 8 | { 9 | TabPage.call(this, host, container); 10 | 11 | // If the container is a panel, extract the content element and set it as the tab's content 12 | if (this.container.containerType === 'panel') 13 | { 14 | this.panel = container; 15 | this.containerElement = this.panel.elementContent; 16 | 17 | // detach the container element from the panel's frame. 18 | // It will be reattached when this tab page is destroyed 19 | // This enables the panel's frame (title bar etc) to be hidden 20 | // inside the tab page 21 | utils.removeNode(this.containerElement); 22 | } 23 | } 24 | 25 | DocumentTabPage.prototype = Object.create(TabPage.prototype); 26 | DocumentTabPage.prototype.constructor = DocumentTabPage; 27 | module.exports = DocumentTabPage; 28 | 29 | DocumentTabPage.prototype.destroy = function() 30 | { 31 | TabPage.prototype.destroy.call(this); 32 | 33 | // Restore the panel content element back into the panel frame 34 | utils.removeNode(this.containerElement); 35 | this.panel.elementContentHost.appendChild(this.containerElement); 36 | }; 37 | -------------------------------------------------------------------------------- /src/containers/DocumentManagerContainer.js: -------------------------------------------------------------------------------- 1 | var FillDockContainer = require('./FillDockContainer'), 2 | DocumentTabPage = require('./DocumentTabPage'), 3 | TabHost = require('../tab/TabHost'); 4 | 5 | /** 6 | * The document manager is then central area of the dock layout hierarchy. 7 | * This is where more important panels are placed (e.g. the text editor in an IDE, 8 | * 3D view in a modelling package etc 9 | */ 10 | 11 | function DocumentManagerContainer(dockManager) 12 | { 13 | FillDockContainer.call(this, dockManager, TabHost.DIRECTION_TOP); 14 | this.minimumAllowedChildNodes = 0; 15 | this.element.classList.add('document-manager'); 16 | this.tabHost.createTabPage = this._createDocumentTabPage; 17 | this.tabHost.displayCloseButton = true; 18 | } 19 | 20 | DocumentManagerContainer.prototype = Object.create(FillDockContainer.prototype); 21 | DocumentManagerContainer.prototype.constructor = DocumentManagerContainer; 22 | module.exports = DocumentManagerContainer; 23 | 24 | DocumentManagerContainer.prototype._createDocumentTabPage = function(tabHost, container) 25 | { 26 | return new DocumentTabPage(tabHost, container); 27 | }; 28 | 29 | DocumentManagerContainer.prototype.saveState = function(state) 30 | { 31 | FillDockContainer.prototype.saveState.call(this, state); 32 | state.documentManager = true; 33 | }; 34 | 35 | /** Returns the selected document tab */ 36 | DocumentManagerContainer.prototype.selectedTab = function() 37 | { 38 | return this.tabHost.activeTab; 39 | }; 40 | -------------------------------------------------------------------------------- /src/serialization/DockGraphSerializer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The serializer saves / loads the state of the dock layout hierarchy 3 | */ 4 | function DockGraphSerializer() {} 5 | 6 | module.exports = DockGraphSerializer; 7 | 8 | DockGraphSerializer.prototype.serialize = function(model) 9 | { 10 | var graphInfo = this._buildGraphInfo(model.rootNode); 11 | var dialogs = this._buildDialogsInfo(model.dialogs); 12 | return JSON.stringify({graphInfo: graphInfo, dialogsInfo: dialogs}); 13 | }; 14 | 15 | DockGraphSerializer.prototype._buildGraphInfo = function(node) 16 | { 17 | var nodeState = {}; 18 | node.container.saveState(nodeState); 19 | 20 | var childrenInfo = []; 21 | var self = this; 22 | node.children.forEach(function(childNode) { 23 | childrenInfo.push(self._buildGraphInfo(childNode)); 24 | }); 25 | 26 | var nodeInfo = {}; 27 | nodeInfo.containerType = node.container.containerType; 28 | nodeInfo.state = nodeState; 29 | nodeInfo.children = childrenInfo; 30 | return nodeInfo; 31 | }; 32 | 33 | DockGraphSerializer.prototype._buildDialogsInfo = function(dialogs) 34 | { 35 | var dialogsInfo = []; 36 | dialogs.forEach(function(dialog) { 37 | var panelState = {}; 38 | var panelContainer = dialog.panel; 39 | panelContainer.saveState(panelState); 40 | 41 | var panelInfo = {}; 42 | panelInfo.containerType = panelContainer.containerType; 43 | panelInfo.state = panelState; 44 | panelInfo.children = []; 45 | panelInfo.position = dialog.getPosition(); 46 | panelInfo.isHidden = dialog.isHidden; 47 | dialogsInfo.push(panelInfo); 48 | }); 49 | 50 | return dialogsInfo; 51 | }; 52 | -------------------------------------------------------------------------------- /dist/demos/ide/demo.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #F8F8F8; 4 | font-family: 'Open Sans', sans-serif; 5 | font-size: 14px; 6 | font-weight: normal; 7 | line-height: 1.2em; 8 | margin: 0px; 9 | overflow: hidden; 10 | position:fixed; 11 | } 12 | 13 | .demo-header { 14 | background-color: #000; 15 | color: #888; 16 | width: 100%; 17 | height: 46px; 18 | } 19 | 20 | .demo-header-title { 21 | font-family: 'Open Sans', sans-serif; 22 | font-size: 22px; 23 | padding: 15px; 24 | float: left; 25 | position:relative; 26 | } 27 | 28 | .demo-header-description { 29 | font-family: 'Open Sans', sans-serif; 30 | font-size: 12px; 31 | float: left; 32 | padding: 15px; 33 | position:relative; 34 | } 35 | 36 | .my-dock-manager { 37 | background-color: #F8F8F8; 38 | clear: both; 39 | } 40 | 41 | .solution-window { 42 | width: 300px; 43 | height: 200px; 44 | background-color: #eee; 45 | } 46 | 47 | .output-window { 48 | width: 300px; 49 | height: 200px; 50 | background-color: #eee; 51 | } 52 | 53 | .properties-window { 54 | width: 300px; 55 | height: 200px; 56 | background-color: #aac; 57 | } 58 | 59 | .toolbox-window { 60 | width: 300px; 61 | height: 200px; 62 | background-color: #eee; 63 | } 64 | 65 | .editor1-window { 66 | width: 300px; 67 | height: 200px; 68 | background-color: #fff; 69 | } 70 | 71 | .editor2-window { 72 | width: 300px; 73 | height: 200px; 74 | background-color: #fff; 75 | } 76 | 77 | .outline-window { 78 | width: 300px; 79 | height: 200px; 80 | background-color: #ddd; 81 | } 82 | 83 | .problems-window { 84 | width: 300px; 85 | height: 200px; 86 | background-color: #edd; 87 | } 88 | 89 | .editor-host { 90 | height: auto; 91 | overflow: visible; 92 | } 93 | 94 | .activeline {background: #e8f2ff !important;} 95 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | gutil = require('gulp-util'), 3 | jshint = require('gulp-jshint'), 4 | 5 | source = require('vinyl-source-stream'), 6 | watchify = require('watchify'), 7 | browserify = require('browserify'), 8 | 9 | path = require('path'), 10 | 11 | index = './src/index.js', 12 | outdir = './dist/js', 13 | bundle = 'dockspawn', 14 | outfile = 'dockspawn.js'; 15 | 16 | function rebundle(file) { 17 | if (file) { 18 | gutil.log('Rebundling,', path.basename(file[0]), 'has changes.'); 19 | } 20 | 21 | return this.bundle() 22 | // log errors if they happen 23 | .on('error', gutil.log.bind(gutil, 'Browserify Error')) 24 | .pipe(source(outfile)) 25 | .pipe(gulp.dest(outdir)); 26 | } 27 | 28 | function createBundler(args) { 29 | args = args || {}; 30 | args.standalone = bundle; 31 | 32 | return browserify(index, args); 33 | } 34 | 35 | /***** 36 | * Dev task, incrementally rebuilds the output bundle as the the sources change 37 | *****/ 38 | gulp.task('dev', function() { 39 | watchify.args.standalone = bundle; 40 | var bundler = watchify(createBundler(watchify.args)); 41 | 42 | bundler.on('update', rebundle); 43 | 44 | return rebundle.call(bundler); 45 | }); 46 | 47 | /***** 48 | * Build task, builds the output bundle 49 | *****/ 50 | gulp.task('build', function () { 51 | return rebundle.call(createBundler()); 52 | }); 53 | 54 | /***** 55 | * JSHint task, lints the lib and test *.js files. 56 | *****/ 57 | gulp.task('jshint', function () { 58 | return gulp.src([ 59 | './src/**/*.js', 60 | 'gulpfile.js' 61 | ]) 62 | .pipe(jshint()) 63 | .pipe(jshint.reporter('jshint-summary')); 64 | }); 65 | 66 | /***** 67 | * Base task 68 | *****/ 69 | gulp.task('default', ['jshint', 'build']); 70 | -------------------------------------------------------------------------------- /dist/demos/ide/infovis/Spacetree.css: -------------------------------------------------------------------------------- 1 | .jit-autoadjust-label { 2 | padding: 15px; 3 | } 4 | 5 | #update, #restore { 6 | text-align: center; 7 | width: 200px; 8 | margin:0px 35px 10px 35px; 9 | color: #f00; 10 | } 11 | 12 | .button { 13 | display: inline-block; 14 | outline: none; 15 | cursor: pointer; 16 | text-align: center; 17 | text-decoration: none; 18 | font: 14px / 100% Arial, Helvetica, sans-serif; 19 | padding: 0.5em 1em 0.55em; 20 | text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); 21 | -webkit-border-radius: 0.5em; 22 | -moz-border-radius: 0.5em; 23 | border-radius: 0.5em; 24 | -webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 25 | -moz-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 26 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2); 27 | } 28 | 29 | .button:hover { 30 | text-decoration: none; 31 | } 32 | 33 | .button:active { 34 | position: relative; 35 | top: 1px; 36 | } 37 | 38 | /* white */ 39 | .white { 40 | color: #606060; 41 | border: solid 1px #b7b7b7; 42 | background: #fff; 43 | background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#ededed)); 44 | background: -moz-linear-gradient(top, #fff, #ededed); 45 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed'); 46 | } 47 | 48 | .white:hover { 49 | background: #ededed; 50 | background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#dcdcdc)); 51 | background: -moz-linear-gradient(top, #fff, #dcdcdc); 52 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#dcdcdc'); 53 | } 54 | 55 | .white:active { 56 | color: #999; 57 | background: -webkit-gradient(linear, left top, left bottom, from(#ededed), to(#fff)); 58 | background: -moz-linear-gradient(top, #ededed, #fff); 59 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed', endColorstr='#ffffff'); 60 | } 61 | -------------------------------------------------------------------------------- /src/tab/TabPage.js: -------------------------------------------------------------------------------- 1 | var TabHandle = require('./TabHandle'), 2 | PanelContainer = require('../containers/PanelContainer'), 3 | utils = require('../utils/utils'); 4 | 5 | function TabPage(host, container) 6 | { 7 | if (arguments.length === 0) { 8 | return; 9 | } 10 | 11 | this.selected = false; 12 | this.host = host; 13 | this.container = container; 14 | 15 | this.handle = new TabHandle(this); 16 | this.containerElement = container.containerElement; 17 | 18 | if (container instanceof PanelContainer) 19 | { 20 | var panel = container; 21 | panel.onTitleChanged = this.onTitleChanged.bind(this); 22 | } 23 | } 24 | 25 | module.exports = TabPage; 26 | 27 | TabPage.prototype.onTitleChanged = function(/*sender, title*/) 28 | { 29 | this.handle.updateTitle(); 30 | }; 31 | 32 | TabPage.prototype.destroy = function() 33 | { 34 | this.handle.destroy(); 35 | 36 | if (this.container instanceof PanelContainer) 37 | { 38 | var panel = this.container; 39 | delete panel.onTitleChanged; 40 | } 41 | }; 42 | 43 | TabPage.prototype.onSelected = function() 44 | { 45 | this.host.onTabPageSelected(this); 46 | if (this.container instanceof PanelContainer) 47 | { 48 | var panel = this.container; 49 | panel.dockManager.notifyOnTabChange(this); 50 | } 51 | 52 | }; 53 | 54 | TabPage.prototype.setSelected = function(flag) 55 | { 56 | this.selected = flag; 57 | this.handle.setSelected(flag); 58 | 59 | if (this.selected) 60 | { 61 | this.host.contentElement.appendChild(this.containerElement); 62 | // force a resize again 63 | var width = this.host.contentElement.clientWidth; 64 | var height = this.host.contentElement.clientHeight; 65 | this.container.resize(width, height); 66 | } 67 | else { 68 | utils.removeNode(this.containerElement); 69 | } 70 | }; 71 | 72 | TabPage.prototype.resize = function(width, height) 73 | { 74 | this.container.resize(width, height); 75 | }; 76 | -------------------------------------------------------------------------------- /src/containers/SplitterDockContainer.js: -------------------------------------------------------------------------------- 1 | var SplitterPanel = require('../splitter/SplitterPanel'); 2 | 3 | function SplitterDockContainer(name, dockManager, childContainers) 4 | { 5 | // for prototype inheritance purposes only 6 | if (arguments.length === 0) { 7 | return; 8 | } 9 | 10 | this.name = name; 11 | this.dockManager = dockManager; 12 | this.splitterPanel = new SplitterPanel(childContainers, this.stackedVertical); 13 | this.containerElement = this.splitterPanel.panelElement; 14 | this.minimumAllowedChildNodes = 2; 15 | } 16 | 17 | module.exports = SplitterDockContainer; 18 | 19 | SplitterDockContainer.prototype.resize = function(width, height) 20 | { 21 | // if (_cachedWidth === _cachedWidth && _cachedHeight === _height) { 22 | // // No need to resize 23 | // return; 24 | // } 25 | this.splitterPanel.resize(width, height); 26 | this._cachedWidth = width; 27 | this._cachedHeight = height; 28 | }; 29 | 30 | SplitterDockContainer.prototype.performLayout = function(childContainers) 31 | { 32 | this.splitterPanel.performLayout(childContainers); 33 | }; 34 | 35 | SplitterDockContainer.prototype.setActiveChild = function(/*child*/) 36 | { 37 | }; 38 | 39 | SplitterDockContainer.prototype.destroy = function() 40 | { 41 | this.splitterPanel.destroy(); 42 | }; 43 | 44 | /** 45 | * Sets the percentage of space the specified [container] takes in the split panel 46 | * The percentage is specified in [ratio] and is between 0..1 47 | */ 48 | SplitterDockContainer.prototype.setContainerRatio = function(container, ratio) 49 | { 50 | this.splitterPanel.setContainerRatio(container, ratio); 51 | this.resize(this.width, this.height); 52 | }; 53 | 54 | SplitterDockContainer.prototype.saveState = function(state) 55 | { 56 | state.width = this.width; 57 | state.height = this.height; 58 | }; 59 | 60 | SplitterDockContainer.prototype.loadState = function(state) 61 | { 62 | this.state = {width: state.width, height: state.height}; 63 | // this.resize(state.width, state.height); 64 | }; 65 | 66 | Object.defineProperty(SplitterDockContainer.prototype, 'width', { 67 | get: function() 68 | { 69 | if (this._cachedWidth === undefined) 70 | this._cachedWidth = this.splitterPanel.panelElement.clientWidth; 71 | return this._cachedWidth; 72 | } 73 | }); 74 | 75 | Object.defineProperty(SplitterDockContainer.prototype, 'height', { 76 | get: function() 77 | { 78 | if (this._cachedHeight === undefined) 79 | this._cachedHeight = this.splitterPanel.panelElement.clientHeight; 80 | return this._cachedHeight; 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /src/dock/DockNode.js: -------------------------------------------------------------------------------- 1 | function DockNode(container) 2 | { 3 | /** The dock container represented by this node */ 4 | this.container = container; 5 | this.children = []; 6 | } 7 | 8 | module.exports = DockNode; 9 | 10 | DockNode.prototype.detachFromParent = function() 11 | { 12 | if (this.parent) 13 | { 14 | this.parent.removeChild(this); 15 | delete this.parent; 16 | } 17 | }; 18 | 19 | DockNode.prototype.removeChild = function(childNode) 20 | { 21 | var index = this.children.indexOf(childNode); 22 | if (index >= 0) 23 | this.children.splice(index, 1); 24 | }; 25 | 26 | DockNode.prototype.addChild = function(childNode) 27 | { 28 | childNode.detachFromParent(); 29 | childNode.parent = this; 30 | this.children.push(childNode); 31 | }; 32 | 33 | DockNode.prototype.addChildBefore = function(referenceNode, childNode) 34 | { 35 | this._addChildWithDirection(referenceNode, childNode, true); 36 | }; 37 | 38 | DockNode.prototype.addChildAfter = function(referenceNode, childNode) 39 | { 40 | this._addChildWithDirection(referenceNode, childNode, false); 41 | }; 42 | 43 | DockNode.prototype._addChildWithDirection = function(referenceNode, childNode, before) 44 | { 45 | // Detach this node from it's parent first 46 | childNode.detachFromParent(); 47 | childNode.parent = this; 48 | 49 | var referenceIndex = this.children.indexOf(referenceNode); 50 | var preList = this.children.slice(0, referenceIndex); 51 | var postList = this.children.slice(referenceIndex + 1, this.children.length); 52 | 53 | this.children = preList.slice(0); 54 | if (before) 55 | { 56 | this.children.push(childNode); 57 | this.children.push(referenceNode); 58 | } 59 | else 60 | { 61 | this.children.push(referenceNode); 62 | this.children.push(childNode); 63 | } 64 | Array.prototype.push.apply(this.children, postList); 65 | }; 66 | 67 | DockNode.prototype.performLayout = function() 68 | { 69 | var childContainers = this.children.map(function(childNode) { return childNode.container; }); 70 | this.container.performLayout(childContainers); 71 | }; 72 | 73 | DockNode.prototype.debugDumpTree = function(indent) 74 | { 75 | if (indent === undefined) 76 | indent = 0; 77 | 78 | var message = this.container.name; 79 | for (var i = 0; i < indent; i++) 80 | message = '\t' + message; 81 | 82 | var parentType = this.parent === undefined ? 'null' : this.parent.container.containerType; 83 | console.log('>>' + message + ' [' + parentType + ']'); 84 | 85 | this.children.forEach(function(childNode) { childNode.debugDumpTree(indent + 1); }); 86 | }; 87 | -------------------------------------------------------------------------------- /src/containers/FillDockContainer.js: -------------------------------------------------------------------------------- 1 | var TabHost = require('../tab/TabHost'), 2 | utils = require('../utils/utils'); 3 | 4 | function FillDockContainer(dockManager, tabStripDirection) 5 | { 6 | if (arguments.length === 0) { 7 | return; 8 | } 9 | 10 | if (tabStripDirection === undefined) { 11 | tabStripDirection = TabHost.DIRECTION_BOTTOM; 12 | } 13 | 14 | this.dockManager = dockManager; 15 | this.tabOrientation = tabStripDirection; 16 | this.name = utils.getNextId('fill_'); 17 | this.element = document.createElement('div'); 18 | this.containerElement = this.element; 19 | this.containerType = 'fill'; 20 | this.minimumAllowedChildNodes = 2; 21 | this.element.classList.add('dock-container'); 22 | this.element.classList.add('dock-container-fill'); 23 | this.tabHost = new TabHost(this.tabOrientation); 24 | var that = this; 25 | this.tabHostListener = { 26 | onChange: function (e) { 27 | that.dockManager._requestTabReorder(that, e); 28 | } 29 | }; 30 | this.tabHost.addListener(this.tabHostListener); 31 | this.element.appendChild(this.tabHost.hostElement); 32 | } 33 | 34 | module.exports = FillDockContainer; 35 | 36 | FillDockContainer.prototype.setActiveChild = function(child) 37 | { 38 | this.tabHost.setActiveTab(child); 39 | }; 40 | 41 | FillDockContainer.prototype.resize = function(width, height) 42 | { 43 | this.element.style.width = width + 'px'; 44 | this.element.style.height = height + 'px'; 45 | this.tabHost.resize(width, height); 46 | }; 47 | 48 | FillDockContainer.prototype.performLayout = function(children) 49 | { 50 | this.tabHost.performLayout(children); 51 | }; 52 | 53 | FillDockContainer.prototype.destroy = function() 54 | { 55 | if (utils.removeNode(this.element)) 56 | delete this.element; 57 | }; 58 | 59 | FillDockContainer.prototype.saveState = function(state) 60 | { 61 | state.width = this.width; 62 | state.height = this.height; 63 | }; 64 | 65 | FillDockContainer.prototype.loadState = function(state) 66 | { 67 | // this.resize(state.width, state.height); 68 | // this.width = state.width; 69 | // this.height = state.height; 70 | this.state = {width: state.width, height: state.height}; 71 | }; 72 | 73 | Object.defineProperty(FillDockContainer.prototype, 'width', { 74 | get: function() { 75 | // if(this.element.clientWidth === 0 && this.stateWidth !== 0) 76 | // return this.stateWidth; 77 | return this.element.clientWidth; 78 | }, 79 | set: function(value) { 80 | this.element.style.width = value + 'px'; 81 | } 82 | }); 83 | 84 | Object.defineProperty(FillDockContainer.prototype, 'height', { 85 | get: function() { 86 | // if(this.element.clientHeight === 0 && this.stateHeight !== 0) 87 | // return this.stateHeight; 88 | return this.element.clientHeight; 89 | }, 90 | set: function(value) { 91 | this.element.style.height = value + 'px'; 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pkg: require('../package.json'), 3 | 4 | // tab 5 | TabHandle: require('./tab/TabHandle'), 6 | TabHost: require('./tab/TabHost'), 7 | TabPage: require('./tab/TabPage'), 8 | 9 | // dialog 10 | Dialog: require('./dialog/Dialog'), 11 | 12 | // decorators 13 | DraggableContainer: require('./decorators/DraggableContainer'), 14 | ResizableContainer: require('./decorators/ResizableContainer'), 15 | 16 | // dock 17 | DockLayoutEngine: require('./dock/DockLayoutEngine'), 18 | DockManager: require('./dock/DockManager'), 19 | DockManagerContext: require('./dock/DockManagerContext'), 20 | DockModel: require('./dock/DockModel'), 21 | DockNode: require('./dock/DockNode'), 22 | DockWheel: require('./dock/DockWheel'), 23 | DockWheelItem: require('./dock/DockWheelItem'), 24 | 25 | // containers 26 | DocumentManagerContainer: require('./containers/DocumentManagerContainer'), 27 | FillDockContainer: require('./containers/FillDockContainer'), 28 | HorizontalDockContainer: require('./containers/HorizontalDockContainer'), 29 | PanelContainer: require('./containers/PanelContainer'), 30 | SplitterDockContainer: require('./containers/SplitterDockContainer'), 31 | VerticalDockContainer: require('./containers/VerticalDockContainer'), 32 | 33 | // splitter 34 | SplitterBar: require('./splitter/SplitterBar'), 35 | SplitterPanel: require('./splitter/SplitterPanel'), 36 | 37 | // serialization 38 | DockGraphDeserializer: require('./serialization/DockGraphDeserializer'), 39 | DockGraphSerializer: require('./serialization/DockGraphSerializer'), 40 | 41 | // utils 42 | Point: require('./utils/Point'), 43 | EventHandler: require('./utils/EventHandler'), 44 | UndockInitiator: require('./utils/UndockInitiator') 45 | }; 46 | 47 | module.exports.version = module.exports.pkg.version; 48 | 49 | if (!Array.prototype.remove) { 50 | Array.prototype.remove = function (value) { 51 | var idx = this.indexOf(value); 52 | 53 | if (idx !== -1) { 54 | return this.splice(idx, 1); 55 | } 56 | 57 | return false; 58 | }; 59 | } 60 | 61 | if (!Array.prototype.contains) { 62 | Array.prototype.contains = function (obj) { 63 | var i = this.length; 64 | 65 | while (i--) { 66 | if (this[i] === obj) { 67 | return true; 68 | } 69 | } 70 | 71 | return false; 72 | }; 73 | } 74 | 75 | if (!Array.prototype.orderByIndexes) { 76 | Array.prototype.orderByIndexes = function (indexes){ 77 | var sortedArray = []; 78 | 79 | for (var i = 0; i < indexes.length; i++) { 80 | sortedArray.push(this[indexes[i]]); 81 | } 82 | 83 | return sortedArray; 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/UndockInitiator.js: -------------------------------------------------------------------------------- 1 | var EventHandler = require('./EventHandler'), 2 | Point = require('./Point'); 3 | 4 | /** 5 | * Listens for events on the [element] and notifies the [listener] 6 | * if an undock event has been invoked. An undock event is invoked 7 | * when the user clicks on the event and drags is beyond the 8 | * specified [thresholdPixels] 9 | */ 10 | function UndockInitiator(element, listener, thresholdPixels) { 11 | if (!thresholdPixels) { 12 | thresholdPixels = 10; 13 | } 14 | 15 | this.element = element; 16 | this.listener = listener; 17 | this.thresholdPixels = thresholdPixels; 18 | this._enabled = false; 19 | this.horizontalChange = true; 20 | } 21 | 22 | module.exports = UndockInitiator; 23 | 24 | Object.defineProperty(UndockInitiator.prototype, 'enabled', { 25 | get: function() { 26 | return this._enabled; 27 | }, 28 | set: function(value) { 29 | this._enabled = value; 30 | if (this._enabled) { 31 | if (this.mouseDownHandler) { 32 | this.mouseDownHandler.cancel(); 33 | delete this.mouseDownHandler; 34 | } 35 | 36 | this.mouseDownHandler = new EventHandler(this.element, 'mousedown', this.onMouseDown.bind(this)); 37 | } 38 | else { 39 | if (this.mouseDownHandler) { 40 | this.mouseDownHandler.cancel(); 41 | delete this.mouseDownHandler; 42 | } 43 | 44 | if (this.mouseUpHandler) { 45 | this.mouseUpHandler.cancel(); 46 | delete this.mouseUpHandler; 47 | } 48 | 49 | if (this.mouseMoveHandler) { 50 | this.mouseMoveHandler.cancel(); 51 | delete this.mouseMoveHandler; 52 | } 53 | } 54 | } 55 | }); 56 | 57 | UndockInitiator.prototype.setThresholdPixels = function (thresholdPixels, horizontalChange){ 58 | this.horizontalChange = horizontalChange; 59 | this.thresholdPixels = thresholdPixels; 60 | }; 61 | 62 | UndockInitiator.prototype.onMouseDown = function (e) { 63 | // Make sure we dont do this on floating dialogs 64 | if (this.enabled) { 65 | if (this.mouseUpHandler) { 66 | this.mouseUpHandler.cancel(); 67 | delete this.mouseUpHandler; 68 | } 69 | 70 | if (this.mouseMoveHandler) { 71 | this.mouseMoveHandler.cancel(); 72 | delete this.mouseMoveHandler; 73 | } 74 | 75 | this.mouseUpHandler = new EventHandler(window, 'mouseup', this.onMouseUp.bind(this)); 76 | this.mouseMoveHandler = new EventHandler(window, 'mousemove', this.onMouseMove.bind(this)); 77 | this.dragStartPosition = new Point(e.pageX, e.pageY); 78 | } 79 | }; 80 | 81 | UndockInitiator.prototype.onMouseUp = function () { 82 | if (this.mouseUpHandler) { 83 | this.mouseUpHandler.cancel(); 84 | delete this.mouseUpHandler; 85 | } 86 | 87 | if (this.mouseMoveHandler) { 88 | this.mouseMoveHandler.cancel(); 89 | delete this.mouseMoveHandler; 90 | } 91 | }; 92 | 93 | UndockInitiator.prototype.onMouseMove = function (e) { 94 | var position = new Point(e.pageX, e.pageY); 95 | var dx = this.horizontalChange ? position.x - this.dragStartPosition.x : 10; 96 | var dy = position.y - this.dragStartPosition.y; 97 | var distance = Math.sqrt(dx * dx + dy * dy); 98 | 99 | if (distance > this.thresholdPixels) { 100 | this.enabled = false; 101 | this._requestUndock(e); 102 | } 103 | }; 104 | 105 | UndockInitiator.prototype._requestUndock = function (e) { 106 | var dragOffsetX = this.dragStartPosition.x - this.element.offsetLeft; 107 | var dragOffsetY = this.dragStartPosition.y - this.element.offsetTop; 108 | var dragOffset = new Point(dragOffsetX, dragOffsetY); 109 | this.listener(e, dragOffset); 110 | }; 111 | -------------------------------------------------------------------------------- /src/dialog/Dialog.js: -------------------------------------------------------------------------------- 1 | var PanelContainer = require('../containers/PanelContainer'), 2 | DraggableContainer = require('../decorators/DraggableContainer'), 3 | ResizableContainer = require('../decorators/ResizableContainer'), 4 | EventHandler = require('../utils/EventHandler'), 5 | utils = require('../utils/utils'); 6 | 7 | function Dialog(panel, dockManager) 8 | { 9 | this.panel = panel; 10 | this.zIndexCounter = 100; 11 | this.dockManager = dockManager; 12 | this.eventListener = dockManager; 13 | this._initialize(); 14 | this.dockManager.context.model.dialogs.push(this); 15 | this.position = dockManager.defaultDialogPosition; 16 | 17 | this.dockManager.notifyOnCreateDialog(this); 18 | } 19 | 20 | module.exports = Dialog; 21 | 22 | Dialog.prototype.saveState = function(x, y){ 23 | this.position = {x: x, y: y}; 24 | this.dockManager.notifyOnChangeDialogPosition(this, x, y); 25 | }; 26 | 27 | Dialog.fromElement = function(id, dockManager) 28 | { 29 | return new Dialog(new PanelContainer(document.getElementById(id), dockManager), dockManager); 30 | }; 31 | 32 | Dialog.prototype._initialize = function() 33 | { 34 | this.panel.floatingDialog = this; 35 | this.elementDialog = document.createElement('div'); 36 | this.elementDialog.appendChild(this.panel.elementPanel); 37 | this.draggable = new DraggableContainer(this, this.panel, this.elementDialog, this.panel.elementTitle); 38 | this.resizable = new ResizableContainer(this, this.draggable, this.draggable.topLevelElement); 39 | 40 | document.body.appendChild(this.elementDialog); 41 | this.elementDialog.classList.add('dialog-floating'); 42 | this.elementDialog.classList.add('rounded-corner-top'); 43 | this.panel.elementTitle.classList.add('rounded-corner-top'); 44 | 45 | this.mouseDownHandler = new EventHandler(this.elementDialog, 'mousedown', this.onMouseDown.bind(this)); 46 | this.resize(this.panel.elementPanel.clientWidth, this.panel.elementPanel.clientHeight); 47 | this.isHidden = false; 48 | this.bringToFront(); 49 | }; 50 | 51 | Dialog.prototype.setPosition = function(x, y) 52 | { 53 | this.position = {x: x, y: y}; 54 | this.elementDialog.style.left = x + 'px'; 55 | this.elementDialog.style.top = y + 'px'; 56 | this.dockManager.notifyOnChangeDialogPosition(this, x, y); 57 | }; 58 | 59 | Dialog.prototype.getPosition = function() 60 | { 61 | return { 62 | left: this.position ? this.position.x : 0, 63 | top: this.position ? this.position.y : 0 64 | }; 65 | }; 66 | 67 | Dialog.prototype.onMouseDown = function() 68 | { 69 | this.bringToFront(); 70 | }; 71 | 72 | Dialog.prototype.destroy = function() 73 | { 74 | if (this.mouseDownHandler) 75 | { 76 | this.mouseDownHandler.cancel(); 77 | delete this.mouseDownHandler; 78 | } 79 | this.elementDialog.classList.remove('rounded-corner-top'); 80 | this.panel.elementTitle.classList.remove('rounded-corner-top'); 81 | utils.removeNode(this.elementDialog); 82 | this.draggable.removeDecorator(); 83 | utils.removeNode(this.panel.elementPanel); 84 | this.dockManager.context.model.dialogs.remove(this); 85 | delete this.panel.floatingDialog; 86 | }; 87 | 88 | Dialog.prototype.resize = function(width, height) 89 | { 90 | this.resizable.resize(width, height); 91 | }; 92 | 93 | Dialog.prototype.setTitle = function(title) 94 | { 95 | this.panel.setTitle(title); 96 | }; 97 | 98 | Dialog.prototype.setTitleIcon = function(iconName) 99 | { 100 | this.panel.setTitleIcon(iconName); 101 | }; 102 | 103 | Dialog.prototype.bringToFront = function() 104 | { 105 | this.elementDialog.style.zIndex = this.zIndexCounter++; 106 | }; 107 | 108 | Dialog.prototype.hide = function() 109 | { 110 | this.elementDialog.style.zIndex = 0; 111 | this.elementDialog.style.display = 'none'; 112 | if(!this.isHidden) 113 | { 114 | this.isHidden = true; 115 | this.dockManager.notifyOnHideDialog(this); 116 | } 117 | }; 118 | 119 | Dialog.prototype.show = function() 120 | { 121 | this.elementDialog.style.zIndex = 100; 122 | this.elementDialog.style.display = 'block'; 123 | if(this.isHidden) 124 | { 125 | this.isHidden = false; 126 | this.dockManager.notifyOnShowDialog(this); 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /src/splitter/SplitterBar.js: -------------------------------------------------------------------------------- 1 | var EventHandler = require('../utils/EventHandler'), 2 | utils = require('../utils/utils'); 3 | 4 | function SplitterBar(previousContainer, nextContainer, stackedVertical) 5 | { 6 | // The panel to the left/top side of the bar, depending on the bar orientation 7 | this.previousContainer = previousContainer; 8 | // The panel to the right/bottom side of the bar, depending on the bar orientation 9 | this.nextContainer = nextContainer; 10 | this.stackedVertical = stackedVertical; 11 | this.barElement = document.createElement('div'); 12 | this.barElement.classList.add(stackedVertical ? 'splitbar-horizontal' : 'splitbar-vertical'); 13 | this.mouseDownHandler = new EventHandler(this.barElement, 'mousedown', this.onMouseDown.bind(this)); 14 | this.minPanelSize = 50; // TODO: Get from container configuration 15 | this.readyToProcessNextDrag = true; 16 | } 17 | 18 | module.exports = SplitterBar; 19 | 20 | SplitterBar.prototype.onMouseDown = function(e) 21 | { 22 | this._startDragging(e); 23 | }; 24 | 25 | SplitterBar.prototype.onMouseUp = function(e) 26 | { 27 | this._stopDragging(e); 28 | }; 29 | 30 | SplitterBar.prototype.onMouseMoved = function(e) 31 | { 32 | if (!this.readyToProcessNextDrag) 33 | return; 34 | this.readyToProcessNextDrag = false; 35 | 36 | var dockManager = this.previousContainer.dockManager; 37 | dockManager.suspendLayout(); 38 | var dx = e.pageX - this.previousMouseEvent.pageX; 39 | var dy = e.pageY - this.previousMouseEvent.pageY; 40 | this._performDrag(dx, dy); 41 | this.previousMouseEvent = e; 42 | this.readyToProcessNextDrag = true; 43 | dockManager.resumeLayout(); 44 | }; 45 | 46 | SplitterBar.prototype._performDrag = function(dx, dy) 47 | { 48 | var previousWidth = this.previousContainer.containerElement.clientWidth; 49 | var previousHeight = this.previousContainer.containerElement.clientHeight; 50 | var nextWidth = this.nextContainer.containerElement.clientWidth; 51 | var nextHeight = this.nextContainer.containerElement.clientHeight; 52 | 53 | var previousPanelSize = this.stackedVertical ? previousHeight : previousWidth; 54 | var nextPanelSize = this.stackedVertical ? nextHeight : nextWidth; 55 | var deltaMovement = this.stackedVertical ? dy : dx; 56 | var newPreviousPanelSize = previousPanelSize + deltaMovement; 57 | var newNextPanelSize = nextPanelSize - deltaMovement; 58 | 59 | if (newPreviousPanelSize < this.minPanelSize || newNextPanelSize < this.minPanelSize) 60 | { 61 | // One of the panels is smaller than it should be. 62 | // In that case, check if the small panel's size is being increased 63 | var continueProcessing = (newPreviousPanelSize < this.minPanelSize && newPreviousPanelSize > previousPanelSize) || 64 | (newNextPanelSize < this.minPanelSize && newNextPanelSize > nextPanelSize); 65 | 66 | if (!continueProcessing) 67 | return; 68 | } 69 | 70 | if (this.stackedVertical) 71 | { 72 | this.previousContainer.resize(previousWidth, newPreviousPanelSize); 73 | this.nextContainer.resize(nextWidth, newNextPanelSize); 74 | } 75 | else 76 | { 77 | this.previousContainer.resize(newPreviousPanelSize, previousHeight); 78 | this.nextContainer.resize(newNextPanelSize, nextHeight); 79 | } 80 | }; 81 | 82 | SplitterBar.prototype._startDragging = function(e) 83 | { 84 | utils.disableGlobalTextSelection(); 85 | if (this.mouseMovedHandler) 86 | { 87 | this.mouseMovedHandler.cancel(); 88 | delete this.mouseMovedHandler; 89 | } 90 | if (this.mouseUpHandler) 91 | { 92 | this.mouseUpHandler.cancel(); 93 | delete this.mouseUpHandler; 94 | } 95 | this.mouseMovedHandler = new EventHandler(window, 'mousemove', this.onMouseMoved.bind(this)); 96 | this.mouseUpHandler = new EventHandler(window, 'mouseup', this.onMouseUp.bind(this)); 97 | this.previousMouseEvent = e; 98 | }; 99 | 100 | SplitterBar.prototype._stopDragging = function() 101 | { 102 | utils.enableGlobalTextSelection(); 103 | document.body.classList.remove('disable-selection'); 104 | if (this.mouseMovedHandler) 105 | { 106 | this.mouseMovedHandler.cancel(); 107 | delete this.mouseMovedHandler; 108 | } 109 | if (this.mouseUpHandler) 110 | { 111 | this.mouseUpHandler.cancel(); 112 | delete this.mouseUpHandler; 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /src/decorators/DraggableContainer.js: -------------------------------------------------------------------------------- 1 | var EventHandler = require('../utils/EventHandler'), 2 | Point = require('../utils/Point'), 3 | utils = require('../utils/utils'); 4 | 5 | function DraggableContainer(dialog, delegate, topLevelElement, dragHandle) 6 | { 7 | this.dialog = dialog; 8 | this.delegate = delegate; 9 | this.containerElement = delegate.containerElement; 10 | this.dockManager = delegate.dockManager; 11 | this.topLevelElement = topLevelElement; 12 | this.containerType = delegate.containerType; 13 | this.mouseDownHandler = new EventHandler(dragHandle, 'mousedown', this.onMouseDown.bind(this)); 14 | this.topLevelElement.style.marginLeft = topLevelElement.offsetLeft + 'px'; 15 | this.topLevelElement.style.marginTop = topLevelElement.offsetTop + 'px'; 16 | this.minimumAllowedChildNodes = delegate.minimumAllowedChildNodes; 17 | } 18 | 19 | module.exports = DraggableContainer; 20 | 21 | DraggableContainer.prototype.destroy = function() 22 | { 23 | this.removeDecorator(); 24 | this.delegate.destroy(); 25 | }; 26 | 27 | DraggableContainer.prototype.saveState = function(state) 28 | { 29 | this.delegate.saveState(state); 30 | }; 31 | 32 | DraggableContainer.prototype.loadState = function(state) 33 | { 34 | this.delegate.loadState(state); 35 | }; 36 | 37 | DraggableContainer.prototype.setActiveChild = function(/*child*/) 38 | { 39 | }; 40 | 41 | Object.defineProperty(DraggableContainer.prototype, 'width', { 42 | get: function() { return this.delegate.width; } 43 | }); 44 | 45 | Object.defineProperty(DraggableContainer.prototype, 'height', { 46 | get: function() { return this.delegate.height; } 47 | }); 48 | 49 | DraggableContainer.prototype.name = function(value) 50 | { 51 | if (value) 52 | this.delegate.name = value; 53 | return this.delegate.name; 54 | }; 55 | 56 | DraggableContainer.prototype.resize = function(width, height) 57 | { 58 | this.delegate.resize(width, height); 59 | }; 60 | 61 | DraggableContainer.prototype.performLayout = function(children) 62 | { 63 | this.delegate.performLayout(children); 64 | }; 65 | 66 | DraggableContainer.prototype.removeDecorator = function() 67 | { 68 | if (this.mouseDownHandler) 69 | { 70 | this.mouseDownHandler.cancel(); 71 | delete this.mouseDownHandler; 72 | } 73 | }; 74 | 75 | DraggableContainer.prototype.onMouseDown = function(event) 76 | { 77 | this._startDragging(event); 78 | this.previousMousePosition = { x: event.pageX, y: event.pageY }; 79 | if (this.mouseMoveHandler) 80 | { 81 | this.mouseMoveHandler.cancel(); 82 | delete this.mouseMoveHandler; 83 | } 84 | if (this.mouseUpHandler) 85 | { 86 | this.mouseUpHandler.cancel(); 87 | delete this.mouseUpHandler; 88 | } 89 | 90 | this.mouseMoveHandler = new EventHandler(window, 'mousemove', this.onMouseMove.bind(this)); 91 | this.mouseUpHandler = new EventHandler(window, 'mouseup', this.onMouseUp.bind(this)); 92 | }; 93 | 94 | DraggableContainer.prototype.onMouseUp = function(event) 95 | { 96 | this._stopDragging(event); 97 | this.mouseMoveHandler.cancel(); 98 | delete this.mouseMoveHandler; 99 | this.mouseUpHandler.cancel(); 100 | delete this.mouseUpHandler; 101 | }; 102 | 103 | DraggableContainer.prototype._startDragging = function(event) 104 | { 105 | if (this.dialog.eventListener) 106 | this.dialog.eventListener.onDialogDragStarted(this.dialog, event); 107 | document.body.classList.add('disable-selection'); 108 | }; 109 | 110 | DraggableContainer.prototype._stopDragging = function(event) 111 | { 112 | if (this.dialog.eventListener) 113 | this.dialog.eventListener.onDialogDragEnded(this.dialog, event); 114 | document.body.classList.remove('disable-selection'); 115 | }; 116 | 117 | DraggableContainer.prototype.onMouseMove = function(event) 118 | { 119 | var currentMousePosition = new Point(event.pageX, event.pageY); 120 | 121 | var dx = this.dockManager.checkXBounds(this.topLevelElement, currentMousePosition, this.previousMousePosition); 122 | var dy = this.dockManager.checkYBounds(this.topLevelElement, currentMousePosition, this.previousMousePosition); 123 | this._performDrag(dx, dy); 124 | this.previousMousePosition = currentMousePosition; 125 | }; 126 | 127 | DraggableContainer.prototype._performDrag = function(dx, dy) 128 | { 129 | var left = dx + utils.getPixels(this.topLevelElement.style.marginLeft); 130 | var top = dy + utils.getPixels(this.topLevelElement.style.marginTop); 131 | this.topLevelElement.style.marginLeft = left + 'px'; 132 | this.topLevelElement.style.marginTop = top + 'px'; 133 | }; 134 | -------------------------------------------------------------------------------- /src/serialization/DockGraphDeserializer.js: -------------------------------------------------------------------------------- 1 | var DockModel = require('../dock/DockModel'), 2 | DockNode = require('../dock/DockNode'), 3 | PanelContainer = require('../containers/PanelContainer'), 4 | HorizontalDockContainer = require('../containers/HorizontalDockContainer'), 5 | VerticalDockContainer = require('../containers/VerticalDockContainer'), 6 | DocumentManagerContainer = require('../containers/DocumentManagerContainer'), 7 | FillDockContainer = require('../containers/FillDockContainer'), 8 | Dialog = require('../dialog/Dialog'), 9 | utils = require('../utils/utils'); 10 | 11 | /** 12 | * Deserializes the dock layout hierarchy from JSON and creates a dock hierarhcy graph 13 | */ 14 | function DockGraphDeserializer(dockManager) 15 | { 16 | this.dockManager = dockManager; 17 | } 18 | 19 | module.exports = DockGraphDeserializer; 20 | 21 | DockGraphDeserializer.prototype.deserialize = function(_json) 22 | { 23 | var info = JSON.parse(_json); 24 | var model = new DockModel(); 25 | model.rootNode = this._buildGraph(info.graphInfo); 26 | model.dialogs = this._buildDialogs(info.dialogsInfo); 27 | return model; 28 | }; 29 | 30 | DockGraphDeserializer.prototype._buildGraph = function(nodeInfo) 31 | { 32 | var childrenInfo = nodeInfo.children; 33 | var children = []; 34 | var self = this; 35 | childrenInfo.forEach(function(childInfo) 36 | { 37 | var childNode = self._buildGraph(childInfo); 38 | if (childNode !== null) { 39 | children.push(childNode); 40 | } 41 | }); 42 | 43 | // Build the container owned by this node 44 | var container = this._createContainer(nodeInfo, children); 45 | if(container === null) { 46 | return null; 47 | } 48 | // Build the node for this container and attach it's children 49 | var node = new DockNode(container); 50 | node.children = children; 51 | node.children.reverse().forEach(function(childNode) { 52 | childNode.parent = node; 53 | }); 54 | node.children.reverse(); 55 | // node.container.setActiveChild(node.container); 56 | return node; 57 | }; 58 | 59 | DockGraphDeserializer.prototype._createContainer = function(nodeInfo, children) 60 | { 61 | var containerType = nodeInfo.containerType; 62 | var containerState = nodeInfo.state; 63 | var container; 64 | 65 | var childContainers = []; 66 | children.forEach(function(childNode) { childContainers.push(childNode.container); }); 67 | 68 | 69 | if (containerType === 'panel'){ 70 | container = new PanelContainer.loadFromState(containerState, this.dockManager); 71 | if(!container.prepareForDocking) 72 | return null; 73 | container.prepareForDocking(); 74 | utils.removeNode(container.elementPanel); 75 | } 76 | else if (containerType === 'horizontal') 77 | container = new HorizontalDockContainer(this.dockManager, childContainers); 78 | else if (containerType === 'vertical') 79 | container = new VerticalDockContainer(this.dockManager, childContainers); 80 | else if (containerType === 'fill') 81 | { 82 | // Check if this is a document manager 83 | 84 | // TODO: Layout engine compares the string 'fill', so cannot create another subclass type 85 | // called document_manager and have to resort to this hack. use RTTI in layout engine 86 | var typeDocumentManager = containerState.documentManager; 87 | if (typeDocumentManager) 88 | container = new DocumentManagerContainer(this.dockManager); 89 | else 90 | container = new FillDockContainer(this.dockManager); 91 | } 92 | else 93 | throw new Error('Cannot create dock container of unknown type: ' + containerType); 94 | 95 | // Restore the state of the container 96 | 97 | container.loadState(containerState); 98 | 99 | // container.performLayout(childContainers); 100 | return container; 101 | }; 102 | 103 | DockGraphDeserializer.prototype._buildDialogs = function(dialogsInfo) 104 | { 105 | var dialogs = []; 106 | var self = this; 107 | dialogsInfo.forEach(function(dialogInfo) { 108 | var containerType = dialogInfo.containerType; 109 | var containerState = dialogInfo.state; 110 | var container; 111 | if (containerType === 'panel'){ 112 | container = new PanelContainer.loadFromState(containerState, self.dockManager); 113 | if (container.prepareForDocking) { 114 | utils.removeNode(container.elementPanel); 115 | container.isDialog = true; 116 | var dialog = new Dialog(container, self.dockManager); 117 | if(dialogInfo.position.left > document.body.clientWidth || 118 | dialogInfo.position.top > document.body.clientHeight - 70){ 119 | dialogInfo.position.left = 20; 120 | dialogInfo.position.top = 70; 121 | } 122 | dialog.setPosition(dialogInfo.position.left, dialogInfo.position.top); 123 | dialog.isHidden = dialogInfo.isHidden; 124 | if(dialog.isHidden) 125 | dialog.hide(); 126 | dialogs.push(dialog); 127 | } 128 | } 129 | 130 | }); 131 | return dialogs; 132 | }; 133 | -------------------------------------------------------------------------------- /dist/css/dock-manager.css: -------------------------------------------------------------------------------- 1 | 2 | /************* Panel with title bar ************/ 3 | .panel-base { 4 | } 5 | 6 | .panel-titlebar { 7 | background-color: #333; 8 | height: 25px; 9 | width: 100%; 10 | overflow: hidden; 11 | } 12 | 13 | .panel-titlebar-text { 14 | float: left; 15 | padding-left:10px; 16 | padding-top:5px; 17 | font-weight: bold; 18 | color:#aaa; 19 | } 20 | 21 | .panel-titlebar-button-close { 22 | float: right; 23 | padding-top:5px; 24 | padding-right:8px; 25 | font-weight: bold; 26 | color:#aaa; 27 | } 28 | 29 | .panel-titlebar-button-close:hover { 30 | cursor: pointer; 31 | float: right; 32 | padding-top:5px; 33 | padding-right:8px; 34 | font-weight: bold; 35 | color:#fff; 36 | } 37 | 38 | .panel-content { 39 | background-color: #FFF; 40 | width: 100%; 41 | } 42 | 43 | .panel-content * { 44 | margin: 0px; 45 | } 46 | 47 | /***************** Floating dialog box ****************/ 48 | .dialog-floating { 49 | position: absolute; 50 | box-shadow: 5px 5px 20px #000; 51 | } 52 | 53 | /************ Resize decorator ************/ 54 | .resize-handle { 55 | position: absolute; 56 | width: 6px; 57 | height: 6px; 58 | } 59 | 60 | .resize-handle-corner { 61 | position: absolute; 62 | width: 12px; 63 | height: 12px; 64 | } 65 | 66 | .resize-handle-e { cursor: e-resize; } 67 | .resize-handle-w { cursor: w-resize; } 68 | .resize-handle-s { cursor: s-resize; } 69 | .resize-handle-n { cursor: n-resize; } 70 | .resize-handle-ne { cursor: ne-resize; } 71 | .resize-handle-nw { cursor: nw-resize; } 72 | .resize-handle-se { cursor: se-resize; } 73 | .resize-handle-sw { cursor: sw-resize; } 74 | 75 | 76 | /******************* Dock Manager ********************/ 77 | .dock-container { 78 | background-color: #888; 79 | } 80 | 81 | .dock-container-fill { 82 | 83 | } 84 | 85 | /******************* Document Manager ********************/ 86 | .document-manager { 87 | background-color: #666; 88 | } 89 | 90 | 91 | /******************* Dock Wheel ********************/ 92 | .dock-wheel-base { 93 | position: absolute; 94 | } 95 | 96 | .dock-wheel-item { 97 | position: absolute; 98 | width:32px; 99 | height:32px; 100 | } 101 | 102 | .dock-wheel-fill { 103 | margin-left: -16px; 104 | margin-top: -16px; 105 | } 106 | 107 | .dock-wheel-left { 108 | margin-left: -48px; 109 | margin-top: -16px; 110 | } 111 | 112 | .dock-wheel-right { 113 | margin-left: 16px; 114 | margin-top: -16px; 115 | } 116 | 117 | .dock-wheel-top { 118 | margin-left: -16px; 119 | margin-top: -48px; 120 | } 121 | 122 | .dock-wheel-down { 123 | margin-left: -16px; 124 | margin-top: 16px; 125 | } 126 | 127 | .dock-wheel-panel-preview { 128 | position: absolute; 129 | background-color:rgba(128, 128, 255, 0.5); 130 | } 131 | 132 | .dock-wheel-fill-icon { background:url(../images/dock_fill.png) 0 0; } 133 | .dock-wheel-left-icon { background:url(../images/dock_left.png) 0 0; } 134 | .dock-wheel-right-icon { background:url(../images/dock_right.png) 0 0; } 135 | .dock-wheel-top-icon { background:url(../images/dock_top.png) 0 0; } 136 | .dock-wheel-down-icon { background:url(../images/dock_bottom.png) 0 0; } 137 | 138 | .dock-wheel-fill-icon-hover { background:url(../images/dock_fill_sel.png) 0 0; } 139 | .dock-wheel-left-icon-hover { background:url(../images/dock_left_sel.png) 0 0; } 140 | .dock-wheel-right-icon-hover { background:url(../images/dock_right_sel.png) 0 0; } 141 | .dock-wheel-top-icon-hover { background:url(../images/dock_top_sel.png) 0 0; } 142 | .dock-wheel-down-icon-hover { background:url(../images/dock_bottom_sel.png) 0 0; } 143 | 144 | /**************************** Splitter *********************************/ 145 | .splitter-container-horizontal { 146 | float:left; 147 | /* background-color: #333; */ 148 | } 149 | 150 | .splitter-container-vertical { 151 | /* background-color: #333; */ 152 | } 153 | 154 | .splitbar-horizontal { 155 | background-color: #000000; 156 | width:100%; 157 | height:5px; 158 | cursor:n-resize; 159 | } 160 | 161 | .splitbar-vertical { 162 | background-color: #000000; 163 | width:5px; 164 | height:100%; 165 | float:left; 166 | cursor:e-resize; 167 | } 168 | 169 | .splitbar-horizontal-ghoust{ 170 | background-color: #ffcc00; 171 | width:100%; 172 | height:5px; 173 | cursor:n-resize; 174 | position:absolute; 175 | } 176 | .splitbar-vertical-ghoust { 177 | background-color: #ffcc00; 178 | width:5px; 179 | height:100%; 180 | position:absolute; 181 | cursor:e-resize; 182 | } 183 | 184 | 185 | /*************************** Tab Host ********************************/ 186 | .tab-host { 187 | background-color: #666; 188 | } 189 | 190 | .tab-content { 191 | background-color: #666; 192 | } 193 | 194 | /*.tab-content * { 195 | margin: 0px; 196 | }*/ 197 | 198 | .tab-handle { 199 | position: relative; 200 | background-color: #808080; 201 | color:#D5D5D5; 202 | height: 22px; 203 | float: left; 204 | overflow: hidden; 205 | pointer: default; 206 | box-shadow: 0px 5px 20px #000; 207 | } 208 | .tab-handle:hover { 209 | cursor: pointer; 210 | background-color: #ffcc00; 211 | color:#400; 212 | } 213 | 214 | .tab-handle-selected { 215 | background-color: #ffcc00; 216 | color:#400; 217 | } 218 | 219 | .tab-handle-text { 220 | margin-top: 3px; 221 | margin-left: 6px; 222 | margin-right: 6px; 223 | white-space: nowrap; 224 | overflow: hidden; 225 | float: left; 226 | } 227 | 228 | .tab-handle-close-button { 229 | margin-top: 3px; 230 | margin-right: 6px; 231 | float: right; 232 | } 233 | 234 | .tab-handle-close-button:hover { 235 | color:#fff; 236 | } 237 | 238 | .tab-handle-list-container { 239 | background-color: #333; 240 | height: 22px; 241 | overflow: hidden; 242 | } 243 | 244 | .tab-handle-content-seperator { 245 | background-color: #ffcc00; 246 | height: 4px; 247 | } 248 | 249 | /*************************** Text Selection **************************/ 250 | .disable-selection { 251 | -moz-user-select: none; 252 | -khtml-user-select: none; 253 | -webkit-user-select: none; 254 | user-select: none; 255 | cursor: default; 256 | } 257 | 258 | 259 | /************************ Misc Utils *************************/ 260 | .full-screen { 261 | display:block; 262 | 263 | /*set the div in the top-left corner of the screen*/ 264 | top:0; 265 | left:0; 266 | 267 | /*set the width and height to 100% of the screen*/ 268 | width:100%; 269 | height:100%; 270 | 271 | } 272 | 273 | .rounded-corner-top { 274 | border-top-right-radius: 10px; 275 | border-top-left-radius: 10px; 276 | } 277 | -------------------------------------------------------------------------------- /src/splitter/SplitterPanel.js: -------------------------------------------------------------------------------- 1 | var SplitterBar = require('./SplitterBar'), 2 | utils = require('../utils/utils'); 3 | 4 | /** 5 | * A splitter panel manages the child containers inside it with splitter bars. 6 | * It can be stacked horizontally or vertically 7 | */ 8 | function SplitterPanel(childContainers, stackedVertical) 9 | { 10 | this.childContainers = childContainers; 11 | this.stackedVertical = stackedVertical; 12 | this.panelElement = document.createElement('div'); 13 | this.spiltterBars = []; 14 | this._buildSplitterDOM(); 15 | } 16 | 17 | module.exports = SplitterPanel; 18 | 19 | SplitterPanel.prototype._buildSplitterDOM = function() 20 | { 21 | if (this.childContainers.length <= 1) 22 | throw new Error('Splitter panel should contain atleast 2 panels'); 23 | 24 | this.spiltterBars = []; 25 | for (var i = 0; i < this.childContainers.length - 1; i++) 26 | { 27 | var previousContainer = this.childContainers[i]; 28 | var nextContainer = this.childContainers[i + 1]; 29 | var splitterBar = new SplitterBar(previousContainer, nextContainer, this.stackedVertical); 30 | this.spiltterBars.push(splitterBar); 31 | 32 | // Add the container and split bar to the panel's base div element 33 | this._insertContainerIntoPanel(previousContainer); 34 | this.panelElement.appendChild(splitterBar.barElement); 35 | } 36 | this._insertContainerIntoPanel(this.childContainers.slice(-1)[0]); 37 | }; 38 | 39 | SplitterPanel.prototype.performLayout = function(children) 40 | { 41 | this.removeFromDOM(); 42 | 43 | // rebuild 44 | this.childContainers = children; 45 | this._buildSplitterDOM(); 46 | }; 47 | 48 | SplitterPanel.prototype.removeFromDOM = function() 49 | { 50 | this.childContainers.forEach(function(container) 51 | { 52 | if (container.containerElement) 53 | { 54 | container.containerElement.classList.remove('splitter-container-vertical'); 55 | container.containerElement.classList.remove('splitter-container-horizontal'); 56 | utils.removeNode(container.containerElement); 57 | } 58 | }); 59 | this.spiltterBars.forEach(function(bar) { utils.removeNode(bar.barElement); }); 60 | }; 61 | 62 | SplitterPanel.prototype.destroy = function() 63 | { 64 | this.removeFromDOM(); 65 | this.panelElement.parentNode.removeChild(this.panelElement); 66 | }; 67 | 68 | SplitterPanel.prototype._insertContainerIntoPanel = function(container) 69 | { 70 | if (!container) 71 | { 72 | console.log('undefined'); 73 | } 74 | 75 | utils.removeNode(container.containerElement); 76 | this.panelElement.appendChild(container.containerElement); 77 | container.containerElement.classList.add( 78 | this.stackedVertical ? 'splitter-container-vertical' : 'splitter-container-horizontal' 79 | ); 80 | }; 81 | 82 | /** 83 | * Sets the percentage of space the specified [container] takes in the split panel 84 | * The percentage is specified in [ratio] and is between 0..1 85 | */ 86 | SplitterPanel.prototype.setContainerRatio = function(container, ratio) 87 | { 88 | var splitPanelSize = this.stackedVertical ? this.panelElement.clientHeight : this.panelElement.clientWidth; 89 | var newContainerSize = splitPanelSize * ratio; 90 | var barSize = this.stackedVertical ? 91 | this.spiltterBars[0].barElement.clientHeight : this.spiltterBars[0].barElement.clientWidth; 92 | 93 | var otherPanelSizeQuota = splitPanelSize - newContainerSize - barSize * this.spiltterBars.length; 94 | var otherPanelScaleMultipler = otherPanelSizeQuota / splitPanelSize; 95 | 96 | for (var i = 0; i < this.childContainers.length; i++) 97 | { 98 | var child = this.childContainers[i]; 99 | var size; 100 | if (child !== container) 101 | { 102 | size = this.stackedVertical ? child.containerElement.clientHeight : child.containerElement.clientWidth; 103 | size *= otherPanelScaleMultipler; 104 | } 105 | else 106 | size = newContainerSize; 107 | 108 | if (this.stackedVertical) 109 | child.resize(child.width, Math.floor(size)); 110 | else 111 | child.resize(Math.floor(size), child.height); 112 | } 113 | }; 114 | 115 | SplitterPanel.prototype.resize = function(width, height) 116 | { 117 | if (this.childContainers.length <= 1) 118 | return; 119 | 120 | var i; 121 | 122 | // Adjust the fixed dimension that is common to all (i.e. width, if stacked vertical; height, if stacked horizontally) 123 | for (i = 0; i < this.childContainers.length; i++) 124 | { 125 | var childContainer = this.childContainers[i]; 126 | if (this.stackedVertical) 127 | childContainer.resize(width, childContainer.height); 128 | else 129 | childContainer.resize(childContainer.width, height); 130 | 131 | if (i < this.spiltterBars.length) { 132 | var splitBar = this.spiltterBars[i]; 133 | if (this.stackedVertical) 134 | splitBar.barElement.style.width = width + 'px'; 135 | else 136 | splitBar.barElement.style.height = height + 'px'; 137 | } 138 | } 139 | 140 | // Adjust the varying dimension 141 | var totalChildPanelSize = 0; 142 | // Find out how much space existing child containers take up (excluding the splitter bars) 143 | var self = this; 144 | this.childContainers.forEach(function(container) 145 | { 146 | var size = self.stackedVertical ? 147 | container.height : 148 | container.width; 149 | totalChildPanelSize += size; 150 | }); 151 | 152 | // Get the thickness of the bar 153 | var barSize = this.stackedVertical ? 154 | this.spiltterBars[0].barElement.clientHeight : this.spiltterBars[0].barElement.clientWidth; 155 | 156 | // Find out how much space existing child containers will take after being resized (excluding the splitter bars) 157 | var targetTotalChildPanelSize = this.stackedVertical ? height : width; 158 | targetTotalChildPanelSize -= barSize * this.spiltterBars.length; 159 | 160 | // Get the scale multiplier 161 | totalChildPanelSize = Math.max(totalChildPanelSize, 1); 162 | var scaleMultiplier = targetTotalChildPanelSize / totalChildPanelSize; 163 | 164 | 165 | // Update the size with this multiplier 166 | var updatedTotalChildPanelSize = 0; 167 | for (i = 0; i < this.childContainers.length; i++) 168 | { 169 | var child = this.childContainers[i]; 170 | var original = this.stackedVertical ? 171 | child.containerElement.clientHeight : 172 | child.containerElement.clientWidth; 173 | 174 | var newSize = scaleMultiplier > 1 ? Math.floor(original * scaleMultiplier) : 175 | Math.ceil(original * scaleMultiplier); 176 | updatedTotalChildPanelSize += newSize; 177 | 178 | // If this is the last node, add any extra pixels to fix the rounding off errors and match the requested size 179 | if (i === this.childContainers.length - 1) 180 | newSize += targetTotalChildPanelSize - updatedTotalChildPanelSize; 181 | 182 | // Set the size of the panel 183 | if (this.stackedVertical) 184 | child.resize(child.width, newSize ); 185 | else 186 | child.resize( newSize, child.height); 187 | } 188 | 189 | this.panelElement.style.width = width + 'px'; 190 | this.panelElement.style.height = height + 'px'; 191 | }; 192 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // -------------------------------------------------------------------- 3 | // JSHint Configuration 4 | // -------------------------------------------------------------------- 5 | // 6 | // @author Chad Engler 7 | 8 | // == Enforcing Options =============================================== 9 | // 10 | // These options tell JSHint to be more strict towards your code. Use 11 | // them if you want to allow only a safe subset of JavaScript, very 12 | // useful when your codebase is shared with a big number of developers 13 | // with different skill levels. 14 | 15 | "bitwise" : false, // Disallow bitwise operators (&, |, ^, etc.). 16 | "camelcase" : true, // Force all variable names to use either camelCase or UPPER_CASE. 17 | "curly" : false, // Require {} for every new block or scope. 18 | "eqeqeq" : true, // Require triple equals i.e. `===`. 19 | "es3" : false, // Enforce conforming to ECMAScript 3. 20 | "forin" : false, // Disallow `for in` loops without `hasOwnPrototype`. 21 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 22 | "indent" : 4, // Require that 4 spaces are used for indentation. 23 | "latedef" : "nofunc", // Prohibit variable use before definition. 24 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 25 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 26 | "noempty" : true, // Prohibit use of empty blocks. 27 | "nonew" : true, // Prohibit use of constructors for side-effects. 28 | "plusplus" : false, // Disallow use of `++` & `--`. 29 | "quotmark" : true, // Force consistency when using quote marks. 30 | "undef" : true, // Require all non-global variables be declared before they are used. 31 | "unused" : true, // Warn when varaibles are created but not used. 32 | "strict" : false, // Require `use strict` pragma in every file. 33 | "trailing" : true, // Prohibit trailing whitespaces. 34 | "maxparams" : 7, // Prohibit having more than X number of params in a function. 35 | "maxdepth" : 6, // Prohibit nested blocks from going more than X levels deep. 36 | "maxstatements" : false, // Restrict the number of statements in a function. 37 | "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. 38 | "maxlen" : 125, // Require that all lines are n characters or less. 39 | "globals" : { // Register globals that are used in the code. 40 | "Phaser": false, 41 | "PIXI": false 42 | }, 43 | 44 | // == Relaxing Options ================================================ 45 | // 46 | // These options allow you to suppress certain types of warnings. Use 47 | // them only if you are absolutely positive that you know what you are 48 | // doing. 49 | 50 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). 51 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 52 | "debug" : false, // Allow debugger statements e.g. browser breakpoints. 53 | "eqnull" : false, // Tolerate use of `== null`. 54 | "esnext" : false, // Allow ES.next specific features such as `const` and `let`. 55 | "evil" : false, // Tolerate use of `eval`. 56 | "expr" : false, // Tolerate `ExpressionStatement` as Programs. 57 | "funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside. 58 | "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). 59 | "iterator" : false, // Allow usage of __iterator__ property. 60 | "lastsemic" : false, // Tolerate missing semicolons when the it is omitted for the last statement in a one-line block. 61 | "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 62 | "laxcomma" : false, // Suppress warnings about comma-first coding style. 63 | "loopfunc" : false, // Allow functions to be defined within loops. 64 | "moz" : false, // Code that uses Mozilla JS extensions will set this to true 65 | "multistr" : false, // Tolerate multi-line strings. 66 | "proto" : false, // Tolerate __proto__ property. This property is deprecated. 67 | "scripturl" : false, // Tolerate script-targeted URLs. 68 | "smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only. 69 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 70 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 71 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 72 | "validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function. 73 | 74 | // == Environments ==================================================== 75 | // 76 | // These options pre-define global variables that are exposed by 77 | // popular JavaScript libraries and runtime environments—such as 78 | // browser or node.js. 79 | 80 | "browser" : true, // Standard browser globals e.g. `window`, `document`. 81 | "couch" : false, // Enable globals exposed by CouchDB. 82 | "devel" : false, // Allow development statements e.g. `console.log();`. 83 | "dojo" : false, // Enable globals exposed by Dojo Toolkit. 84 | "jquery" : false, // Enable globals exposed by jQuery JavaScript library. 85 | "mootools" : false, // Enable globals exposed by MooTools JavaScript framework. 86 | "node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment. (for Gruntfile) 87 | "nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape. 88 | "prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework. 89 | "rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment. 90 | "worker" : false, // Enable globals available when your code is running as a WebWorker. 91 | "wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host. 92 | "yui" : false, // Enable globals exposed by YUI library. 93 | 94 | // == JSLint Legacy =================================================== 95 | // 96 | // These options are legacy from JSLint. Aside from bug fixes they will 97 | // not be improved in any way and might be removed at any point. 98 | 99 | "nomen" : false, // Prohibit use of initial or trailing underbars in names. 100 | "onevar" : false, // Allow only one `var` statement per function. 101 | "passfail" : false, // Stop on first error. 102 | "white" : false, // Check against strict whitespace and indentation rules. 103 | 104 | // == Undocumented Options ============================================ 105 | // 106 | // While I've found these options in [example1][2] and [example2][3] 107 | // they are not described in the [JSHint Options documentation][4]. 108 | // 109 | // [4]: http://www.jshint.com/options/ 110 | 111 | "maxerr" : 100 // Maximum errors before stopping. 112 | } 113 | -------------------------------------------------------------------------------- /src/tab/TabHandle.js: -------------------------------------------------------------------------------- 1 | var PanelContainer = require('../containers/PanelContainer'), 2 | UndockInitiator = require('../utils/UndockInitiator'), 3 | EventHandler = require('../utils/EventHandler'), 4 | utils = require('../utils/utils'); 5 | 6 | /** 7 | * A tab handle represents the tab button on the tab strip 8 | */ 9 | function TabHandle(parent) 10 | { 11 | this.parent = parent; 12 | var undockHandler = TabHandle.prototype._performUndock.bind(this); 13 | this.elementBase = document.createElement('div'); 14 | this.elementText = document.createElement('div'); 15 | this.elementCloseButton = document.createElement('div'); 16 | this.elementBase.classList.add('tab-handle'); 17 | this.elementBase.classList.add('disable-selection'); // Disable text selection 18 | this.elementText.classList.add('tab-handle-text'); 19 | this.elementCloseButton.classList.add('tab-handle-close-button'); 20 | this.elementBase.appendChild(this.elementText); 21 | if (this.parent.host.displayCloseButton) 22 | this.elementBase.appendChild(this.elementCloseButton); 23 | 24 | this.parent.host.tabListElement.appendChild(this.elementBase); 25 | 26 | var panel = parent.container; 27 | var title = panel.getRawTitle(); 28 | var that = this; 29 | this.undockListener = { 30 | onDockEnabled:function(e){ that.undockEnabled(e.state); }, 31 | onHideCloseButton: function(e){ that.hideCloseButton(e.state); } 32 | }; 33 | this.eventListeners = []; 34 | panel.addListener(this.undockListener); 35 | 36 | this.elementText.innerHTML = title; 37 | 38 | // Set the close button text (font awesome) 39 | if (this.parent.container instanceof PanelContainer && this.parent.container.dockManager.closeTabIconTemplate) { 40 | this.elementCloseButton.innerHTML = this.parent.container.dockManager.closeTabIconTemplate(); 41 | } 42 | else { 43 | this.elementCloseButton.innerHTML = ''; 44 | } 45 | 46 | this._bringToFront(this.elementBase); 47 | 48 | this.undockInitiator = new UndockInitiator(this.elementBase, undockHandler); 49 | this.undockInitiator.enabled = true; 50 | this.mouseClickHandler = new EventHandler(this.elementBase, 'click', this.onMouseClicked.bind(this)); 51 | this.mouseDownHandler = new EventHandler(this.elementBase, 'mousedown', this.onMouseDown.bind(this)); 52 | this.closeButtonHandler = new EventHandler(this.elementCloseButton, 'mousedown', this.onCloseButtonClicked.bind(this)); 53 | 54 | this.moveThreshold = 10; 55 | this.zIndexCounter = 100; 56 | } 57 | 58 | module.exports = TabHandle; 59 | 60 | TabHandle.prototype.addListener = function(listener){ 61 | this.eventListeners.push(listener); 62 | }; 63 | 64 | TabHandle.prototype.removeListener = function(listener) 65 | { 66 | this.eventListeners.splice(this.eventListeners.indexOf(listener), 1); 67 | }; 68 | 69 | TabHandle.prototype.undockEnabled = function(state) 70 | { 71 | this.undockInitiator.enabled = state; 72 | }; 73 | 74 | TabHandle.prototype.onMouseDown = function(e) 75 | { 76 | if(this.undockInitiator.enabled) 77 | this.undockInitiator.setThresholdPixels(40, false); 78 | if (this.mouseMoveHandler) 79 | { 80 | this.mouseMoveHandler.cancel(); 81 | delete this.mouseMoveHandler; 82 | } 83 | if (this.mouseUpHandler) 84 | { 85 | this.mouseUpHandler.cancel(); 86 | delete this.mouseUpHandler; 87 | } 88 | this.stargDragPosition = e.clientX; 89 | this.mouseMoveHandler = new EventHandler(this.elementBase, 'mousemove', this.onMouseMove.bind(this)); 90 | this.mouseUpHandler = new EventHandler(window, 'mouseup', this.onMouseUp.bind(this)); 91 | }; 92 | 93 | TabHandle.prototype.onMouseUp = function() 94 | { 95 | if(this.undockInitiator.enabled) 96 | this.undockInitiator.setThresholdPixels(10, true); 97 | if(this.elementBase){ 98 | this.elementBase.classList.remove('tab-handle-dragged'); 99 | } 100 | this.dragged = false; 101 | this.mouseMoveHandler.cancel(); 102 | this.mouseUpHandler.cancel(); 103 | delete this.mouseMoveHandler; 104 | delete this.mouseUpHandler; 105 | 106 | }; 107 | 108 | TabHandle.prototype.generateMoveTabEvent = function(event, pos) { 109 | var contain = pos > event.rect.left && pos < event.rect.right; 110 | var m = Math.abs(event.bound - pos); 111 | if(m < this.moveThreshold && contain) 112 | this.moveTabEvent(this, event.state); 113 | }; 114 | 115 | TabHandle.prototype.moveTabEvent = function(that, state){ 116 | that.eventListeners.forEach(function(listener) { 117 | if (listener.onMoveTab) { 118 | listener.onMoveTab({self: that, state: state}); 119 | } 120 | }); 121 | 122 | }; 123 | 124 | TabHandle.prototype.onMouseMove = function(e) 125 | { 126 | if(Math.abs(this.stargDragPosition - e.clientX) < 10) 127 | return; 128 | this.elementBase.classList.add('tab-handle-dragged'); 129 | this.dragged = true; 130 | this.prev = this.current; 131 | this.current = e.clientX; 132 | this.direction = this.current - this.prev; 133 | var tabRect = this.elementBase.getBoundingClientRect(); 134 | var event = this.direction < 0 ? {state: 'left', bound: tabRect.left, rect:tabRect} : 135 | {state: 'right', bound: tabRect.right, rect:tabRect}; 136 | if(this.direction !== 0) this.generateMoveTabEvent(event, this.current); 137 | }; 138 | 139 | TabHandle.prototype.hideCloseButton = function(state) 140 | { 141 | this.elementCloseButton.style.display = state ? 'none' : 'block'; 142 | }; 143 | 144 | TabHandle.prototype.updateTitle = function() 145 | { 146 | if (this.parent.container instanceof PanelContainer) 147 | { 148 | var panel = this.parent.container; 149 | var title = panel.getRawTitle(); 150 | this.elementText.innerHTML = title; 151 | } 152 | }; 153 | 154 | TabHandle.prototype.destroy = function() 155 | { 156 | var panel = this.parent.container; 157 | panel.removeListener(this.undockListener); 158 | 159 | this.mouseClickHandler.cancel(); 160 | this.mouseDownHandler.cancel(); 161 | this.closeButtonHandler.cancel(); 162 | 163 | if (this.mouseUpHandler) { 164 | this.mouseUpHandler.cancel(); 165 | } 166 | 167 | utils.removeNode(this.elementBase); 168 | utils.removeNode(this.elementCloseButton); 169 | delete this.elementBase; 170 | delete this.elementCloseButton; 171 | }; 172 | 173 | TabHandle.prototype._performUndock = function(e, dragOffset) 174 | { 175 | if (this.parent.container.containerType === 'panel') 176 | { 177 | this.undockInitiator.enabled = false; 178 | var panel = this.parent.container; 179 | return panel.performUndockToDialog(e, dragOffset); 180 | } 181 | else 182 | return null; 183 | }; 184 | 185 | TabHandle.prototype.onMouseClicked = function() 186 | { 187 | this.parent.onSelected(); 188 | }; 189 | 190 | TabHandle.prototype.onCloseButtonClicked = function() 191 | { 192 | // If the page contains a panel element, undock it and destroy it 193 | if (this.parent.container.containerType === 'panel') 194 | { 195 | this.parent.container.close(); 196 | // this.undockInitiator.enabled = false; 197 | // var panel = this.parent.container; 198 | // panel.performUndock(); 199 | } 200 | }; 201 | 202 | TabHandle.prototype.setSelected = function(selected) 203 | { 204 | var selectedClassName = 'tab-handle-selected'; 205 | if (selected) 206 | this.elementBase.classList.add(selectedClassName); 207 | else 208 | this.elementBase.classList.remove(selectedClassName); 209 | }; 210 | 211 | TabHandle.prototype.setZIndex = function(zIndex) 212 | { 213 | this.elementBase.style.zIndex = zIndex; 214 | }; 215 | 216 | TabHandle.prototype._bringToFront = function(element) 217 | { 218 | element.style.zIndex = this.zIndexCounter; 219 | this.zIndexCounter++; 220 | }; 221 | -------------------------------------------------------------------------------- /dist/demos/ide/infovis/dock_tree_vis.js: -------------------------------------------------------------------------------- 1 | var labelType, useGradients, nativeTextSupport, animate; 2 | 3 | (function() { 4 | var ua = navigator.userAgent, 5 | iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i), 6 | typeOfCanvas = typeof HTMLCanvasElement, 7 | nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'), 8 | textSupport = nativeCanvasSupport 9 | && (typeof document.createElement('canvas').getContext('2d').fillText == 'function'); 10 | //I'm setting this based on the fact that ExCanvas provides text support for IE 11 | //and that as of today iPhone/iPad current text support is lame 12 | labelType = (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML'; 13 | nativeTextSupport = labelType == 'Native'; 14 | useGradients = nativeCanvasSupport; 15 | animate = !(iStuff || !nativeCanvasSupport); 16 | })(); 17 | 18 | var Log = { 19 | elem: false, 20 | write: function(text){} 21 | }; 22 | 23 | var _spaceTree; 24 | 25 | function getJsonTree(dockNode) { 26 | var children = []; 27 | for (var i = 0; i < dockNode.children.length; i++) { 28 | var childInfo = getJsonTree(dockNode.children[i]); 29 | children.push(childInfo); 30 | } 31 | var id = dockNode.container.name; 32 | var title = id; 33 | var type = dockNode.container.containerType; 34 | if (type == "panel") { 35 | title = dockNode.container.title; 36 | } 37 | var caption = title + " [" + type + "]"; 38 | var json = { 39 | id: id, 40 | name: caption, 41 | data: {}, 42 | children: children 43 | }; 44 | return json; 45 | } 46 | 47 | DockVisListener = function() {}; 48 | DockVisListener.prototype.onDock = function(dockManager, dockNode) { 49 | var rootNode = dockManager.context.model.rootNode; // TODO: Root node is not synced properly 50 | RebuildDebugTree(rootNode); 51 | }; 52 | DockVisListener.prototype.onUndock = function(dockManager, dockNode) { 53 | var rootNode = dockManager.context.model.rootNode; // TODO: Root node is not synced properly 54 | RebuildDebugTree(rootNode); 55 | }; 56 | 57 | function RebuildDebugTree(rootNode) { 58 | var json = getJsonTree(rootNode); 59 | BindDebugData(json); 60 | } 61 | 62 | function InitDebugTreeVis(dockManager) { 63 | var dockListener = new DockVisListener(); 64 | dockManager.addLayoutListener(dockListener); 65 | 66 | //Implement a node rendering function called 'nodeline' that plots a straight line 67 | //when contracting or expanding a subtree. 68 | $jit.ST.Plot.NodeTypes.implement({ 69 | 'nodeline': { 70 | 'render': function(node, canvas, animating) { 71 | if(animating === 'expand' || animating === 'contract') { 72 | var pos = node.pos.getc(true), nconfig = this.node, data = node.data; 73 | var width = nconfig.width, height = nconfig.height; 74 | var algnPos = this.getAlignedPos(pos, width, height); 75 | var ctx = canvas.getCtx(), ort = this.config.orientation; 76 | ctx.beginPath(); 77 | if(ort == 'left' || ort == 'right') { 78 | ctx.moveTo(algnPos.x, algnPos.y + height / 2); 79 | ctx.lineTo(algnPos.x + width, algnPos.y + height / 2); 80 | } else { 81 | ctx.moveTo(algnPos.x + width / 2, algnPos.y); 82 | ctx.lineTo(algnPos.x + width / 2, algnPos.y + height); 83 | } 84 | ctx.stroke(); 85 | } 86 | } 87 | } 88 | 89 | }); 90 | 91 | //init Spacetree 92 | //Create a new ST instance 93 | var st = new $jit.ST({ 94 | //id of viz container element 95 | injectInto: 'infovis', 96 | //set duration for the animation 97 | duration: 0, 98 | //set animation transition type 99 | transition: $jit.Trans.Quart.easeInOut, 100 | //set distance between node and its children 101 | levelDistance: 50, 102 | subtreeOffset: 10, 103 | siblingOffset: 20, 104 | levelsToShow: 100, 105 | constrained: false, 106 | orientation: 'top', 107 | //enable panning 108 | Navigation: { 109 | enable:true, 110 | panning:true 111 | }, 112 | //set node and edge styles 113 | //set overridable=true for styling individual 114 | //nodes or edges 115 | Node: { 116 | height: 20, 117 | width: 140, 118 | //use a custom 119 | //node rendering function 120 | type: 'nodeline', 121 | color:'#23A4FF', 122 | lineWidth: 2, 123 | align:"center", 124 | overridable: true 125 | }, 126 | 127 | Edge: { 128 | type: 'bezier', 129 | lineWidth: 2, 130 | color:'#23A4FF', 131 | overridable: true 132 | }, 133 | 134 | onBeforeCompute: function(node){ 135 | Log.write("loading " + node.name); 136 | }, 137 | 138 | onAfterCompute: function(){ 139 | Log.write("done"); 140 | }, 141 | 142 | //This method is called on DOM label creation. 143 | //Use this method to add event handlers and styles to 144 | //your node. 145 | onCreateLabel: function(label, node){ 146 | label.id = node.id; 147 | label.innerHTML = node.name; 148 | label.onclick = function(){ 149 | if(normal.checked) { 150 | st.onClick(node.id); 151 | } else { 152 | st.setRoot(node.id, 'animate'); 153 | } 154 | }; 155 | //set label styles 156 | var style = label.style; 157 | style.width = 140 + 'px'; 158 | style.height = 17 + 'px'; 159 | style.cursor = 'pointer'; 160 | style.color = '#fff'; 161 | style.fontSize = '0.8em'; 162 | style.textAlign= 'center'; 163 | style.paddingTop = '3px'; 164 | }, 165 | 166 | //This method is called right before plotting 167 | //a node. It's useful for changing an individual node 168 | //style properties before plotting it. 169 | //The data properties prefixed with a dollar 170 | //sign will override the global node style properties. 171 | onBeforePlotNode: function(node){ 172 | //add some color to the nodes in the path between the 173 | //root node and the selected node. 174 | if (node.selected) { 175 | node.data.$color = "#ff7"; 176 | } 177 | else { 178 | delete node.data.$color; 179 | //if the node belongs to the last plotted level 180 | if(!node.anySubnode("exist")) { 181 | //count children number 182 | var count = 0; 183 | node.eachSubnode(function(n) { count++; }); 184 | //assign a node color based on 185 | //how many children it has 186 | node.data.$color = ['#aaa', '#baa', '#caa', '#daa', '#eaa', '#faa'][count]; 187 | } 188 | } 189 | }, 190 | 191 | //This method is called right before plotting 192 | //an edge. It's useful for changing an individual edge 193 | //style properties before plotting it. 194 | //Edge data proprties prefixed with a dollar sign will 195 | //override the Edge global style properties. 196 | onBeforePlotLine: function(adj){ 197 | if (adj.nodeFrom.selected && adj.nodeTo.selected) { 198 | adj.data.$color = "#eed"; 199 | adj.data.$lineWidth = 3; 200 | } 201 | else { 202 | delete adj.data.$color; 203 | delete adj.data.$lineWidth; 204 | } 205 | } 206 | }); 207 | 208 | _spaceTree = st; 209 | RebuildDebugTree(dockManager.context.model.rootNode); 210 | } 211 | 212 | 213 | function BindDebugData(json) { 214 | //load json data 215 | _spaceTree.loadJSON(json); 216 | //compute node positions and layout 217 | _spaceTree.compute(); 218 | //optional: make a translation of the tree 219 | _spaceTree.geom.translate(new $jit.Complex(0, 0), "current"); 220 | //emulate a click on the root node. 221 | _spaceTree.onClick(_spaceTree.root); 222 | } -------------------------------------------------------------------------------- /src/tab/TabHost.js: -------------------------------------------------------------------------------- 1 | var TabPage = require('./TabPage'); 2 | 3 | /** 4 | * Tab Host control contains tabs known as TabPages. 5 | * The tab strip can be aligned in different orientations 6 | */ 7 | function TabHost(tabStripDirection, displayCloseButton) 8 | { 9 | /** 10 | * Create a tab host with the tab strip aligned in the [tabStripDirection] direciton 11 | * Only TabHost.DIRECTION_BOTTOM and TabHost.DIRECTION_TOP are supported 12 | */ 13 | if (tabStripDirection === undefined) { 14 | tabStripDirection = TabHost.DIRECTION_BOTTOM; 15 | } 16 | 17 | if (displayCloseButton === undefined) { 18 | displayCloseButton = false; 19 | } 20 | 21 | this.tabStripDirection = tabStripDirection; 22 | this.displayCloseButton = displayCloseButton; // Indicates if the close button next to the tab handle should be displayed 23 | this.pages = []; 24 | var that = this; 25 | that.eventListeners = []; 26 | this.tabHandleListener = { 27 | onMoveTab :function(e){ that.onMoveTab(e); } 28 | }; 29 | this.hostElement = document.createElement('div'); // The main tab host DOM element 30 | this.tabListElement = document.createElement('div'); // Hosts the tab handles 31 | this.separatorElement = document.createElement('div'); // A seperator line between the tabs and content 32 | this.contentElement = document.createElement('div'); // Hosts the active tab content 33 | this.createTabPage = this._createDefaultTabPage; // Factory for creating tab pages 34 | 35 | if (this.tabStripDirection === TabHost.DIRECTION_BOTTOM) 36 | { 37 | this.hostElement.appendChild(this.contentElement); 38 | this.hostElement.appendChild(this.separatorElement); 39 | this.hostElement.appendChild(this.tabListElement); 40 | } 41 | else if (this.tabStripDirection === TabHost.DIRECTION_TOP) 42 | { 43 | this.hostElement.appendChild(this.tabListElement); 44 | this.hostElement.appendChild(this.separatorElement); 45 | this.hostElement.appendChild(this.contentElement); 46 | } 47 | else { 48 | throw new Error('Only top and bottom tab strip orientations are supported'); 49 | } 50 | 51 | this.hostElement.classList.add('tab-host'); 52 | this.tabListElement.classList.add('tab-handle-list-container'); 53 | this.separatorElement.classList.add('tab-handle-content-seperator'); 54 | this.contentElement.classList.add('tab-content'); 55 | } 56 | 57 | module.exports = TabHost; 58 | 59 | // constants 60 | TabHost.DIRECTION_TOP = 0; 61 | TabHost.DIRECTION_BOTTOM = 1; 62 | TabHost.DIRECTION_LEFT = 2; 63 | TabHost.DIRECTION_RIGHT = 3; 64 | 65 | TabHost.prototype.onMoveTab = function(e){ 66 | // this.tabListElement; 67 | var index = Array.prototype.slice.call(this.tabListElement.childNodes).indexOf(e.self.elementBase); 68 | 69 | this.change(/*host*/this, /*handle*/e.self, e.state, index); 70 | }; 71 | 72 | TabHost.prototype.performTabsLayout = function(indexes){ 73 | this.pages = this.pages.orderByIndexes(indexes); 74 | 75 | var items = this.tabListElement.childNodes; 76 | var itemsArr = []; 77 | for (var i in items) { 78 | if (items[i].nodeType === 1) { // get rid of the whitespace text nodes 79 | itemsArr.push(items[i]); 80 | } 81 | } 82 | itemsArr = itemsArr.orderByIndexes(indexes); 83 | for (i = 0; i < itemsArr.length; ++i) { 84 | this.tabListElement.appendChild(itemsArr[i]); 85 | } 86 | }; 87 | 88 | TabHost.prototype.addListener = function(listener){ 89 | this.eventListeners.push(listener); 90 | }; 91 | 92 | TabHost.prototype.removeListener = function(listener) 93 | { 94 | this.eventListeners.splice(this.eventListeners.indexOf(listener), 1); 95 | }; 96 | 97 | TabHost.prototype.change = function(host, handle, state, index){ 98 | this.eventListeners.forEach(function(listener) { 99 | if (listener.onChange) { 100 | listener.onChange({host: host, handle: handle, state: state, index: index}); 101 | } 102 | }); 103 | }; 104 | 105 | TabHost.prototype._createDefaultTabPage = function(tabHost, container) 106 | { 107 | return new TabPage(tabHost, container); 108 | }; 109 | 110 | TabHost.prototype.setActiveTab = function(/*container*/) 111 | { 112 | if (this.pages.length > 0) { 113 | this.onTabPageSelected(this.pages[0]); 114 | } 115 | }; 116 | 117 | TabHost.prototype.resize = function(width, height) 118 | { 119 | this.hostElement.style.width = width + 'px'; 120 | this.hostElement.style.height = height + 'px'; 121 | 122 | var tabHeight = this.tabListElement.clientHeight; 123 | if (this.timeoutPerform) //lazy check 124 | clearTimeout(this.timeoutPerform); 125 | var self = this; 126 | this.timeoutPerform = setTimeout(function () { 127 | self.resizeTabListElement(width, height); 128 | }, 100); 129 | var separatorHeight = this.separatorElement.clientHeight; 130 | var contentHeight = height - tabHeight - separatorHeight; 131 | this.contentElement.style.height = contentHeight + 'px'; 132 | 133 | if (this.activeTab) 134 | this.activeTab.resize(width, contentHeight); 135 | }; 136 | 137 | TabHost.prototype.resizeTabListElement = function(width/*, height*/) { 138 | if(this.pages.length === 0) return; 139 | var tabListWidth = 0; 140 | this.pages.forEach(function(page){ 141 | var handle = page.handle; 142 | handle.elementBase.style.width = ''; //clear 143 | handle.elementText.style.width = ''; 144 | tabListWidth += handle.elementBase.clientWidth; 145 | }); 146 | var scaleMultiplier = width / tabListWidth; 147 | if(scaleMultiplier > 1.2) return; //with a reserve 148 | var self = this; 149 | this.pages.forEach(function(page, index){ 150 | var handle = page.handle; 151 | var newSize = scaleMultiplier * handle.elementBase.clientWidth; 152 | if(index === self.pages.length - 1) 153 | newSize = newSize - 5; 154 | handle.elementBase.style.width = newSize + 'px'; 155 | if(self.tabStripDirection === TabHost.DIRECTION_TOP){ 156 | handle.elementText.style.width = newSize - handle.elementCloseButton.clientWidth - 16 + 'px'; 157 | } 158 | }); 159 | }; 160 | 161 | TabHost.prototype.performLayout = function(children) 162 | { 163 | // Destroy all existing tab pages 164 | this.pages.forEach(function(tab) 165 | { 166 | tab.handle.removeListener(this.tabHandleListener); 167 | tab.destroy(); 168 | }); 169 | this.pages.length = 0; 170 | 171 | var oldActiveTab = this.activeTab; 172 | delete this.activeTab; 173 | 174 | var childPanels = children.filter(function(child) 175 | { 176 | return child.containerType === 'panel'; 177 | }); 178 | 179 | if (childPanels.length > 0) 180 | { 181 | // Rebuild new tab pages 182 | var self = this; 183 | childPanels.forEach(function(child) 184 | { 185 | var page = self.createTabPage(self, child); 186 | page.handle.addListener(self.tabHandleListener); 187 | self.pages.push(page); 188 | 189 | // Restore the active selected tab 190 | if (oldActiveTab && page.container === oldActiveTab.container) 191 | self.activeTab = page; 192 | }); 193 | this._setTabHandlesVisible(true); 194 | } 195 | else 196 | // Do not show an empty tab handle host with zero tabs 197 | this._setTabHandlesVisible(false); 198 | 199 | if (this.activeTab) 200 | this.onTabPageSelected(this.activeTab); 201 | }; 202 | 203 | TabHost.prototype._setTabHandlesVisible = function(visible) 204 | { 205 | this.tabListElement.style.display = visible ? 'block' : 'none'; 206 | this.separatorElement.style.display = visible ? 'block' : 'none'; 207 | }; 208 | 209 | TabHost.prototype.onTabPageSelected = function(page) 210 | { 211 | this.activeTab = page; 212 | this.pages.forEach(function(tabPage) 213 | { 214 | var selected = (tabPage === page); 215 | tabPage.setSelected(selected); 216 | }); 217 | 218 | // adjust the zIndex of the tabs to have proper shadow/depth effect 219 | var zIndexDelta = 1; 220 | var zIndex = 100; 221 | this.pages.forEach(function(tabPage) 222 | { 223 | tabPage.handle.setZIndex(zIndex); 224 | var selected = (tabPage === page); 225 | if (selected) 226 | zIndexDelta = -1; 227 | zIndex += zIndexDelta; 228 | }); 229 | 230 | }; 231 | -------------------------------------------------------------------------------- /dist/demos/ide/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 |
26 |
27 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 55 |
56 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/dock/DockWheel.js: -------------------------------------------------------------------------------- 1 | var DockWheelItem = require('./DockWheelItem'), 2 | utils = require('../utils/utils'); 3 | 4 | /** 5 | * Manages the dock overlay buttons that are displayed over the dock manager 6 | */ 7 | function DockWheel(dockManager) 8 | { 9 | this.dockManager = dockManager; 10 | this.elementMainWheel = document.createElement('div'); // Contains the main wheel's 5 dock buttons 11 | this.elementSideWheel = document.createElement('div'); // Contains the 4 buttons on the side 12 | this.wheelItems = {}; 13 | var wheelTypes = [ 14 | 'left', 'right', 'top', 'down', 'fill', // Main dock wheel buttons 15 | 'left-s', 'right-s', 'top-s', 'down-s' // Buttons on the extreme 4 sides 16 | ]; 17 | var self = this; 18 | wheelTypes.forEach(function(wheelType) 19 | { 20 | self.wheelItems[wheelType] = new DockWheelItem(self, wheelType); 21 | if (wheelType.substr(-2, 2) === '-s') 22 | // Side button 23 | self.elementSideWheel.appendChild(self.wheelItems[wheelType].element); 24 | else 25 | // Main dock wheel button 26 | self.elementMainWheel.appendChild(self.wheelItems[wheelType].element); 27 | }); 28 | 29 | var zIndex = 100000; 30 | this.elementMainWheel.classList.add('dock-wheel-base'); 31 | this.elementSideWheel.classList.add('dock-wheel-base'); 32 | this.elementMainWheel.style.zIndex = zIndex + 1; 33 | this.elementSideWheel.style.zIndex = zIndex; 34 | this.elementPanelPreview = document.createElement('div'); 35 | this.elementPanelPreview.classList.add('dock-wheel-panel-preview'); 36 | this.elementPanelPreview.style.zIndex = zIndex - 1; 37 | this.activeDialog = undefined; // The dialog being dragged, when the wheel is visible 38 | this._activeNode = undefined; 39 | this._visible = false; 40 | } 41 | 42 | module.exports = DockWheel; 43 | 44 | /** The node over which the dock wheel is being displayed on */ 45 | Object.defineProperty(DockWheel.prototype, 'activeNode', { 46 | get: function() { return this._activeNode; }, 47 | set: function(value) 48 | { 49 | var previousValue = this._activeNode; 50 | this._activeNode = value; 51 | 52 | if (previousValue !== this._activeNode) 53 | { 54 | // The active node has been changed. 55 | // Reattach the wheel to the new node's element and show it again 56 | if (this._visible) 57 | this.showWheel(); 58 | } 59 | } 60 | }); 61 | 62 | DockWheel.prototype.showWheel = function() 63 | { 64 | this._visible = true; 65 | if (!this.activeNode) 66 | { 67 | // No active node selected. make sure the wheel is invisible 68 | utils.removeNode(this.elementMainWheel); 69 | utils.removeNode(this.elementSideWheel); 70 | return; 71 | } 72 | var element = this.activeNode.container.containerElement; 73 | var containerWidth = element.clientWidth; 74 | var containerHeight = element.clientHeight; 75 | var baseX = Math.floor(containerWidth / 2) + element.offsetLeft; 76 | var baseY = Math.floor(containerHeight / 2) + element.offsetTop; 77 | this.elementMainWheel.style.left = baseX + 'px'; 78 | this.elementMainWheel.style.top = baseY + 'px'; 79 | 80 | // The positioning of the main dock wheel buttons is done automatically through CSS 81 | // Dynamically calculate the positions of the buttons on the extreme sides of the dock manager 82 | var sideMargin = 20; 83 | var dockManagerWidth = this.dockManager.element.clientWidth; 84 | var dockManagerHeight = this.dockManager.element.clientHeight; 85 | // var dockManagerOffsetX = this.dockManager.element.offsetLeft; 86 | // var dockManagerOffsetY = this.dockManager.element.offsetTop; 87 | 88 | utils.removeNode(this.elementMainWheel); 89 | utils.removeNode(this.elementSideWheel); 90 | element.appendChild(this.elementMainWheel); 91 | this.dockManager.element.appendChild(this.elementSideWheel); 92 | 93 | this._setWheelButtonPosition('left-s', sideMargin, -dockManagerHeight / 2); 94 | this._setWheelButtonPosition('right-s', dockManagerWidth - sideMargin * 2, -dockManagerHeight / 2); 95 | this._setWheelButtonPosition('top-s', dockManagerWidth / 2, -dockManagerHeight + sideMargin); 96 | this._setWheelButtonPosition('down-s', dockManagerWidth / 2, -sideMargin); 97 | }; 98 | 99 | DockWheel.prototype._setWheelButtonPosition = function(wheelId, left, top) 100 | { 101 | var item = this.wheelItems[wheelId]; 102 | var itemHalfWidth = item.element.clientWidth / 2; 103 | var itemHalfHeight = item.element.clientHeight / 2; 104 | 105 | var x = Math.floor(left - itemHalfWidth); 106 | var y = Math.floor(top - itemHalfHeight); 107 | // item.element.style.left = '${x}px'; 108 | // item.element.style.top = '${y}px'; 109 | item.element.style.marginLeft = x + 'px'; 110 | item.element.style.marginTop = y + 'px'; 111 | }; 112 | 113 | DockWheel.prototype.hideWheel = function() 114 | { 115 | this._visible = false; 116 | this.activeNode = undefined; 117 | utils.removeNode(this.elementMainWheel); 118 | utils.removeNode(this.elementSideWheel); 119 | utils.removeNode(this.elementPanelPreview); 120 | 121 | // deactivate all wheels 122 | for (var wheelType in this.wheelItems) 123 | this.wheelItems[wheelType].active = false; 124 | }; 125 | 126 | DockWheel.prototype.onMouseOver = function(wheelItem) 127 | { 128 | if (!this.activeDialog) 129 | return; 130 | 131 | // Display the preview panel to show where the panel would be docked 132 | var rootNode = this.dockManager.context.model.rootNode; 133 | var bounds; 134 | if (wheelItem.id === 'top') { 135 | bounds = this.dockManager.layoutEngine.getDockBounds(this.activeNode, this.activeDialog.panel, 'vertical', true); 136 | } else if (wheelItem.id === 'down') { 137 | bounds = this.dockManager.layoutEngine.getDockBounds(this.activeNode, this.activeDialog.panel, 'vertical', false); 138 | } else if (wheelItem.id === 'left') { 139 | bounds = this.dockManager.layoutEngine.getDockBounds(this.activeNode, this.activeDialog.panel, 'horizontal', true); 140 | } else if (wheelItem.id === 'right') { 141 | bounds = this.dockManager.layoutEngine.getDockBounds(this.activeNode, this.activeDialog.panel, 'horizontal', false); 142 | } else if (wheelItem.id === 'fill') { 143 | bounds = this.dockManager.layoutEngine.getDockBounds(this.activeNode, this.activeDialog.panel, 'fill', false); 144 | } else if (wheelItem.id === 'top-s') { 145 | bounds = this.dockManager.layoutEngine.getDockBounds(rootNode, this.activeDialog.panel, 'vertical', true); 146 | } else if (wheelItem.id === 'down-s') { 147 | bounds = this.dockManager.layoutEngine.getDockBounds(rootNode, this.activeDialog.panel, 'vertical', false); 148 | } else if (wheelItem.id === 'left-s') { 149 | bounds = this.dockManager.layoutEngine.getDockBounds(rootNode, this.activeDialog.panel, 'horizontal', true); 150 | } else if (wheelItem.id === 'right-s') { 151 | bounds = this.dockManager.layoutEngine.getDockBounds(rootNode, this.activeDialog.panel, 'horizontal', false); 152 | } 153 | 154 | if (bounds) 155 | { 156 | this.dockManager.element.appendChild(this.elementPanelPreview); 157 | this.elementPanelPreview.style.left = Math.round(bounds.x) + 'px'; 158 | this.elementPanelPreview.style.top = Math.round(bounds.y) + 'px'; 159 | this.elementPanelPreview.style.width = Math.round(bounds.width) + 'px'; 160 | this.elementPanelPreview.style.height = Math.round(bounds.height) + 'px'; 161 | } 162 | }; 163 | 164 | DockWheel.prototype.onMouseOut = function() 165 | { 166 | utils.removeNode(this.elementPanelPreview); 167 | }; 168 | 169 | /** 170 | * Called if the dialog is dropped in a dock panel. 171 | * The dialog might not necessarily be dropped in one of the dock wheel buttons, 172 | * in which case the request will be ignored 173 | */ 174 | DockWheel.prototype.onDialogDropped = function(dialog) 175 | { 176 | // Check if the dialog was dropped in one of the wheel items 177 | var wheelItem = this._getActiveWheelItem(); 178 | if (wheelItem) 179 | this._handleDockRequest(wheelItem, dialog); 180 | }; 181 | 182 | /** 183 | * Returns the wheel item which has the mouse cursor on top of it 184 | */ 185 | DockWheel.prototype._getActiveWheelItem = function() 186 | { 187 | for (var wheelType in this.wheelItems) 188 | { 189 | var wheelItem = this.wheelItems[wheelType]; 190 | if (wheelItem.active) 191 | return wheelItem; 192 | } 193 | return undefined; 194 | }; 195 | 196 | DockWheel.prototype._handleDockRequest = function(wheelItem, dialog) 197 | { 198 | if (!this.activeNode) 199 | return; 200 | if (wheelItem.id === 'left') { 201 | this.dockManager.dockDialogLeft(this.activeNode, dialog); 202 | } else if (wheelItem.id === 'right') { 203 | this.dockManager.dockDialogRight(this.activeNode, dialog); 204 | } else if (wheelItem.id === 'top') { 205 | this.dockManager.dockDialogUp(this.activeNode, dialog); 206 | } else if (wheelItem.id === 'down') { 207 | this.dockManager.dockDialogDown(this.activeNode, dialog); 208 | } else if (wheelItem.id === 'fill') { 209 | this.dockManager.dockDialogFill(this.activeNode, dialog); 210 | } else if (wheelItem.id === 'left-s') { 211 | this.dockManager.dockDialogLeft(this.dockManager.context.model.rootNode, dialog); 212 | } else if (wheelItem.id === 'right-s') { 213 | this.dockManager.dockDialogRight(this.dockManager.context.model.rootNode, dialog); 214 | } else if (wheelItem.id === 'top-s') { 215 | this.dockManager.dockDialogUp(this.dockManager.context.model.rootNode, dialog); 216 | } else if (wheelItem.id === 'down-s') { 217 | this.dockManager.dockDialogDown(this.dockManager.context.model.rootNode, dialog); 218 | } 219 | }; 220 | 221 | -------------------------------------------------------------------------------- /src/decorators/ResizableContainer.js: -------------------------------------------------------------------------------- 1 | var EventHandler = require('../utils/EventHandler'), 2 | Point = require('../utils/Point'), 3 | utils = require('../utils/utils'); 4 | 5 | /** 6 | * Decorates a dock container with resizer handles around its base element 7 | * This enables the container to be resized from all directions 8 | */ 9 | function ResizableContainer(dialog, delegate, topLevelElement) 10 | { 11 | this.dialog = dialog; 12 | this.delegate = delegate; 13 | this.containerElement = delegate.containerElement; 14 | this.dockManager = delegate.dockManager; 15 | this.topLevelElement = topLevelElement; 16 | this.containerType = delegate.containerType; 17 | this.topLevelElement.style.marginLeft = this.topLevelElement.offsetLeft + 'px'; 18 | this.topLevelElement.style.marginTop = this.topLevelElement.offsetTop + 'px'; 19 | this.minimumAllowedChildNodes = delegate.minimumAllowedChildNodes; 20 | this._buildResizeHandles(); 21 | this.readyToProcessNextResize = true; 22 | } 23 | 24 | module.exports = ResizableContainer; 25 | 26 | ResizableContainer.prototype.setActiveChild = function(/*child*/) 27 | { 28 | }; 29 | 30 | ResizableContainer.prototype._buildResizeHandles = function() 31 | { 32 | this.resizeHandles = []; 33 | // this._buildResizeHandle(true, false, true, false); // Dont need the corner resizer near the close button 34 | this._buildResizeHandle(false, true, true, false); 35 | this._buildResizeHandle(true, false, false, true); 36 | this._buildResizeHandle(false, true, false, true); 37 | 38 | this._buildResizeHandle(true, false, false, false); 39 | this._buildResizeHandle(false, true, false, false); 40 | this._buildResizeHandle(false, false, true, false); 41 | this._buildResizeHandle(false, false, false, true); 42 | }; 43 | 44 | ResizableContainer.prototype._buildResizeHandle = function(east, west, north, south) 45 | { 46 | var handle = new ResizeHandle(); 47 | handle.east = east; 48 | handle.west = west; 49 | handle.north = north; 50 | handle.south = south; 51 | 52 | // Create an invisible div for the handle 53 | handle.element = document.createElement('div'); 54 | this.topLevelElement.appendChild(handle.element); 55 | 56 | // Build the class name for the handle 57 | var verticalClass = ''; 58 | var horizontalClass = ''; 59 | if (north) verticalClass = 'n'; 60 | if (south) verticalClass = 's'; 61 | if (east) horizontalClass = 'e'; 62 | if (west) horizontalClass = 'w'; 63 | var cssClass = 'resize-handle-' + verticalClass + horizontalClass; 64 | if (verticalClass.length > 0 && horizontalClass.length > 0) 65 | handle.corner = true; 66 | 67 | handle.element.classList.add(handle.corner ? 'resize-handle-corner' : 'resize-handle'); 68 | handle.element.classList.add(cssClass); 69 | this.resizeHandles.push(handle); 70 | 71 | var self = this; 72 | handle.mouseDownHandler = new EventHandler(handle.element, 'mousedown', function(e) { self.onMouseDown(handle, e); }); 73 | }; 74 | 75 | ResizableContainer.prototype.saveState = function(state) 76 | { 77 | this.delegate.saveState(state); 78 | }; 79 | 80 | ResizableContainer.prototype.loadState = function(state) 81 | { 82 | this.delegate.loadState(state); 83 | }; 84 | 85 | Object.defineProperty(ResizableContainer.prototype, 'width', { 86 | get: function() { return this.delegate.width; } 87 | }); 88 | 89 | Object.defineProperty(ResizableContainer.prototype, 'height', { 90 | get: function() { return this.delegate.height; } 91 | }); 92 | 93 | ResizableContainer.prototype.name = function(value) 94 | { 95 | if (value) 96 | this.delegate.name = value; 97 | return this.delegate.name; 98 | }; 99 | 100 | ResizableContainer.prototype.resize = function(width, height) 101 | { 102 | this.delegate.resize(width, height); 103 | this._adjustResizeHandles(width, height); 104 | }; 105 | 106 | ResizableContainer.prototype._adjustResizeHandles = function(width, height) 107 | { 108 | var self = this; 109 | this.resizeHandles.forEach(function(handle) { 110 | handle.adjustSize(self.topLevelElement, width, height); 111 | }); 112 | }; 113 | 114 | ResizableContainer.prototype.performLayout = function(children) 115 | { 116 | this.delegate.performLayout(children); 117 | }; 118 | 119 | ResizableContainer.prototype.destroy = function() 120 | { 121 | this.removeDecorator(); 122 | this.delegate.destroy(); 123 | }; 124 | 125 | ResizableContainer.prototype.removeDecorator = function() 126 | { 127 | }; 128 | 129 | ResizableContainer.prototype.onMouseMoved = function(handle, e) 130 | { 131 | if (!this.readyToProcessNextResize) 132 | return; 133 | this.readyToProcessNextResize = false; 134 | 135 | // window.requestLayoutFrame(() { 136 | this.dockManager.suspendLayout(); 137 | var currentMousePosition = new Point(e.pageX, e.pageY); 138 | var dx = this.dockManager.checkXBounds(this.topLevelElement, currentMousePosition, this.previousMousePosition); 139 | var dy = this.dockManager.checkYBounds(this.topLevelElement, currentMousePosition, this.previousMousePosition); 140 | this._performDrag(handle, dx, dy); 141 | this.previousMousePosition = currentMousePosition; 142 | this.readyToProcessNextResize = true; 143 | if(this.dialog.panel) 144 | this.dockManager.resumeLayout(this.dialog.panel); 145 | }; 146 | 147 | ResizableContainer.prototype.onMouseDown = function(handle, event) 148 | { 149 | this.previousMousePosition = new Point(event.pageX, event.pageY); 150 | if (handle.mouseMoveHandler) 151 | { 152 | handle.mouseMoveHandler.cancel(); 153 | delete handle.mouseMoveHandler; 154 | } 155 | if (handle.mouseUpHandler) 156 | { 157 | handle.mouseUpHandler.cancel(); 158 | delete handle.mouseUpHandler; 159 | } 160 | 161 | // Create the mouse event handlers 162 | var self = this; 163 | handle.mouseMoveHandler = new EventHandler(window, 'mousemove', function(e) { self.onMouseMoved(handle, e); }); 164 | handle.mouseUpHandler = new EventHandler(window, 'mouseup', function(e) { self.onMouseUp(handle, e); }); 165 | 166 | document.body.classList.add('disable-selection'); 167 | }; 168 | 169 | ResizableContainer.prototype.onMouseUp = function(handle) 170 | { 171 | handle.mouseMoveHandler.cancel(); 172 | handle.mouseUpHandler.cancel(); 173 | delete handle.mouseMoveHandler; 174 | delete handle.mouseUpHandler; 175 | 176 | document.body.classList.remove('disable-selection'); 177 | }; 178 | 179 | ResizableContainer.prototype._performDrag = function(handle, dx, dy) 180 | { 181 | var bounds = {}; 182 | bounds.left = utils.getPixels(this.topLevelElement.style.marginLeft); 183 | bounds.top = utils.getPixels(this.topLevelElement.style.marginTop); 184 | bounds.width = this.topLevelElement.clientWidth; 185 | bounds.height = this.topLevelElement.clientHeight; 186 | 187 | if (handle.east) this._resizeEast(dx, bounds); 188 | if (handle.west) this._resizeWest(dx, bounds); 189 | if (handle.north) this._resizeNorth(dy, bounds); 190 | if (handle.south) this._resizeSouth(dy, bounds); 191 | }; 192 | 193 | ResizableContainer.prototype._resizeWest = function(dx, bounds) 194 | { 195 | this._resizeContainer(dx, 0, -dx, 0, bounds); 196 | }; 197 | 198 | ResizableContainer.prototype._resizeEast = function(dx, bounds) 199 | { 200 | this._resizeContainer(0, 0, dx, 0, bounds); 201 | }; 202 | 203 | ResizableContainer.prototype._resizeNorth = function(dy, bounds) 204 | { 205 | this._resizeContainer(0, dy, 0, -dy, bounds); 206 | }; 207 | 208 | ResizableContainer.prototype._resizeSouth = function(dy, bounds) 209 | { 210 | this._resizeContainer(0, 0, 0, dy, bounds); 211 | }; 212 | 213 | ResizableContainer.prototype._resizeContainer = function(leftDelta, topDelta, widthDelta, heightDelta, bounds) 214 | { 215 | bounds.left += leftDelta; 216 | bounds.top += topDelta; 217 | bounds.width += widthDelta; 218 | bounds.height += heightDelta; 219 | 220 | var minWidth = 50; // TODO: Move to external configuration 221 | var minHeight = 50; // TODO: Move to external configuration 222 | bounds.width = Math.max(bounds.width, minWidth); 223 | bounds.height = Math.max(bounds.height, minHeight); 224 | 225 | this.topLevelElement.style.marginLeft = bounds.left + 'px'; 226 | this.topLevelElement.style.marginTop = bounds.top + 'px'; 227 | 228 | this.resize(bounds.width, bounds.height); 229 | }; 230 | 231 | 232 | function ResizeHandle() 233 | { 234 | this.element = undefined; 235 | this.handleSize = 6; // TODO: Get this from DOM 236 | this.cornerSize = 12; // TODO: Get this from DOM 237 | this.east = false; 238 | this.west = false; 239 | this.north = false; 240 | this.south = false; 241 | this.corner = false; 242 | } 243 | 244 | ResizeHandle.prototype.adjustSize = function(container, clientWidth, clientHeight) 245 | { 246 | if (this.corner) 247 | { 248 | if (this.west) this.element.style.left = '0px'; 249 | if (this.east) this.element.style.left = (clientWidth - this.cornerSize) + 'px'; 250 | if (this.north) this.element.style.top = '0px'; 251 | if (this.south) this.element.style.top = (clientHeight - this.cornerSize) + 'px'; 252 | } 253 | else 254 | { 255 | if (this.west) 256 | { 257 | this.element.style.left = '0px'; 258 | this.element.style.top = this.cornerSize + 'px'; 259 | } 260 | if (this.east) { 261 | this.element.style.left = (clientWidth - this.handleSize) + 'px'; 262 | this.element.style.top = this.cornerSize + 'px'; 263 | } 264 | if (this.north) { 265 | this.element.style.left = this.cornerSize + 'px'; 266 | this.element.style.top = '0px'; 267 | } 268 | if (this.south) { 269 | this.element.style.left = this.cornerSize + 'px'; 270 | this.element.style.top = (clientHeight - this.handleSize) + 'px'; 271 | } 272 | 273 | if (this.west || this.east) { 274 | this.element.style.height = (clientHeight - this.cornerSize * 2) + 'px'; 275 | } else { 276 | this.element.style.width = (clientWidth - this.cornerSize * 2) + 'px'; 277 | } 278 | } 279 | }; 280 | -------------------------------------------------------------------------------- /src/containers/PanelContainer.js: -------------------------------------------------------------------------------- 1 | var EventHandler = require('../utils/EventHandler'), 2 | UndockInitiator = require('../utils/UndockInitiator'), 3 | utils = require('../utils/utils'); 4 | 5 | /** 6 | * This dock container wraps the specified element on a panel frame with a title bar and close button 7 | */ 8 | function PanelContainer(elementContent, dockManager, title) 9 | { 10 | if (!title) 11 | title = 'Panel'; 12 | this.elementContent = elementContent; 13 | this.dockManager = dockManager; 14 | this.title = title; 15 | this.containerType = 'panel'; 16 | this.iconName = 'fa fa-arrow-circle-right'; 17 | this.iconTemplate = null; 18 | this.minimumAllowedChildNodes = 0; 19 | this._floatingDialog = undefined; 20 | this.isDialog = false; 21 | this._canUndock = dockManager._undockEnabled; 22 | this.eventListeners = []; 23 | this._initialize(); 24 | } 25 | 26 | module.exports = PanelContainer; 27 | 28 | PanelContainer.prototype.canUndock = function(state){ 29 | this._canUndock = state; 30 | this.undockInitiator.enabled = state; 31 | this.eventListeners.forEach(function(listener) { 32 | if (listener.onDockEnabled) { 33 | listener.onDockEnabled({self: this, state: state}); 34 | } 35 | }); 36 | 37 | }; 38 | 39 | PanelContainer.prototype.addListener = function(listener){ 40 | this.eventListeners.push(listener); 41 | }; 42 | 43 | PanelContainer.prototype.removeListener = function(listener) 44 | { 45 | this.eventListeners.splice(this.eventListeners.indexOf(listener), 1); 46 | }; 47 | 48 | Object.defineProperty(PanelContainer.prototype, 'floatingDialog', { 49 | get: function() { return this._floatingDialog; }, 50 | set: function(value) 51 | { 52 | this._floatingDialog = value; 53 | var canUndock = (this._floatingDialog === undefined); 54 | this.undockInitiator.enabled = canUndock; 55 | } 56 | }); 57 | 58 | PanelContainer.loadFromState = function(state, dockManager) 59 | { 60 | var elementName = state.element; 61 | var elementContent = document.getElementById(elementName); 62 | if(elementContent === null) { 63 | return null; 64 | } 65 | var ret = new PanelContainer(elementContent, dockManager); 66 | ret.loadState(state); 67 | return ret; 68 | }; 69 | 70 | PanelContainer.prototype.saveState = function(state) 71 | { 72 | state.element = this.elementContent.id; 73 | state.width = this.width; 74 | state.height = this.height; 75 | }; 76 | 77 | PanelContainer.prototype.loadState = function(state) 78 | { 79 | this.width = state.width; 80 | this.height = state.height; 81 | this.state = {width: state.width, height: state.height}; 82 | // this.resize(this.width, this.height); 83 | }; 84 | 85 | PanelContainer.prototype.setActiveChild = function(/*child*/) 86 | { 87 | }; 88 | 89 | Object.defineProperty(PanelContainer.prototype, 'containerElement', { 90 | get: function() { return this.elementPanel; } 91 | }); 92 | 93 | PanelContainer.prototype._initialize = function() 94 | { 95 | this.name = utils.getNextId('panel_'); 96 | this.elementPanel = document.createElement('div'); 97 | this.elementTitle = document.createElement('div'); 98 | this.elementTitleText = document.createElement('div'); 99 | this.elementContentHost = document.createElement('div'); 100 | this.elementButtonClose = document.createElement('div'); 101 | 102 | this.elementPanel.appendChild(this.elementTitle); 103 | this.elementTitle.appendChild(this.elementTitleText); 104 | this.elementTitle.appendChild(this.elementButtonClose); 105 | this.elementButtonClose.innerHTML = ''; 106 | this.elementButtonClose.classList.add('panel-titlebar-button-close'); 107 | this.elementPanel.appendChild(this.elementContentHost); 108 | 109 | this.elementPanel.classList.add('panel-base'); 110 | this.elementTitle.classList.add('panel-titlebar'); 111 | this.elementTitle.classList.add('disable-selection'); 112 | this.elementTitleText.classList.add('panel-titlebar-text'); 113 | this.elementContentHost.classList.add('panel-content'); 114 | 115 | // set the size of the dialog elements based on the panel's size 116 | var panelWidth = this.elementContent.clientWidth; 117 | var panelHeight = this.elementContent.clientHeight; 118 | var titleHeight = this.elementTitle.clientHeight; 119 | this._setPanelDimensions(panelWidth, panelHeight + titleHeight); 120 | 121 | // Add the panel to the body 122 | document.body.appendChild(this.elementPanel); 123 | 124 | this.closeButtonClickedHandler = 125 | new EventHandler(this.elementButtonClose, 'click', this.onCloseButtonClicked.bind(this)); 126 | 127 | utils.removeNode(this.elementContent); 128 | this.elementContentHost.appendChild(this.elementContent); 129 | 130 | // Extract the title from the content element's attribute 131 | var contentTitle = this.elementContent.dataset.panelCaption; 132 | var contentIcon = this.elementContent.dataset.panelIcon; 133 | if (contentTitle) this.title = contentTitle; 134 | if (contentIcon) this.iconName = contentIcon; 135 | this._updateTitle(); 136 | 137 | this.undockInitiator = new UndockInitiator(this.elementTitle, this.performUndockToDialog.bind(this)); 138 | delete this.floatingDialog; 139 | }; 140 | 141 | 142 | PanelContainer.prototype.hideCloseButton = function(state){ 143 | this.elementButtonClose.style.display = state ? 'none' : 'block'; 144 | this.eventListeners.forEach(function(listener) { 145 | if (listener.onHideCloseButton) { 146 | listener.onHideCloseButton({self: this, state: state}); 147 | } 148 | }); 149 | }; 150 | 151 | 152 | PanelContainer.prototype.destroy = function() 153 | { 154 | utils.removeNode(this.elementPanel); 155 | if (this.closeButtonClickedHandler) 156 | { 157 | this.closeButtonClickedHandler.cancel(); 158 | delete this.closeButtonClickedHandler; 159 | } 160 | }; 161 | 162 | /** 163 | * Undocks the panel and and converts it to a dialog box 164 | */ 165 | PanelContainer.prototype.performUndockToDialog = function(e, dragOffset) 166 | { 167 | this.isDialog = true; 168 | this.undockInitiator.enabled = false; 169 | return this.dockManager.requestUndockToDialog(this, e, dragOffset); 170 | }; 171 | 172 | /** 173 | * Undocks the container and from the layout hierarchy 174 | * The container would be removed from the DOM 175 | */ 176 | PanelContainer.prototype.performUndock = function() 177 | { 178 | 179 | this.undockInitiator.enabled = false; 180 | this.dockManager.requestUndock(this); 181 | }; 182 | 183 | PanelContainer.prototype.prepareForDocking = function() 184 | { 185 | this.isDialog = false; 186 | this.undockInitiator.enabled = this.canUndock; 187 | }; 188 | 189 | Object.defineProperty(PanelContainer.prototype, 'width', { 190 | get: function() { return this._cachedWidth; }, 191 | set: function(value) 192 | { 193 | if (value !== this._cachedWidth) 194 | { 195 | this._cachedWidth = value; 196 | this.elementPanel.style.width = value + 'px'; 197 | } 198 | } 199 | }); 200 | 201 | Object.defineProperty(PanelContainer.prototype, 'height', { 202 | get: function() { return this._cachedHeight; }, 203 | set: function(value) 204 | { 205 | if (value !== this._cachedHeight) 206 | { 207 | this._cachedHeight = value; 208 | this.elementPanel.style.height = value + 'px'; 209 | } 210 | } 211 | }); 212 | 213 | PanelContainer.prototype.resize = function(width, height) 214 | { 215 | // if (this._cachedWidth === width && this._cachedHeight === height) 216 | // { 217 | // // Already in the desired size 218 | // return; 219 | // } 220 | this._setPanelDimensions(width, height); 221 | this._cachedWidth = width; 222 | this._cachedHeight = height; 223 | }; 224 | 225 | PanelContainer.prototype._setPanelDimensions = function(width, height) 226 | { 227 | this.elementTitle.style.width = width + 'px'; 228 | this.elementContentHost.style.width = width + 'px'; 229 | this.elementContent.style.width = width + 'px'; 230 | this.elementPanel.style.width = width + 'px'; 231 | 232 | var titleBarHeight = this.elementTitle.clientHeight; 233 | var contentHeight = height - titleBarHeight; 234 | this.elementContentHost.style.height = contentHeight + 'px'; 235 | this.elementContent.style.height = contentHeight + 'px'; 236 | this.elementPanel.style.height = height + 'px'; 237 | }; 238 | 239 | PanelContainer.prototype.setTitle = function(title) 240 | { 241 | this.title = title; 242 | this._updateTitle(); 243 | if (this.onTitleChanged) 244 | this.onTitleChanged(this, title); 245 | }; 246 | 247 | PanelContainer.prototype.setTitleIcon = function(iconName) 248 | { 249 | this.iconName = iconName; 250 | this._updateTitle(); 251 | if (this.onTitleChanged) 252 | this.onTitleChanged(this, this.title); 253 | }; 254 | 255 | PanelContainer.prototype.setTitleIconTemplate = function(iconTemplate) 256 | { 257 | this.iconTemplate = iconTemplate; 258 | this._updateTitle(); 259 | if (this.onTitleChanged) 260 | this.onTitleChanged(this, this.title); 261 | }; 262 | 263 | PanelContainer.prototype.setCloseIconTemplate = function(closeIconTemplate) 264 | { 265 | this.elementButtonClose.innerHTML = closeIconTemplate(); 266 | }; 267 | 268 | PanelContainer.prototype._updateTitle = function() 269 | { 270 | if(this.iconTemplate !== null) 271 | { 272 | this.elementTitleText.innerHTML = this.iconTemplate(this.iconName) + this.title; 273 | return; 274 | } 275 | this.elementTitleText.innerHTML = ' ' + this.title; 276 | }; 277 | 278 | PanelContainer.prototype.getRawTitle = function() 279 | { 280 | return this.elementTitleText.innerHTML; 281 | }; 282 | 283 | PanelContainer.prototype.performLayout = function(/*children*/) 284 | { 285 | }; 286 | 287 | PanelContainer.prototype.onCloseButtonClicked = function() 288 | { 289 | this.close(); 290 | }; 291 | 292 | PanelContainer.prototype.close = function() { 293 | //TODO: hide 294 | if (this.isDialog) { 295 | this.floatingDialog.hide(); 296 | 297 | this.floatingDialog.setPosition(this.dockManager.defaultDialogPosition.x, this.dockManager.defaultDialogPosition.y); 298 | } 299 | else 300 | { 301 | this.performUndockToDialog(); 302 | this.floatingDialog.hide(); 303 | this.floatingDialog.setPosition(this.dockManager.defaultDialogPosition.x, this.dockManager.defaultDialogPosition.y); 304 | } 305 | this.dockManager.notifyOnClosePanel(this); 306 | }; 307 | -------------------------------------------------------------------------------- /src/dock/DockLayoutEngine.js: -------------------------------------------------------------------------------- 1 | var DockNode = require('./DockNode'), 2 | HorizontalDockContainer = require('../containers/HorizontalDockContainer'), 3 | VerticalDockContainer = require('../containers/VerticalDockContainer'), 4 | FillDockContainer = require('../containers/FillDockContainer'), 5 | Rectangle = require('../utils/Rectangle'), 6 | utils = require('../utils/utils'); 7 | 8 | function DockLayoutEngine(dockManager) 9 | { 10 | this.dockManager = dockManager; 11 | } 12 | 13 | module.exports = DockLayoutEngine; 14 | 15 | /** docks the [newNode] to the left of [referenceNode] */ 16 | DockLayoutEngine.prototype.dockLeft = function(referenceNode, newNode) 17 | { 18 | this._performDock(referenceNode, newNode, 'horizontal', true); 19 | }; 20 | 21 | /** docks the [newNode] to the right of [referenceNode] */ 22 | DockLayoutEngine.prototype.dockRight = function(referenceNode, newNode) { 23 | this._performDock(referenceNode, newNode, 'horizontal', false); 24 | }; 25 | 26 | /** docks the [newNode] to the top of [referenceNode] */ 27 | DockLayoutEngine.prototype.dockUp = function(referenceNode, newNode) { 28 | this._performDock(referenceNode, newNode, 'vertical', true); 29 | }; 30 | 31 | /** docks the [newNode] to the bottom of [referenceNode] */ 32 | DockLayoutEngine.prototype.dockDown = function(referenceNode, newNode) { 33 | this._performDock(referenceNode, newNode, 'vertical', false); 34 | }; 35 | 36 | /** docks the [newNode] by creating a new tab inside [referenceNode] */ 37 | DockLayoutEngine.prototype.dockFill = function(referenceNode, newNode) { 38 | this._performDock(referenceNode, newNode, 'fill', false); 39 | }; 40 | 41 | DockLayoutEngine.prototype.undock = function(node) 42 | { 43 | var parentNode = node.parent; 44 | if (!parentNode) 45 | throw new Error('Cannot undock. panel is not a leaf node'); 46 | 47 | // Get the position of the node relative to it's siblings 48 | var siblingIndex = parentNode.children.indexOf(node); 49 | 50 | // Detach the node from the dock manager's tree hierarchy 51 | node.detachFromParent(); 52 | 53 | // Fix the node's parent hierarchy 54 | if (parentNode.children.length < parentNode.container.minimumAllowedChildNodes) { 55 | // If the child count falls below the minimum threshold, destroy the parent and merge 56 | // the children with their grandparents 57 | var grandParent = parentNode.parent; 58 | for (var i = 0; i < parentNode.children.length; i++) 59 | { 60 | var otherChild = parentNode.children[i]; 61 | if (grandParent) 62 | { 63 | // parent node is not a root node 64 | grandParent.addChildAfter(parentNode, otherChild); 65 | parentNode.detachFromParent(); 66 | var width =parentNode.container.containerElement.clientWidth; 67 | var height = parentNode.container.containerElement.clientHeight; 68 | parentNode.container.destroy(); 69 | 70 | otherChild.container.resize(width, height); 71 | grandParent.performLayout(); 72 | } 73 | else 74 | { 75 | // Parent is a root node. 76 | // Make the other child the root node 77 | parentNode.detachFromParent(); 78 | parentNode.container.destroy(); 79 | this.dockManager.setRootNode(otherChild); 80 | } 81 | } 82 | } 83 | else 84 | { 85 | // the node to be removed has 2 or more other siblings. So it is safe to continue 86 | // using the parent composite container. 87 | parentNode.performLayout(); 88 | 89 | // Set the next sibling as the active child (e.g. for a Tab host, it would select it as the active tab) 90 | if (parentNode.children.length > 0) 91 | { 92 | var nextActiveSibling = parentNode.children[Math.max(0, siblingIndex - 1)]; 93 | parentNode.container.setActiveChild(nextActiveSibling.container); 94 | } 95 | } 96 | this.dockManager.invalidate(); 97 | 98 | this.dockManager.notifyOnUnDock(node); 99 | }; 100 | 101 | DockLayoutEngine.prototype.reorderTabs = function(node, handle, state, index){ 102 | var N = node.children.length; 103 | var nodeIndexToDelete = state === 'left' ? index : index + 1; 104 | var indexes = Array.apply(null, {length: N}).map(Number.call, Number); 105 | var indexValue = indexes.splice(nodeIndexToDelete, 1)[0]; //remove element 106 | indexes.splice(state === 'left' ? index - 1 : index, 0, indexValue); //insert 107 | 108 | node.children = node.children.orderByIndexes(indexes); //apply 109 | node.container.tabHost.performTabsLayout(indexes); 110 | this.dockManager.notifyOnTabsReorder(node); 111 | }; 112 | 113 | DockLayoutEngine.prototype._performDock = function(referenceNode, newNode, direction, insertBeforeReference) 114 | { 115 | if (referenceNode.parent && referenceNode.parent.container.containerType === 'fill') 116 | referenceNode = referenceNode.parent; 117 | 118 | if (direction === 'fill' && referenceNode.container.containerType === 'fill') 119 | { 120 | referenceNode.addChild(newNode); 121 | referenceNode.performLayout(); 122 | referenceNode.container.setActiveChild(newNode.container); 123 | this.dockManager.invalidate(); 124 | this.dockManager.notifyOnDock(newNode); 125 | return; 126 | } 127 | 128 | // Check if reference node is root node 129 | var model = this.dockManager.context.model, 130 | compositeContainer, 131 | compositeNode, 132 | referenceParent; 133 | 134 | if (referenceNode === model.rootNode) 135 | { 136 | compositeContainer = this._createDockContainer(direction, newNode, referenceNode); 137 | compositeNode = new DockNode(compositeContainer); 138 | 139 | if (insertBeforeReference) 140 | { 141 | compositeNode.addChild(newNode); 142 | compositeNode.addChild(referenceNode); 143 | } 144 | else 145 | { 146 | compositeNode.addChild(referenceNode); 147 | compositeNode.addChild(newNode); 148 | } 149 | 150 | // Attach the root node to the dock manager's DOM 151 | this.dockManager.setRootNode(compositeNode); 152 | this.dockManager.rebuildLayout(this.dockManager.context.model.rootNode); 153 | compositeNode.container.setActiveChild(newNode.container); 154 | this.dockManager.invalidate(); 155 | this.dockManager.notifyOnDock(newNode); 156 | return; 157 | } 158 | 159 | if (referenceNode.parent.container.containerType !== direction) { 160 | referenceParent = referenceNode.parent; 161 | 162 | // Get the dimensions of the reference node, for resizing later on 163 | var referenceNodeWidth = referenceNode.container.containerElement.clientWidth; 164 | var referenceNodeHeight = referenceNode.container.containerElement.clientHeight; 165 | 166 | // Get the dimensions of the reference node, for resizing later on 167 | var referenceNodeParentWidth = referenceParent.container.containerElement.clientWidth; 168 | var referenceNodeParentHeight = referenceParent.container.containerElement.clientHeight; 169 | 170 | // Replace the reference node with a new composite node with the reference and new node as it's children 171 | compositeContainer = this._createDockContainer(direction, newNode, referenceNode); 172 | compositeNode = new DockNode(compositeContainer); 173 | 174 | referenceParent.addChildAfter(referenceNode, compositeNode); 175 | referenceNode.detachFromParent(); 176 | utils.removeNode(referenceNode.container.containerElement); 177 | 178 | if (insertBeforeReference) 179 | { 180 | compositeNode.addChild(newNode); 181 | compositeNode.addChild(referenceNode); 182 | } 183 | else 184 | { 185 | compositeNode.addChild(referenceNode); 186 | compositeNode.addChild(newNode); 187 | } 188 | 189 | referenceParent.performLayout(); 190 | compositeNode.performLayout(); 191 | 192 | compositeNode.container.setActiveChild(newNode.container); 193 | compositeNode.container.resize(referenceNodeWidth, referenceNodeHeight); 194 | referenceParent.container.resize(referenceNodeParentWidth, referenceNodeParentHeight); 195 | } 196 | else 197 | { 198 | // Add as a sibling, since the parent of the reference node is of the right composite type 199 | referenceParent = referenceNode.parent; 200 | if (insertBeforeReference) 201 | referenceParent.addChildBefore(referenceNode, newNode); 202 | else 203 | referenceParent.addChildAfter(referenceNode, newNode); 204 | referenceParent.performLayout(); 205 | referenceParent.container.setActiveChild(newNode.container); 206 | } 207 | 208 | // force resize the panel 209 | var containerWidth = newNode.container.containerElement.clientWidth; 210 | var containerHeight = newNode.container.containerElement.clientHeight; 211 | newNode.container.resize(containerWidth, containerHeight); 212 | 213 | this.dockManager.invalidate(); 214 | this.dockManager.notifyOnDock(newNode); 215 | }; 216 | 217 | DockLayoutEngine.prototype._forceResizeCompositeContainer = function(container) 218 | { 219 | var width = container.containerElement.clientWidth; 220 | var height = container.containerElement.clientHeight; 221 | container.resize(width, height); 222 | }; 223 | 224 | DockLayoutEngine.prototype._createDockContainer = function(containerType, newNode, referenceNode) 225 | { 226 | if (containerType === 'horizontal') 227 | return new HorizontalDockContainer(this.dockManager, [newNode.container, referenceNode.container]); 228 | if (containerType === 'vertical') 229 | return new VerticalDockContainer(this.dockManager, [newNode.container, referenceNode.container]); 230 | if (containerType === 'fill') 231 | return new FillDockContainer(this.dockManager); 232 | throw new Error('Failed to create dock container of type: ' + containerType); 233 | }; 234 | 235 | 236 | /** 237 | * Gets the bounds of the new node if it were to dock with the specified configuration 238 | * The state is not modified in this function. It is used for showing a preview of where 239 | * the panel would be docked when hovered over a dock wheel button 240 | */ 241 | DockLayoutEngine.prototype.getDockBounds = function(referenceNode, containerToDock, direction, insertBeforeReference) 242 | { 243 | var compositeNode; // The node that contains the splitter / fill node 244 | var childCount; 245 | var childPosition; 246 | var bounds; 247 | 248 | if (direction === 'fill') 249 | { 250 | // Since this is a fill operation, the highlight bounds is the same as the reference node 251 | // TODO: Create a tab handle highlight to show that it's going to be docked in a tab 252 | var targetElement = referenceNode.container.containerElement; 253 | bounds = new Rectangle(); 254 | bounds.x = targetElement.offsetLeft; 255 | bounds.y = targetElement.offsetTop; 256 | bounds.width = targetElement.clientWidth; 257 | bounds.height= targetElement.clientHeight; 258 | return bounds; 259 | } 260 | 261 | if (referenceNode.parent && referenceNode.parent.container.containerType === 'fill') 262 | // Ignore the fill container's child and move one level up 263 | referenceNode = referenceNode.parent; 264 | 265 | // Flag to indicate of the renference node was replaced with a new composite node with 2 children 266 | var hierarchyModified = false; 267 | if (referenceNode.parent && referenceNode.parent.container.containerType === direction) { 268 | // The parent already is of the desired composite type. Will be inserted as sibling to the reference node 269 | compositeNode = referenceNode.parent; 270 | childCount = compositeNode.children.length; 271 | childPosition = compositeNode.children.indexOf(referenceNode) + (insertBeforeReference ? 0 : 1); 272 | } else { 273 | // The reference node will be replaced with a new composite node of the desired type with 2 children 274 | compositeNode = referenceNode; 275 | childCount = 1; // The newly inserted composite node will contain the reference node 276 | childPosition = (insertBeforeReference ? 0 : 1); 277 | hierarchyModified = true; 278 | } 279 | 280 | var splitBarSize = 5; // TODO: Get from DOM 281 | var targetPanelSize = 0; 282 | var targetPanelStart = 0; 283 | if (direction === 'vertical' || direction === 'horizontal') 284 | { 285 | // Existing size of the composite container (without the splitter bars). 286 | // This will also be the final size of the composite (splitter / fill) 287 | // container after the new panel has been docked 288 | var compositeSize = this._getVaringDimension(compositeNode.container, direction) - (childCount - 1) * splitBarSize; 289 | 290 | // size of the newly added panel 291 | var newPanelOriginalSize = this._getVaringDimension(containerToDock, direction); 292 | var scaleMultiplier = compositeSize / (compositeSize + newPanelOriginalSize); 293 | 294 | // Size of the panel after it has been docked and scaled 295 | targetPanelSize = newPanelOriginalSize * scaleMultiplier; 296 | if (hierarchyModified) 297 | targetPanelStart = insertBeforeReference ? 0 : compositeSize * scaleMultiplier; 298 | else 299 | { 300 | for (var i = 0; i < childPosition; i++) 301 | targetPanelStart += this._getVaringDimension(compositeNode.children[i].container, direction); 302 | targetPanelStart *= scaleMultiplier; 303 | } 304 | } 305 | 306 | bounds = new Rectangle(); 307 | if (direction === 'vertical') 308 | { 309 | bounds.x = compositeNode.container.containerElement.offsetLeft; 310 | bounds.y = compositeNode.container.containerElement.offsetTop + targetPanelStart; 311 | bounds.width = compositeNode.container.width; 312 | bounds.height = targetPanelSize; 313 | } else if (direction === 'horizontal') { 314 | bounds.x = compositeNode.container.containerElement.offsetLeft + targetPanelStart; 315 | bounds.y = compositeNode.container.containerElement.offsetTop; 316 | bounds.width = targetPanelSize; 317 | bounds.height = compositeNode.container.height; 318 | } 319 | 320 | return bounds; 321 | }; 322 | 323 | DockLayoutEngine.prototype._getVaringDimension = function(container, direction) 324 | { 325 | if (direction === 'vertical') 326 | return container.height; 327 | if (direction === 'horizontal') 328 | return container.width; 329 | return 0; 330 | }; 331 | -------------------------------------------------------------------------------- /src/dock/DockManager.js: -------------------------------------------------------------------------------- 1 | var DockManagerContext = require('./DockManagerContext'), 2 | DockNode = require('./DockNode'), 3 | DockWheel = require('./DockWheel'), 4 | DockLayoutEngine = require('./DockLayoutEngine'), 5 | Dialog = require('../dialog/Dialog'), 6 | DockGraphSerializer = require('../serialization/DockGraphSerializer'), 7 | DockGraphDeserializer = require('../serialization/DockGraphDeserializer'), 8 | EventHandler = require('../utils/EventHandler'), 9 | Point = require('../utils/Point'), 10 | utils = require('../utils/utils'); 11 | 12 | /** 13 | * Dock manager manages all the dock panels in a hierarchy, similar to visual studio. 14 | * It owns a Html Div element inside which all panels are docked 15 | * Initially the document manager takes up the central space and acts as the root node 16 | */ 17 | 18 | function DockManager(element) 19 | { 20 | if (element === undefined) 21 | throw new Error('Invalid Dock Manager element provided'); 22 | 23 | this.element = element; 24 | this.context = this.dockWheel = this.layoutEngine = this.mouseMoveHandler = undefined; 25 | this.layoutEventListeners = []; 26 | 27 | this.defaultDialogPosition = new Point(0, 0); 28 | } 29 | 30 | module.exports = DockManager; 31 | 32 | DockManager.prototype.initialize = function() 33 | { 34 | this.context = new DockManagerContext(this); 35 | var documentNode = new DockNode(this.context.documentManagerView); 36 | this.context.model.rootNode = documentNode; 37 | this.context.model.documentManagerNode = documentNode; 38 | this.context.model.dialogs = []; 39 | this.setRootNode(this.context.model.rootNode); 40 | // Resize the layout 41 | this.resize(this.element.clientWidth, this.element.clientHeight); 42 | this.dockWheel = new DockWheel(this); 43 | this.layoutEngine = new DockLayoutEngine(this); 44 | this._undockEnabled = true; 45 | this.rebuildLayout(this.context.model.rootNode); 46 | }; 47 | 48 | DockManager.prototype.checkXBounds = function(container, currentMousePosition, previousMousePosition){ 49 | var dx = Math.floor(currentMousePosition.x - previousMousePosition.x); 50 | var leftBounds = currentMousePosition.x + dx < 0 || (container.offsetLeft + container.offsetWidth + dx - 40 ) < 0; 51 | var rightBounds = 52 | currentMousePosition.x + dx > this.element.offsetWidth || 53 | (container.offsetLeft + dx + 40) > this.element.offsetWidth; 54 | 55 | if (leftBounds || rightBounds) 56 | { 57 | previousMousePosition.x = currentMousePosition.x; 58 | dx = 0; 59 | } 60 | 61 | return dx; 62 | }; 63 | 64 | DockManager.prototype.checkYBounds = function(container, currentMousePosition, previousMousePosition){ 65 | var dy = Math.floor(currentMousePosition.y - previousMousePosition.y); 66 | var topBounds = container.offsetTop + dy < this.element.offsetTop; 67 | var bottomBounds = 68 | currentMousePosition.y + dy > this.element.offsetHeight || 69 | (container.offsetTop + dy > this.element.offsetHeight + this.element.offsetTop - 20); 70 | 71 | if (topBounds || bottomBounds) 72 | { 73 | previousMousePosition.y = currentMousePosition.y; 74 | dy = 0; 75 | } 76 | 77 | return dy; 78 | }; 79 | 80 | DockManager.prototype.rebuildLayout = function(node) 81 | { 82 | var self = this; 83 | node.children.forEach(function(child) { 84 | self.rebuildLayout(child); 85 | }); 86 | node.performLayout(); 87 | 88 | }; 89 | 90 | DockManager.prototype.invalidate = function() 91 | { 92 | this.resize(this.element.clientWidth, this.element.clientHeight); 93 | }; 94 | 95 | DockManager.prototype.resize = function(width, height) 96 | { 97 | this.element.style.width = width + 'px'; 98 | this.element.style.height = height + 'px'; 99 | this.context.model.rootNode.container.resize(width, height); 100 | }; 101 | 102 | /** 103 | * Reset the dock model . This happens when the state is loaded from json 104 | */ 105 | DockManager.prototype.setModel = function(model) 106 | { 107 | utils.removeNode(this.context.documentManagerView.containerElement); 108 | this.context.model = model; 109 | this.setRootNode(model.rootNode); 110 | 111 | this.rebuildLayout(model.rootNode); 112 | this.loadResize(model.rootNode); 113 | // this.invalidate(); 114 | }; 115 | 116 | DockManager.prototype.loadResize = function(node) 117 | { 118 | var self = this; 119 | node.children.reverse().forEach(function(child) { 120 | self.loadResize(child); 121 | node.container.setActiveChild(child.container); 122 | }); 123 | node.children.reverse(); 124 | node.container.resize(node.container.state.width, node.container.state.height); 125 | 126 | // node.performLayout(); 127 | }; 128 | 129 | DockManager.prototype.setRootNode = function(node) 130 | { 131 | // if (this.context.model.rootNode) 132 | // { 133 | // // detach it from the dock manager's base element 134 | // context.model.rootNode.detachFromParent(); 135 | // } 136 | 137 | // Attach the new node to the dock manager's base element and set as root node 138 | node.detachFromParent(); 139 | this.context.model.rootNode = node; 140 | this.element.appendChild(node.container.containerElement); 141 | }; 142 | 143 | 144 | DockManager.prototype.onDialogDragStarted = function(sender, e) 145 | { 146 | this.dockWheel.activeNode = this._findNodeOnPoint(e.pageX, e.pageY); 147 | this.dockWheel.activeDialog = sender; 148 | this.dockWheel.showWheel(); 149 | if (this.mouseMoveHandler) 150 | { 151 | this.mouseMoveHandler.cancel(); 152 | delete this.mouseMoveHandler; 153 | } 154 | this.mouseMoveHandler = new EventHandler(window, 'mousemove', this.onMouseMoved.bind(this)); 155 | }; 156 | 157 | DockManager.prototype.onDialogDragEnded = function(sender) 158 | { 159 | if (this.mouseMoveHandler) 160 | { 161 | this.mouseMoveHandler.cancel(); 162 | delete this.mouseMoveHandler; 163 | } 164 | this.dockWheel.onDialogDropped(sender); 165 | this.dockWheel.hideWheel(); 166 | delete this.dockWheel.activeDialog; 167 | //TODO: not so good 168 | sender.saveState(sender.elementDialog.offsetLeft, sender.elementDialog.offsetTop); 169 | }; 170 | 171 | DockManager.prototype.onMouseMoved = function(e) 172 | { 173 | this.dockWheel.activeNode = this._findNodeOnPoint(e.clientX, e.clientY); 174 | }; 175 | 176 | /** 177 | * Perform a DFS on the dock model's tree to find the 178 | * deepest level panel (i.e. the top-most non-overlapping panel) 179 | * that is under the mouse cursor 180 | * Retuns null if no node is found under this point 181 | */ 182 | DockManager.prototype._findNodeOnPoint = function(x, y) 183 | { 184 | var stack = []; 185 | stack.push(this.context.model.rootNode); 186 | var bestMatch; 187 | 188 | while (stack.length > 0) 189 | { 190 | var topNode = stack.pop(); 191 | 192 | if (utils.isPointInsideNode(x, y, topNode)) 193 | { 194 | // This node contains the point. 195 | bestMatch = topNode; 196 | 197 | // Keep looking future down 198 | [].push.apply(stack, topNode.children); 199 | } 200 | } 201 | return bestMatch; 202 | }; 203 | 204 | /** Dock the [dialog] to the left of the [referenceNode] node */ 205 | DockManager.prototype.dockDialogLeft = function(referenceNode, dialog) 206 | { 207 | return this._requestDockDialog(referenceNode, dialog, this.layoutEngine.dockLeft.bind(this.layoutEngine)); 208 | }; 209 | 210 | /** Dock the [dialog] to the right of the [referenceNode] node */ 211 | DockManager.prototype.dockDialogRight = function(referenceNode, dialog) 212 | { 213 | return this._requestDockDialog(referenceNode, dialog, this.layoutEngine.dockRight.bind(this.layoutEngine)); 214 | }; 215 | 216 | /** Dock the [dialog] above the [referenceNode] node */ 217 | DockManager.prototype.dockDialogUp = function(referenceNode, dialog) 218 | { 219 | return this._requestDockDialog(referenceNode, dialog, this.layoutEngine.dockUp.bind(this.layoutEngine)); 220 | }; 221 | 222 | /** Dock the [dialog] below the [referenceNode] node */ 223 | DockManager.prototype.dockDialogDown = function(referenceNode, dialog) 224 | { 225 | return this._requestDockDialog(referenceNode, dialog, this.layoutEngine.dockDown.bind(this.layoutEngine)); 226 | }; 227 | 228 | /** Dock the [dialog] as a tab inside the [referenceNode] node */ 229 | DockManager.prototype.dockDialogFill = function(referenceNode, dialog) 230 | { 231 | return this._requestDockDialog(referenceNode, dialog, this.layoutEngine.dockFill.bind(this.layoutEngine)); 232 | }; 233 | 234 | /** Dock the [container] to the left of the [referenceNode] node */ 235 | DockManager.prototype.dockLeft = function(referenceNode, container, ratio) 236 | { 237 | return this._requestDockContainer(referenceNode, container, this.layoutEngine.dockLeft.bind(this.layoutEngine), ratio); 238 | }; 239 | 240 | /** Dock the [container] to the right of the [referenceNode] node */ 241 | DockManager.prototype.dockRight = function(referenceNode, container, ratio) 242 | { 243 | return this._requestDockContainer(referenceNode, container, this.layoutEngine.dockRight.bind(this.layoutEngine), ratio); 244 | }; 245 | 246 | /** Dock the [container] above the [referenceNode] node */ 247 | DockManager.prototype.dockUp = function(referenceNode, container, ratio) 248 | { 249 | return this._requestDockContainer(referenceNode, container, this.layoutEngine.dockUp.bind(this.layoutEngine), ratio); 250 | }; 251 | 252 | /** Dock the [container] below the [referenceNode] node */ 253 | DockManager.prototype.dockDown = function(referenceNode, container, ratio) 254 | { 255 | return this._requestDockContainer(referenceNode, container, this.layoutEngine.dockDown.bind(this.layoutEngine), ratio); 256 | }; 257 | 258 | /** Dock the [container] as a tab inside the [referenceNode] node */ 259 | DockManager.prototype.dockFill = function(referenceNode, container) 260 | { 261 | return this._requestDockContainer(referenceNode, container, this.layoutEngine.dockFill.bind(this.layoutEngine)); 262 | }; 263 | DockManager.prototype.floatDialog = function(container, x, y) 264 | { 265 | var panel = container; 266 | utils.removeNode(panel.elementPanel); 267 | panel.isDialog = true; 268 | var dialog = new Dialog(panel, this); 269 | dialog.setPosition(x, y); 270 | return dialog; 271 | }; 272 | 273 | DockManager.prototype._requestDockDialog = function(referenceNode, dialog, layoutDockFunction) 274 | { 275 | // Get the active dialog that was dragged on to the dock wheel 276 | var panel = dialog.panel; 277 | var newNode = new DockNode(panel); 278 | panel.prepareForDocking(); 279 | dialog.destroy(); 280 | layoutDockFunction(referenceNode, newNode); 281 | // this.invalidate(); 282 | return newNode; 283 | }; 284 | 285 | DockManager.prototype._requestDockContainer = function(referenceNode, container, layoutDockFunction, ratio) 286 | { 287 | // Get the active dialog that was dragged on to the dock wheel 288 | var newNode = new DockNode(container); 289 | if (container.containerType === 'panel') 290 | { 291 | var panel = container; 292 | panel.prepareForDocking(); 293 | utils.removeNode(panel.elementPanel); 294 | } 295 | layoutDockFunction(referenceNode, newNode); 296 | 297 | if (ratio && newNode.parent && 298 | (newNode.parent.container.containerType === 'vertical' || newNode.parent.container.containerType === 'horizontal')) 299 | { 300 | var splitter = newNode.parent.container; 301 | splitter.setContainerRatio(container, ratio); 302 | } 303 | 304 | this.rebuildLayout(this.context.model.rootNode); 305 | this.invalidate(); 306 | return newNode; 307 | }; 308 | 309 | DockManager.prototype._requestTabReorder = function(container, e){ 310 | var node = this._findNodeFromContainer(container); 311 | this.layoutEngine.reorderTabs(node, e.handle, e.state, e.index); 312 | }; 313 | 314 | /** 315 | * Undocks a panel and converts it into a floating dialog window 316 | * It is assumed that only leaf nodes (panels) can be undocked 317 | */ 318 | DockManager.prototype.requestUndockToDialog = function(container, event, dragOffset) 319 | { 320 | var node = this._findNodeFromContainer(container); 321 | this.layoutEngine.undock(node); 322 | 323 | // Create a new dialog window for the undocked panel 324 | var dialog = new Dialog(node.container, this); 325 | 326 | if(event !== undefined){ 327 | // Adjust the relative position 328 | var dialogWidth = dialog.elementDialog.clientWidth; 329 | if (dragOffset.x > dialogWidth) 330 | dragOffset.x = 0.75 * dialogWidth; 331 | dialog.setPosition( 332 | event.clientX - dragOffset.x, 333 | event.clientY - dragOffset.y); 334 | dialog.draggable.onMouseDown(event); 335 | } 336 | return dialog; 337 | }; 338 | 339 | /** Undocks a panel and converts it into a floating dialog window 340 | * It is assumed that only leaf nodes (panels) can be undocked 341 | */ 342 | DockManager.prototype.requestUndock = function(container) 343 | { 344 | var node = this._findNodeFromContainer(container); 345 | this.layoutEngine.undock(node); 346 | }; 347 | 348 | /** 349 | * Removes a dock container from the dock layout hierarcy 350 | * Returns the node that was removed from the dock tree 351 | */ 352 | DockManager.prototype.requestRemove = function(container) 353 | { 354 | var node = this._findNodeFromContainer(container); 355 | var parent = node.parent; 356 | node.detachFromParent(); 357 | if (parent) 358 | this.rebuildLayout(parent); 359 | return node; 360 | }; 361 | 362 | /** Finds the node that owns the specified [container] */ 363 | DockManager.prototype._findNodeFromContainer = function(container) 364 | { 365 | //this.context.model.rootNode.debugDumpTree(); 366 | 367 | var stack = []; 368 | stack.push(this.context.model.rootNode); 369 | 370 | while (stack.length > 0) 371 | { 372 | var topNode = stack.pop(); 373 | 374 | if (topNode.container === container) 375 | return topNode; 376 | [].push.apply(stack, topNode.children); 377 | } 378 | 379 | throw new Error('Cannot find dock node belonging to the element'); 380 | }; 381 | 382 | DockManager.prototype.findNodeFromContainerElement = function(containerElm) { 383 | //this.context.model.rootNode.debugDumpTree(); 384 | 385 | var stack = []; 386 | stack.push(this.context.model.rootNode); 387 | 388 | while (stack.length > 0) 389 | { 390 | var topNode = stack.pop(); 391 | 392 | if (topNode.container.containerElement === containerElm) 393 | return topNode; 394 | [].push.apply(stack, topNode.children); 395 | } 396 | 397 | throw new Error('Cannot find dock node belonging to the element'); 398 | }; 399 | 400 | DockManager.prototype.addLayoutListener = function(listener) 401 | { 402 | this.layoutEventListeners.push(listener); 403 | }; 404 | 405 | DockManager.prototype.removeLayoutListener = function(listener) 406 | { 407 | this.layoutEventListeners.splice(this.layoutEventListeners.indexOf(listener), 1); 408 | }; 409 | 410 | DockManager.prototype.suspendLayout = function() 411 | { 412 | var self = this; 413 | this.layoutEventListeners.forEach(function(listener) { 414 | if (listener.onSuspendLayout) listener.onSuspendLayout(self); 415 | }); 416 | }; 417 | 418 | DockManager.prototype.resumeLayout = function(panel) 419 | { 420 | var self = this; 421 | this.layoutEventListeners.forEach(function(listener) { 422 | if (listener.onResumeLayout) listener.onResumeLayout(self, panel); 423 | }); 424 | }; 425 | 426 | DockManager.prototype.notifyOnDock = function(dockNode) 427 | { 428 | var self = this; 429 | this.layoutEventListeners.forEach(function(listener) { 430 | if (listener.onDock) { 431 | listener.onDock(self, dockNode); 432 | } 433 | }); 434 | }; 435 | 436 | DockManager.prototype.notifyOnTabsReorder = function(dockNode) 437 | { 438 | var self = this; 439 | this.layoutEventListeners.forEach(function(listener) { 440 | if (listener.onTabsReorder) { 441 | listener.onTabsReorder(self, dockNode); 442 | } 443 | }); 444 | }; 445 | 446 | 447 | DockManager.prototype.notifyOnUnDock = function(dockNode) 448 | { 449 | var self = this; 450 | this.layoutEventListeners.forEach(function(listener) { 451 | if (listener.onUndock) { 452 | listener.onUndock(self, dockNode); 453 | } 454 | }); 455 | }; 456 | 457 | DockManager.prototype.notifyOnClosePanel = function(panel) 458 | { 459 | var self = this; 460 | this.layoutEventListeners.forEach(function(listener) { 461 | if (listener.onClosePanel) { 462 | listener.onClosePanel(self, panel); 463 | } 464 | }); 465 | }; 466 | 467 | 468 | DockManager.prototype.notifyOnCreateDialog = function(dialog) 469 | { 470 | var self = this; 471 | this.layoutEventListeners.forEach(function(listener) { 472 | if (listener.onCreateDialog) { 473 | listener.onCreateDialog(self, dialog); 474 | } 475 | }); 476 | }; 477 | 478 | DockManager.prototype.notifyOnHideDialog = function(dialog) 479 | { 480 | var self = this; 481 | this.layoutEventListeners.forEach(function(listener) { 482 | if (listener.onHideDialog) { 483 | listener.onHideDialog(self, dialog); 484 | } 485 | }); 486 | }; 487 | 488 | 489 | DockManager.prototype.notifyOnShowDialog = function(dialog) 490 | { 491 | var self = this; 492 | this.layoutEventListeners.forEach(function(listener) { 493 | if (listener.onShowDialog) { 494 | listener.onShowDialog(self, dialog); 495 | } 496 | }); 497 | }; 498 | 499 | 500 | DockManager.prototype.notifyOnChangeDialogPosition = function(dialog, x, y) 501 | { 502 | var self = this; 503 | this.layoutEventListeners.forEach(function(listener) { 504 | if (listener.onChangeDialogPosition) { 505 | listener.onChangeDialogPosition(self, dialog, x, y); 506 | } 507 | }); 508 | }; 509 | 510 | DockManager.prototype.notifyOnTabChange = function(tabpage) 511 | { 512 | var self = this; 513 | this.layoutEventListeners.forEach(function(listener) { 514 | if (listener.onTabChanged) { 515 | listener.onTabChanged(self, tabpage); 516 | } 517 | }); 518 | }; 519 | 520 | DockManager.prototype.saveState = function() 521 | { 522 | var serializer = new DockGraphSerializer(); 523 | return serializer.serialize(this.context.model); 524 | }; 525 | 526 | DockManager.prototype.loadState = function(json) 527 | { 528 | var deserializer = new DockGraphDeserializer(this); 529 | this.context.model = deserializer.deserialize(json); 530 | this.setModel(this.context.model); 531 | }; 532 | 533 | DockManager.prototype.getPanels = function() 534 | { 535 | var panels = []; 536 | //all visible nodes 537 | this._allPanels(this.context.model.rootNode, panels); 538 | 539 | //all visible or not dialogs 540 | this.context.model.dialogs.forEach(function(dialog) { 541 | //TODO: check visible 542 | panels.push(dialog.panel); 543 | }); 544 | 545 | return panels; 546 | }; 547 | 548 | DockManager.prototype.undockEnabled = function(state) 549 | { 550 | this._undockEnabled = state; 551 | this.getPanels().forEach(function(panel){ 552 | panel.canUndock(state); 553 | }); 554 | }; 555 | 556 | DockManager.prototype.lockDockState = function(state) 557 | { 558 | this.undockEnabled(!state); // false - not enabled 559 | this.hideCloseButton(state); //true - hide 560 | }; 561 | 562 | DockManager.prototype.hideCloseButton = function(state) 563 | { 564 | this.getPanels().forEach(function(panel){ 565 | panel.hideCloseButton(state); 566 | }); 567 | }; 568 | 569 | DockManager.prototype.updatePanels = function(ids) 570 | { 571 | var panels = []; 572 | //all visible nodes 573 | this._allPanels(this.context.model.rootNode, panels); 574 | //only remove 575 | panels.forEach(function(panel) { 576 | if(!ids.contains(panel.elementContent.id)){ 577 | panel.close(); 578 | } 579 | }); 580 | 581 | this.context.model.dialogs.forEach(function(dialog) { 582 | if(ids.contains(dialog.panel.elementContent.id)){ 583 | dialog.show(); 584 | } 585 | else{ 586 | dialog.hide(); 587 | } 588 | }); 589 | return panels; 590 | }; 591 | 592 | DockManager.prototype.getVisiblePanels = function() 593 | { 594 | var panels = []; 595 | //all visible nodes 596 | this._allPanels(this.context.model.rootNode, panels); 597 | 598 | //all visible 599 | this.context.model.dialogs.forEach(function(dialog) { 600 | if(!dialog.isHidden){ 601 | panels.push(dialog.panel); 602 | } 603 | }); 604 | 605 | return panels; 606 | }; 607 | 608 | DockManager.prototype._allPanels = function(node, panels) 609 | { 610 | var self = this; 611 | node.children.forEach(function(child) { 612 | self._allPanels(child, panels); 613 | }); 614 | if (node.container.containerType === 'panel'){ 615 | panels.push(node.container); 616 | } 617 | }; 618 | 619 | DockManager.prototype.setCloseTabIconTemplate = function(template){ 620 | this.closeTabIconTemplate = template; 621 | }; 622 | 623 | //typedef void LayoutEngineDockFunction(DockNode referenceNode, DockNode newNode); 624 | 625 | /** 626 | * The Dock Manager notifies the listeners of layout changes so client containers that have 627 | * costly layout structures can detach and reattach themself to avoid reflow 628 | */ 629 | //abstract class LayoutEventListener { 630 | //void onSuspendLayout(DockManager dockManager); 631 | //void onResumeLayout(DockManager dockManager); 632 | //} 633 | -------------------------------------------------------------------------------- /dist/css/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | /* FONT PATH 6 | * -------------------------- */ 7 | @font-face { 8 | font-family: 'FontAwesome'; 9 | src: url('../fonts/fontawesome-webfont.eot?v=4.2.0'); 10 | src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | .fa { 15 | display: inline-block; 16 | font: normal normal normal 14px/1 FontAwesome; 17 | font-size: inherit; 18 | text-rendering: auto; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | /* makes the font 33% larger relative to the icon container */ 23 | .fa-lg { 24 | font-size: 1.33333333em; 25 | line-height: 0.75em; 26 | vertical-align: -15%; 27 | } 28 | .fa-2x { 29 | font-size: 2em; 30 | } 31 | .fa-3x { 32 | font-size: 3em; 33 | } 34 | .fa-4x { 35 | font-size: 4em; 36 | } 37 | .fa-5x { 38 | font-size: 5em; 39 | } 40 | .fa-fw { 41 | width: 1.28571429em; 42 | text-align: center; 43 | } 44 | .fa-ul { 45 | padding-left: 0; 46 | margin-left: 2.14285714em; 47 | list-style-type: none; 48 | } 49 | .fa-ul > li { 50 | position: relative; 51 | } 52 | .fa-li { 53 | position: absolute; 54 | left: -2.14285714em; 55 | width: 2.14285714em; 56 | top: 0.14285714em; 57 | text-align: center; 58 | } 59 | .fa-li.fa-lg { 60 | left: -1.85714286em; 61 | } 62 | .fa-border { 63 | padding: .2em .25em .15em; 64 | border: solid 0.08em #eeeeee; 65 | border-radius: .1em; 66 | } 67 | .pull-right { 68 | float: right; 69 | } 70 | .pull-left { 71 | float: left; 72 | } 73 | .fa.pull-left { 74 | margin-right: .3em; 75 | } 76 | .fa.pull-right { 77 | margin-left: .3em; 78 | } 79 | .fa-spin { 80 | -webkit-animation: fa-spin 2s infinite linear; 81 | animation: fa-spin 2s infinite linear; 82 | } 83 | @-webkit-keyframes fa-spin { 84 | 0% { 85 | -webkit-transform: rotate(0deg); 86 | transform: rotate(0deg); 87 | } 88 | 100% { 89 | -webkit-transform: rotate(359deg); 90 | transform: rotate(359deg); 91 | } 92 | } 93 | @keyframes fa-spin { 94 | 0% { 95 | -webkit-transform: rotate(0deg); 96 | transform: rotate(0deg); 97 | } 98 | 100% { 99 | -webkit-transform: rotate(359deg); 100 | transform: rotate(359deg); 101 | } 102 | } 103 | .fa-rotate-90 { 104 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 105 | -webkit-transform: rotate(90deg); 106 | -ms-transform: rotate(90deg); 107 | transform: rotate(90deg); 108 | } 109 | .fa-rotate-180 { 110 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 111 | -webkit-transform: rotate(180deg); 112 | -ms-transform: rotate(180deg); 113 | transform: rotate(180deg); 114 | } 115 | .fa-rotate-270 { 116 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 117 | -webkit-transform: rotate(270deg); 118 | -ms-transform: rotate(270deg); 119 | transform: rotate(270deg); 120 | } 121 | .fa-flip-horizontal { 122 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); 123 | -webkit-transform: scale(-1, 1); 124 | -ms-transform: scale(-1, 1); 125 | transform: scale(-1, 1); 126 | } 127 | .fa-flip-vertical { 128 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); 129 | -webkit-transform: scale(1, -1); 130 | -ms-transform: scale(1, -1); 131 | transform: scale(1, -1); 132 | } 133 | :root .fa-rotate-90, 134 | :root .fa-rotate-180, 135 | :root .fa-rotate-270, 136 | :root .fa-flip-horizontal, 137 | :root .fa-flip-vertical { 138 | filter: none; 139 | } 140 | .fa-stack { 141 | position: relative; 142 | display: inline-block; 143 | width: 2em; 144 | height: 2em; 145 | line-height: 2em; 146 | vertical-align: middle; 147 | } 148 | .fa-stack-1x, 149 | .fa-stack-2x { 150 | position: absolute; 151 | left: 0; 152 | width: 100%; 153 | text-align: center; 154 | } 155 | .fa-stack-1x { 156 | line-height: inherit; 157 | } 158 | .fa-stack-2x { 159 | font-size: 2em; 160 | } 161 | .fa-inverse { 162 | color: #ffffff; 163 | } 164 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 165 | readers do not read off random characters that represent icons */ 166 | .fa-glass:before { 167 | content: "\f000"; 168 | } 169 | .fa-music:before { 170 | content: "\f001"; 171 | } 172 | .fa-search:before { 173 | content: "\f002"; 174 | } 175 | .fa-envelope-o:before { 176 | content: "\f003"; 177 | } 178 | .fa-heart:before { 179 | content: "\f004"; 180 | } 181 | .fa-star:before { 182 | content: "\f005"; 183 | } 184 | .fa-star-o:before { 185 | content: "\f006"; 186 | } 187 | .fa-user:before { 188 | content: "\f007"; 189 | } 190 | .fa-film:before { 191 | content: "\f008"; 192 | } 193 | .fa-th-large:before { 194 | content: "\f009"; 195 | } 196 | .fa-th:before { 197 | content: "\f00a"; 198 | } 199 | .fa-th-list:before { 200 | content: "\f00b"; 201 | } 202 | .fa-check:before { 203 | content: "\f00c"; 204 | } 205 | .fa-remove:before, 206 | .fa-close:before, 207 | .fa-times:before { 208 | content: "\f00d"; 209 | } 210 | .fa-search-plus:before { 211 | content: "\f00e"; 212 | } 213 | .fa-search-minus:before { 214 | content: "\f010"; 215 | } 216 | .fa-power-off:before { 217 | content: "\f011"; 218 | } 219 | .fa-signal:before { 220 | content: "\f012"; 221 | } 222 | .fa-gear:before, 223 | .fa-cog:before { 224 | content: "\f013"; 225 | } 226 | .fa-trash-o:before { 227 | content: "\f014"; 228 | } 229 | .fa-home:before { 230 | content: "\f015"; 231 | } 232 | .fa-file-o:before { 233 | content: "\f016"; 234 | } 235 | .fa-clock-o:before { 236 | content: "\f017"; 237 | } 238 | .fa-road:before { 239 | content: "\f018"; 240 | } 241 | .fa-download:before { 242 | content: "\f019"; 243 | } 244 | .fa-arrow-circle-o-down:before { 245 | content: "\f01a"; 246 | } 247 | .fa-arrow-circle-o-up:before { 248 | content: "\f01b"; 249 | } 250 | .fa-inbox:before { 251 | content: "\f01c"; 252 | } 253 | .fa-play-circle-o:before { 254 | content: "\f01d"; 255 | } 256 | .fa-rotate-right:before, 257 | .fa-repeat:before { 258 | content: "\f01e"; 259 | } 260 | .fa-refresh:before { 261 | content: "\f021"; 262 | } 263 | .fa-list-alt:before { 264 | content: "\f022"; 265 | } 266 | .fa-lock:before { 267 | content: "\f023"; 268 | } 269 | .fa-flag:before { 270 | content: "\f024"; 271 | } 272 | .fa-headphones:before { 273 | content: "\f025"; 274 | } 275 | .fa-volume-off:before { 276 | content: "\f026"; 277 | } 278 | .fa-volume-down:before { 279 | content: "\f027"; 280 | } 281 | .fa-volume-up:before { 282 | content: "\f028"; 283 | } 284 | .fa-qrcode:before { 285 | content: "\f029"; 286 | } 287 | .fa-barcode:before { 288 | content: "\f02a"; 289 | } 290 | .fa-tag:before { 291 | content: "\f02b"; 292 | } 293 | .fa-tags:before { 294 | content: "\f02c"; 295 | } 296 | .fa-book:before { 297 | content: "\f02d"; 298 | } 299 | .fa-bookmark:before { 300 | content: "\f02e"; 301 | } 302 | .fa-print:before { 303 | content: "\f02f"; 304 | } 305 | .fa-camera:before { 306 | content: "\f030"; 307 | } 308 | .fa-font:before { 309 | content: "\f031"; 310 | } 311 | .fa-bold:before { 312 | content: "\f032"; 313 | } 314 | .fa-italic:before { 315 | content: "\f033"; 316 | } 317 | .fa-text-height:before { 318 | content: "\f034"; 319 | } 320 | .fa-text-width:before { 321 | content: "\f035"; 322 | } 323 | .fa-align-left:before { 324 | content: "\f036"; 325 | } 326 | .fa-align-center:before { 327 | content: "\f037"; 328 | } 329 | .fa-align-right:before { 330 | content: "\f038"; 331 | } 332 | .fa-align-justify:before { 333 | content: "\f039"; 334 | } 335 | .fa-list:before { 336 | content: "\f03a"; 337 | } 338 | .fa-dedent:before, 339 | .fa-outdent:before { 340 | content: "\f03b"; 341 | } 342 | .fa-indent:before { 343 | content: "\f03c"; 344 | } 345 | .fa-video-camera:before { 346 | content: "\f03d"; 347 | } 348 | .fa-photo:before, 349 | .fa-image:before, 350 | .fa-picture-o:before { 351 | content: "\f03e"; 352 | } 353 | .fa-pencil:before { 354 | content: "\f040"; 355 | } 356 | .fa-map-marker:before { 357 | content: "\f041"; 358 | } 359 | .fa-adjust:before { 360 | content: "\f042"; 361 | } 362 | .fa-tint:before { 363 | content: "\f043"; 364 | } 365 | .fa-edit:before, 366 | .fa-pencil-square-o:before { 367 | content: "\f044"; 368 | } 369 | .fa-share-square-o:before { 370 | content: "\f045"; 371 | } 372 | .fa-check-square-o:before { 373 | content: "\f046"; 374 | } 375 | .fa-arrows:before { 376 | content: "\f047"; 377 | } 378 | .fa-step-backward:before { 379 | content: "\f048"; 380 | } 381 | .fa-fast-backward:before { 382 | content: "\f049"; 383 | } 384 | .fa-backward:before { 385 | content: "\f04a"; 386 | } 387 | .fa-play:before { 388 | content: "\f04b"; 389 | } 390 | .fa-pause:before { 391 | content: "\f04c"; 392 | } 393 | .fa-stop:before { 394 | content: "\f04d"; 395 | } 396 | .fa-forward:before { 397 | content: "\f04e"; 398 | } 399 | .fa-fast-forward:before { 400 | content: "\f050"; 401 | } 402 | .fa-step-forward:before { 403 | content: "\f051"; 404 | } 405 | .fa-eject:before { 406 | content: "\f052"; 407 | } 408 | .fa-chevron-left:before { 409 | content: "\f053"; 410 | } 411 | .fa-chevron-right:before { 412 | content: "\f054"; 413 | } 414 | .fa-plus-circle:before { 415 | content: "\f055"; 416 | } 417 | .fa-minus-circle:before { 418 | content: "\f056"; 419 | } 420 | .fa-times-circle:before { 421 | content: "\f057"; 422 | } 423 | .fa-check-circle:before { 424 | content: "\f058"; 425 | } 426 | .fa-question-circle:before { 427 | content: "\f059"; 428 | } 429 | .fa-info-circle:before { 430 | content: "\f05a"; 431 | } 432 | .fa-crosshairs:before { 433 | content: "\f05b"; 434 | } 435 | .fa-times-circle-o:before { 436 | content: "\f05c"; 437 | } 438 | .fa-check-circle-o:before { 439 | content: "\f05d"; 440 | } 441 | .fa-ban:before { 442 | content: "\f05e"; 443 | } 444 | .fa-arrow-left:before { 445 | content: "\f060"; 446 | } 447 | .fa-arrow-right:before { 448 | content: "\f061"; 449 | } 450 | .fa-arrow-up:before { 451 | content: "\f062"; 452 | } 453 | .fa-arrow-down:before { 454 | content: "\f063"; 455 | } 456 | .fa-mail-forward:before, 457 | .fa-share:before { 458 | content: "\f064"; 459 | } 460 | .fa-expand:before { 461 | content: "\f065"; 462 | } 463 | .fa-compress:before { 464 | content: "\f066"; 465 | } 466 | .fa-plus:before { 467 | content: "\f067"; 468 | } 469 | .fa-minus:before { 470 | content: "\f068"; 471 | } 472 | .fa-asterisk:before { 473 | content: "\f069"; 474 | } 475 | .fa-exclamation-circle:before { 476 | content: "\f06a"; 477 | } 478 | .fa-gift:before { 479 | content: "\f06b"; 480 | } 481 | .fa-leaf:before { 482 | content: "\f06c"; 483 | } 484 | .fa-fire:before { 485 | content: "\f06d"; 486 | } 487 | .fa-eye:before { 488 | content: "\f06e"; 489 | } 490 | .fa-eye-slash:before { 491 | content: "\f070"; 492 | } 493 | .fa-warning:before, 494 | .fa-exclamation-triangle:before { 495 | content: "\f071"; 496 | } 497 | .fa-plane:before { 498 | content: "\f072"; 499 | } 500 | .fa-calendar:before { 501 | content: "\f073"; 502 | } 503 | .fa-random:before { 504 | content: "\f074"; 505 | } 506 | .fa-comment:before { 507 | content: "\f075"; 508 | } 509 | .fa-magnet:before { 510 | content: "\f076"; 511 | } 512 | .fa-chevron-up:before { 513 | content: "\f077"; 514 | } 515 | .fa-chevron-down:before { 516 | content: "\f078"; 517 | } 518 | .fa-retweet:before { 519 | content: "\f079"; 520 | } 521 | .fa-shopping-cart:before { 522 | content: "\f07a"; 523 | } 524 | .fa-folder:before { 525 | content: "\f07b"; 526 | } 527 | .fa-folder-open:before { 528 | content: "\f07c"; 529 | } 530 | .fa-arrows-v:before { 531 | content: "\f07d"; 532 | } 533 | .fa-arrows-h:before { 534 | content: "\f07e"; 535 | } 536 | .fa-bar-chart-o:before, 537 | .fa-bar-chart:before { 538 | content: "\f080"; 539 | } 540 | .fa-twitter-square:before { 541 | content: "\f081"; 542 | } 543 | .fa-facebook-square:before { 544 | content: "\f082"; 545 | } 546 | .fa-camera-retro:before { 547 | content: "\f083"; 548 | } 549 | .fa-key:before { 550 | content: "\f084"; 551 | } 552 | .fa-gears:before, 553 | .fa-cogs:before { 554 | content: "\f085"; 555 | } 556 | .fa-comments:before { 557 | content: "\f086"; 558 | } 559 | .fa-thumbs-o-up:before { 560 | content: "\f087"; 561 | } 562 | .fa-thumbs-o-down:before { 563 | content: "\f088"; 564 | } 565 | .fa-star-half:before { 566 | content: "\f089"; 567 | } 568 | .fa-heart-o:before { 569 | content: "\f08a"; 570 | } 571 | .fa-sign-out:before { 572 | content: "\f08b"; 573 | } 574 | .fa-linkedin-square:before { 575 | content: "\f08c"; 576 | } 577 | .fa-thumb-tack:before { 578 | content: "\f08d"; 579 | } 580 | .fa-external-link:before { 581 | content: "\f08e"; 582 | } 583 | .fa-sign-in:before { 584 | content: "\f090"; 585 | } 586 | .fa-trophy:before { 587 | content: "\f091"; 588 | } 589 | .fa-github-square:before { 590 | content: "\f092"; 591 | } 592 | .fa-upload:before { 593 | content: "\f093"; 594 | } 595 | .fa-lemon-o:before { 596 | content: "\f094"; 597 | } 598 | .fa-phone:before { 599 | content: "\f095"; 600 | } 601 | .fa-square-o:before { 602 | content: "\f096"; 603 | } 604 | .fa-bookmark-o:before { 605 | content: "\f097"; 606 | } 607 | .fa-phone-square:before { 608 | content: "\f098"; 609 | } 610 | .fa-twitter:before { 611 | content: "\f099"; 612 | } 613 | .fa-facebook:before { 614 | content: "\f09a"; 615 | } 616 | .fa-github:before { 617 | content: "\f09b"; 618 | } 619 | .fa-unlock:before { 620 | content: "\f09c"; 621 | } 622 | .fa-credit-card:before { 623 | content: "\f09d"; 624 | } 625 | .fa-rss:before { 626 | content: "\f09e"; 627 | } 628 | .fa-hdd-o:before { 629 | content: "\f0a0"; 630 | } 631 | .fa-bullhorn:before { 632 | content: "\f0a1"; 633 | } 634 | .fa-bell:before { 635 | content: "\f0f3"; 636 | } 637 | .fa-certificate:before { 638 | content: "\f0a3"; 639 | } 640 | .fa-hand-o-right:before { 641 | content: "\f0a4"; 642 | } 643 | .fa-hand-o-left:before { 644 | content: "\f0a5"; 645 | } 646 | .fa-hand-o-up:before { 647 | content: "\f0a6"; 648 | } 649 | .fa-hand-o-down:before { 650 | content: "\f0a7"; 651 | } 652 | .fa-arrow-circle-left:before { 653 | content: "\f0a8"; 654 | } 655 | .fa-arrow-circle-right:before { 656 | content: "\f0a9"; 657 | } 658 | .fa-arrow-circle-up:before { 659 | content: "\f0aa"; 660 | } 661 | .fa-arrow-circle-down:before { 662 | content: "\f0ab"; 663 | } 664 | .fa-globe:before { 665 | content: "\f0ac"; 666 | } 667 | .fa-wrench:before { 668 | content: "\f0ad"; 669 | } 670 | .fa-tasks:before { 671 | content: "\f0ae"; 672 | } 673 | .fa-filter:before { 674 | content: "\f0b0"; 675 | } 676 | .fa-briefcase:before { 677 | content: "\f0b1"; 678 | } 679 | .fa-arrows-alt:before { 680 | content: "\f0b2"; 681 | } 682 | .fa-group:before, 683 | .fa-users:before { 684 | content: "\f0c0"; 685 | } 686 | .fa-chain:before, 687 | .fa-link:before { 688 | content: "\f0c1"; 689 | } 690 | .fa-cloud:before { 691 | content: "\f0c2"; 692 | } 693 | .fa-flask:before { 694 | content: "\f0c3"; 695 | } 696 | .fa-cut:before, 697 | .fa-scissors:before { 698 | content: "\f0c4"; 699 | } 700 | .fa-copy:before, 701 | .fa-files-o:before { 702 | content: "\f0c5"; 703 | } 704 | .fa-paperclip:before { 705 | content: "\f0c6"; 706 | } 707 | .fa-save:before, 708 | .fa-floppy-o:before { 709 | content: "\f0c7"; 710 | } 711 | .fa-square:before { 712 | content: "\f0c8"; 713 | } 714 | .fa-navicon:before, 715 | .fa-reorder:before, 716 | .fa-bars:before { 717 | content: "\f0c9"; 718 | } 719 | .fa-list-ul:before { 720 | content: "\f0ca"; 721 | } 722 | .fa-list-ol:before { 723 | content: "\f0cb"; 724 | } 725 | .fa-strikethrough:before { 726 | content: "\f0cc"; 727 | } 728 | .fa-underline:before { 729 | content: "\f0cd"; 730 | } 731 | .fa-table:before { 732 | content: "\f0ce"; 733 | } 734 | .fa-magic:before { 735 | content: "\f0d0"; 736 | } 737 | .fa-truck:before { 738 | content: "\f0d1"; 739 | } 740 | .fa-pinterest:before { 741 | content: "\f0d2"; 742 | } 743 | .fa-pinterest-square:before { 744 | content: "\f0d3"; 745 | } 746 | .fa-google-plus-square:before { 747 | content: "\f0d4"; 748 | } 749 | .fa-google-plus:before { 750 | content: "\f0d5"; 751 | } 752 | .fa-money:before { 753 | content: "\f0d6"; 754 | } 755 | .fa-caret-down:before { 756 | content: "\f0d7"; 757 | } 758 | .fa-caret-up:before { 759 | content: "\f0d8"; 760 | } 761 | .fa-caret-left:before { 762 | content: "\f0d9"; 763 | } 764 | .fa-caret-right:before { 765 | content: "\f0da"; 766 | } 767 | .fa-columns:before { 768 | content: "\f0db"; 769 | } 770 | .fa-unsorted:before, 771 | .fa-sort:before { 772 | content: "\f0dc"; 773 | } 774 | .fa-sort-down:before, 775 | .fa-sort-desc:before { 776 | content: "\f0dd"; 777 | } 778 | .fa-sort-up:before, 779 | .fa-sort-asc:before { 780 | content: "\f0de"; 781 | } 782 | .fa-envelope:before { 783 | content: "\f0e0"; 784 | } 785 | .fa-linkedin:before { 786 | content: "\f0e1"; 787 | } 788 | .fa-rotate-left:before, 789 | .fa-undo:before { 790 | content: "\f0e2"; 791 | } 792 | .fa-legal:before, 793 | .fa-gavel:before { 794 | content: "\f0e3"; 795 | } 796 | .fa-dashboard:before, 797 | .fa-tachometer:before { 798 | content: "\f0e4"; 799 | } 800 | .fa-comment-o:before { 801 | content: "\f0e5"; 802 | } 803 | .fa-comments-o:before { 804 | content: "\f0e6"; 805 | } 806 | .fa-flash:before, 807 | .fa-bolt:before { 808 | content: "\f0e7"; 809 | } 810 | .fa-sitemap:before { 811 | content: "\f0e8"; 812 | } 813 | .fa-umbrella:before { 814 | content: "\f0e9"; 815 | } 816 | .fa-paste:before, 817 | .fa-clipboard:before { 818 | content: "\f0ea"; 819 | } 820 | .fa-lightbulb-o:before { 821 | content: "\f0eb"; 822 | } 823 | .fa-exchange:before { 824 | content: "\f0ec"; 825 | } 826 | .fa-cloud-download:before { 827 | content: "\f0ed"; 828 | } 829 | .fa-cloud-upload:before { 830 | content: "\f0ee"; 831 | } 832 | .fa-user-md:before { 833 | content: "\f0f0"; 834 | } 835 | .fa-stethoscope:before { 836 | content: "\f0f1"; 837 | } 838 | .fa-suitcase:before { 839 | content: "\f0f2"; 840 | } 841 | .fa-bell-o:before { 842 | content: "\f0a2"; 843 | } 844 | .fa-coffee:before { 845 | content: "\f0f4"; 846 | } 847 | .fa-cutlery:before { 848 | content: "\f0f5"; 849 | } 850 | .fa-file-text-o:before { 851 | content: "\f0f6"; 852 | } 853 | .fa-building-o:before { 854 | content: "\f0f7"; 855 | } 856 | .fa-hospital-o:before { 857 | content: "\f0f8"; 858 | } 859 | .fa-ambulance:before { 860 | content: "\f0f9"; 861 | } 862 | .fa-medkit:before { 863 | content: "\f0fa"; 864 | } 865 | .fa-fighter-jet:before { 866 | content: "\f0fb"; 867 | } 868 | .fa-beer:before { 869 | content: "\f0fc"; 870 | } 871 | .fa-h-square:before { 872 | content: "\f0fd"; 873 | } 874 | .fa-plus-square:before { 875 | content: "\f0fe"; 876 | } 877 | .fa-angle-double-left:before { 878 | content: "\f100"; 879 | } 880 | .fa-angle-double-right:before { 881 | content: "\f101"; 882 | } 883 | .fa-angle-double-up:before { 884 | content: "\f102"; 885 | } 886 | .fa-angle-double-down:before { 887 | content: "\f103"; 888 | } 889 | .fa-angle-left:before { 890 | content: "\f104"; 891 | } 892 | .fa-angle-right:before { 893 | content: "\f105"; 894 | } 895 | .fa-angle-up:before { 896 | content: "\f106"; 897 | } 898 | .fa-angle-down:before { 899 | content: "\f107"; 900 | } 901 | .fa-desktop:before { 902 | content: "\f108"; 903 | } 904 | .fa-laptop:before { 905 | content: "\f109"; 906 | } 907 | .fa-tablet:before { 908 | content: "\f10a"; 909 | } 910 | .fa-mobile-phone:before, 911 | .fa-mobile:before { 912 | content: "\f10b"; 913 | } 914 | .fa-circle-o:before { 915 | content: "\f10c"; 916 | } 917 | .fa-quote-left:before { 918 | content: "\f10d"; 919 | } 920 | .fa-quote-right:before { 921 | content: "\f10e"; 922 | } 923 | .fa-spinner:before { 924 | content: "\f110"; 925 | } 926 | .fa-circle:before { 927 | content: "\f111"; 928 | } 929 | .fa-mail-reply:before, 930 | .fa-reply:before { 931 | content: "\f112"; 932 | } 933 | .fa-github-alt:before { 934 | content: "\f113"; 935 | } 936 | .fa-folder-o:before { 937 | content: "\f114"; 938 | } 939 | .fa-folder-open-o:before { 940 | content: "\f115"; 941 | } 942 | .fa-smile-o:before { 943 | content: "\f118"; 944 | } 945 | .fa-frown-o:before { 946 | content: "\f119"; 947 | } 948 | .fa-meh-o:before { 949 | content: "\f11a"; 950 | } 951 | .fa-gamepad:before { 952 | content: "\f11b"; 953 | } 954 | .fa-keyboard-o:before { 955 | content: "\f11c"; 956 | } 957 | .fa-flag-o:before { 958 | content: "\f11d"; 959 | } 960 | .fa-flag-checkered:before { 961 | content: "\f11e"; 962 | } 963 | .fa-terminal:before { 964 | content: "\f120"; 965 | } 966 | .fa-code:before { 967 | content: "\f121"; 968 | } 969 | .fa-mail-reply-all:before, 970 | .fa-reply-all:before { 971 | content: "\f122"; 972 | } 973 | .fa-star-half-empty:before, 974 | .fa-star-half-full:before, 975 | .fa-star-half-o:before { 976 | content: "\f123"; 977 | } 978 | .fa-location-arrow:before { 979 | content: "\f124"; 980 | } 981 | .fa-crop:before { 982 | content: "\f125"; 983 | } 984 | .fa-code-fork:before { 985 | content: "\f126"; 986 | } 987 | .fa-unlink:before, 988 | .fa-chain-broken:before { 989 | content: "\f127"; 990 | } 991 | .fa-question:before { 992 | content: "\f128"; 993 | } 994 | .fa-info:before { 995 | content: "\f129"; 996 | } 997 | .fa-exclamation:before { 998 | content: "\f12a"; 999 | } 1000 | .fa-superscript:before { 1001 | content: "\f12b"; 1002 | } 1003 | .fa-subscript:before { 1004 | content: "\f12c"; 1005 | } 1006 | .fa-eraser:before { 1007 | content: "\f12d"; 1008 | } 1009 | .fa-puzzle-piece:before { 1010 | content: "\f12e"; 1011 | } 1012 | .fa-microphone:before { 1013 | content: "\f130"; 1014 | } 1015 | .fa-microphone-slash:before { 1016 | content: "\f131"; 1017 | } 1018 | .fa-shield:before { 1019 | content: "\f132"; 1020 | } 1021 | .fa-calendar-o:before { 1022 | content: "\f133"; 1023 | } 1024 | .fa-fire-extinguisher:before { 1025 | content: "\f134"; 1026 | } 1027 | .fa-rocket:before { 1028 | content: "\f135"; 1029 | } 1030 | .fa-maxcdn:before { 1031 | content: "\f136"; 1032 | } 1033 | .fa-chevron-circle-left:before { 1034 | content: "\f137"; 1035 | } 1036 | .fa-chevron-circle-right:before { 1037 | content: "\f138"; 1038 | } 1039 | .fa-chevron-circle-up:before { 1040 | content: "\f139"; 1041 | } 1042 | .fa-chevron-circle-down:before { 1043 | content: "\f13a"; 1044 | } 1045 | .fa-html5:before { 1046 | content: "\f13b"; 1047 | } 1048 | .fa-css3:before { 1049 | content: "\f13c"; 1050 | } 1051 | .fa-anchor:before { 1052 | content: "\f13d"; 1053 | } 1054 | .fa-unlock-alt:before { 1055 | content: "\f13e"; 1056 | } 1057 | .fa-bullseye:before { 1058 | content: "\f140"; 1059 | } 1060 | .fa-ellipsis-h:before { 1061 | content: "\f141"; 1062 | } 1063 | .fa-ellipsis-v:before { 1064 | content: "\f142"; 1065 | } 1066 | .fa-rss-square:before { 1067 | content: "\f143"; 1068 | } 1069 | .fa-play-circle:before { 1070 | content: "\f144"; 1071 | } 1072 | .fa-ticket:before { 1073 | content: "\f145"; 1074 | } 1075 | .fa-minus-square:before { 1076 | content: "\f146"; 1077 | } 1078 | .fa-minus-square-o:before { 1079 | content: "\f147"; 1080 | } 1081 | .fa-level-up:before { 1082 | content: "\f148"; 1083 | } 1084 | .fa-level-down:before { 1085 | content: "\f149"; 1086 | } 1087 | .fa-check-square:before { 1088 | content: "\f14a"; 1089 | } 1090 | .fa-pencil-square:before { 1091 | content: "\f14b"; 1092 | } 1093 | .fa-external-link-square:before { 1094 | content: "\f14c"; 1095 | } 1096 | .fa-share-square:before { 1097 | content: "\f14d"; 1098 | } 1099 | .fa-compass:before { 1100 | content: "\f14e"; 1101 | } 1102 | .fa-toggle-down:before, 1103 | .fa-caret-square-o-down:before { 1104 | content: "\f150"; 1105 | } 1106 | .fa-toggle-up:before, 1107 | .fa-caret-square-o-up:before { 1108 | content: "\f151"; 1109 | } 1110 | .fa-toggle-right:before, 1111 | .fa-caret-square-o-right:before { 1112 | content: "\f152"; 1113 | } 1114 | .fa-euro:before, 1115 | .fa-eur:before { 1116 | content: "\f153"; 1117 | } 1118 | .fa-gbp:before { 1119 | content: "\f154"; 1120 | } 1121 | .fa-dollar:before, 1122 | .fa-usd:before { 1123 | content: "\f155"; 1124 | } 1125 | .fa-rupee:before, 1126 | .fa-inr:before { 1127 | content: "\f156"; 1128 | } 1129 | .fa-cny:before, 1130 | .fa-rmb:before, 1131 | .fa-yen:before, 1132 | .fa-jpy:before { 1133 | content: "\f157"; 1134 | } 1135 | .fa-ruble:before, 1136 | .fa-rouble:before, 1137 | .fa-rub:before { 1138 | content: "\f158"; 1139 | } 1140 | .fa-won:before, 1141 | .fa-krw:before { 1142 | content: "\f159"; 1143 | } 1144 | .fa-bitcoin:before, 1145 | .fa-btc:before { 1146 | content: "\f15a"; 1147 | } 1148 | .fa-file:before { 1149 | content: "\f15b"; 1150 | } 1151 | .fa-file-text:before { 1152 | content: "\f15c"; 1153 | } 1154 | .fa-sort-alpha-asc:before { 1155 | content: "\f15d"; 1156 | } 1157 | .fa-sort-alpha-desc:before { 1158 | content: "\f15e"; 1159 | } 1160 | .fa-sort-amount-asc:before { 1161 | content: "\f160"; 1162 | } 1163 | .fa-sort-amount-desc:before { 1164 | content: "\f161"; 1165 | } 1166 | .fa-sort-numeric-asc:before { 1167 | content: "\f162"; 1168 | } 1169 | .fa-sort-numeric-desc:before { 1170 | content: "\f163"; 1171 | } 1172 | .fa-thumbs-up:before { 1173 | content: "\f164"; 1174 | } 1175 | .fa-thumbs-down:before { 1176 | content: "\f165"; 1177 | } 1178 | .fa-youtube-square:before { 1179 | content: "\f166"; 1180 | } 1181 | .fa-youtube:before { 1182 | content: "\f167"; 1183 | } 1184 | .fa-xing:before { 1185 | content: "\f168"; 1186 | } 1187 | .fa-xing-square:before { 1188 | content: "\f169"; 1189 | } 1190 | .fa-youtube-play:before { 1191 | content: "\f16a"; 1192 | } 1193 | .fa-dropbox:before { 1194 | content: "\f16b"; 1195 | } 1196 | .fa-stack-overflow:before { 1197 | content: "\f16c"; 1198 | } 1199 | .fa-instagram:before { 1200 | content: "\f16d"; 1201 | } 1202 | .fa-flickr:before { 1203 | content: "\f16e"; 1204 | } 1205 | .fa-adn:before { 1206 | content: "\f170"; 1207 | } 1208 | .fa-bitbucket:before { 1209 | content: "\f171"; 1210 | } 1211 | .fa-bitbucket-square:before { 1212 | content: "\f172"; 1213 | } 1214 | .fa-tumblr:before { 1215 | content: "\f173"; 1216 | } 1217 | .fa-tumblr-square:before { 1218 | content: "\f174"; 1219 | } 1220 | .fa-long-arrow-down:before { 1221 | content: "\f175"; 1222 | } 1223 | .fa-long-arrow-up:before { 1224 | content: "\f176"; 1225 | } 1226 | .fa-long-arrow-left:before { 1227 | content: "\f177"; 1228 | } 1229 | .fa-long-arrow-right:before { 1230 | content: "\f178"; 1231 | } 1232 | .fa-apple:before { 1233 | content: "\f179"; 1234 | } 1235 | .fa-windows:before { 1236 | content: "\f17a"; 1237 | } 1238 | .fa-android:before { 1239 | content: "\f17b"; 1240 | } 1241 | .fa-linux:before { 1242 | content: "\f17c"; 1243 | } 1244 | .fa-dribbble:before { 1245 | content: "\f17d"; 1246 | } 1247 | .fa-skype:before { 1248 | content: "\f17e"; 1249 | } 1250 | .fa-foursquare:before { 1251 | content: "\f180"; 1252 | } 1253 | .fa-trello:before { 1254 | content: "\f181"; 1255 | } 1256 | .fa-female:before { 1257 | content: "\f182"; 1258 | } 1259 | .fa-male:before { 1260 | content: "\f183"; 1261 | } 1262 | .fa-gittip:before { 1263 | content: "\f184"; 1264 | } 1265 | .fa-sun-o:before { 1266 | content: "\f185"; 1267 | } 1268 | .fa-moon-o:before { 1269 | content: "\f186"; 1270 | } 1271 | .fa-archive:before { 1272 | content: "\f187"; 1273 | } 1274 | .fa-bug:before { 1275 | content: "\f188"; 1276 | } 1277 | .fa-vk:before { 1278 | content: "\f189"; 1279 | } 1280 | .fa-weibo:before { 1281 | content: "\f18a"; 1282 | } 1283 | .fa-renren:before { 1284 | content: "\f18b"; 1285 | } 1286 | .fa-pagelines:before { 1287 | content: "\f18c"; 1288 | } 1289 | .fa-stack-exchange:before { 1290 | content: "\f18d"; 1291 | } 1292 | .fa-arrow-circle-o-right:before { 1293 | content: "\f18e"; 1294 | } 1295 | .fa-arrow-circle-o-left:before { 1296 | content: "\f190"; 1297 | } 1298 | .fa-toggle-left:before, 1299 | .fa-caret-square-o-left:before { 1300 | content: "\f191"; 1301 | } 1302 | .fa-dot-circle-o:before { 1303 | content: "\f192"; 1304 | } 1305 | .fa-wheelchair:before { 1306 | content: "\f193"; 1307 | } 1308 | .fa-vimeo-square:before { 1309 | content: "\f194"; 1310 | } 1311 | .fa-turkish-lira:before, 1312 | .fa-try:before { 1313 | content: "\f195"; 1314 | } 1315 | .fa-plus-square-o:before { 1316 | content: "\f196"; 1317 | } 1318 | .fa-space-shuttle:before { 1319 | content: "\f197"; 1320 | } 1321 | .fa-slack:before { 1322 | content: "\f198"; 1323 | } 1324 | .fa-envelope-square:before { 1325 | content: "\f199"; 1326 | } 1327 | .fa-wordpress:before { 1328 | content: "\f19a"; 1329 | } 1330 | .fa-openid:before { 1331 | content: "\f19b"; 1332 | } 1333 | .fa-institution:before, 1334 | .fa-bank:before, 1335 | .fa-university:before { 1336 | content: "\f19c"; 1337 | } 1338 | .fa-mortar-board:before, 1339 | .fa-graduation-cap:before { 1340 | content: "\f19d"; 1341 | } 1342 | .fa-yahoo:before { 1343 | content: "\f19e"; 1344 | } 1345 | .fa-google:before { 1346 | content: "\f1a0"; 1347 | } 1348 | .fa-reddit:before { 1349 | content: "\f1a1"; 1350 | } 1351 | .fa-reddit-square:before { 1352 | content: "\f1a2"; 1353 | } 1354 | .fa-stumbleupon-circle:before { 1355 | content: "\f1a3"; 1356 | } 1357 | .fa-stumbleupon:before { 1358 | content: "\f1a4"; 1359 | } 1360 | .fa-delicious:before { 1361 | content: "\f1a5"; 1362 | } 1363 | .fa-digg:before { 1364 | content: "\f1a6"; 1365 | } 1366 | .fa-pied-piper:before { 1367 | content: "\f1a7"; 1368 | } 1369 | .fa-pied-piper-alt:before { 1370 | content: "\f1a8"; 1371 | } 1372 | .fa-drupal:before { 1373 | content: "\f1a9"; 1374 | } 1375 | .fa-joomla:before { 1376 | content: "\f1aa"; 1377 | } 1378 | .fa-language:before { 1379 | content: "\f1ab"; 1380 | } 1381 | .fa-fax:before { 1382 | content: "\f1ac"; 1383 | } 1384 | .fa-building:before { 1385 | content: "\f1ad"; 1386 | } 1387 | .fa-child:before { 1388 | content: "\f1ae"; 1389 | } 1390 | .fa-paw:before { 1391 | content: "\f1b0"; 1392 | } 1393 | .fa-spoon:before { 1394 | content: "\f1b1"; 1395 | } 1396 | .fa-cube:before { 1397 | content: "\f1b2"; 1398 | } 1399 | .fa-cubes:before { 1400 | content: "\f1b3"; 1401 | } 1402 | .fa-behance:before { 1403 | content: "\f1b4"; 1404 | } 1405 | .fa-behance-square:before { 1406 | content: "\f1b5"; 1407 | } 1408 | .fa-steam:before { 1409 | content: "\f1b6"; 1410 | } 1411 | .fa-steam-square:before { 1412 | content: "\f1b7"; 1413 | } 1414 | .fa-recycle:before { 1415 | content: "\f1b8"; 1416 | } 1417 | .fa-automobile:before, 1418 | .fa-car:before { 1419 | content: "\f1b9"; 1420 | } 1421 | .fa-cab:before, 1422 | .fa-taxi:before { 1423 | content: "\f1ba"; 1424 | } 1425 | .fa-tree:before { 1426 | content: "\f1bb"; 1427 | } 1428 | .fa-spotify:before { 1429 | content: "\f1bc"; 1430 | } 1431 | .fa-deviantart:before { 1432 | content: "\f1bd"; 1433 | } 1434 | .fa-soundcloud:before { 1435 | content: "\f1be"; 1436 | } 1437 | .fa-database:before { 1438 | content: "\f1c0"; 1439 | } 1440 | .fa-file-pdf-o:before { 1441 | content: "\f1c1"; 1442 | } 1443 | .fa-file-word-o:before { 1444 | content: "\f1c2"; 1445 | } 1446 | .fa-file-excel-o:before { 1447 | content: "\f1c3"; 1448 | } 1449 | .fa-file-powerpoint-o:before { 1450 | content: "\f1c4"; 1451 | } 1452 | .fa-file-photo-o:before, 1453 | .fa-file-picture-o:before, 1454 | .fa-file-image-o:before { 1455 | content: "\f1c5"; 1456 | } 1457 | .fa-file-zip-o:before, 1458 | .fa-file-archive-o:before { 1459 | content: "\f1c6"; 1460 | } 1461 | .fa-file-sound-o:before, 1462 | .fa-file-audio-o:before { 1463 | content: "\f1c7"; 1464 | } 1465 | .fa-file-movie-o:before, 1466 | .fa-file-video-o:before { 1467 | content: "\f1c8"; 1468 | } 1469 | .fa-file-code-o:before { 1470 | content: "\f1c9"; 1471 | } 1472 | .fa-vine:before { 1473 | content: "\f1ca"; 1474 | } 1475 | .fa-codepen:before { 1476 | content: "\f1cb"; 1477 | } 1478 | .fa-jsfiddle:before { 1479 | content: "\f1cc"; 1480 | } 1481 | .fa-life-bouy:before, 1482 | .fa-life-buoy:before, 1483 | .fa-life-saver:before, 1484 | .fa-support:before, 1485 | .fa-life-ring:before { 1486 | content: "\f1cd"; 1487 | } 1488 | .fa-circle-o-notch:before { 1489 | content: "\f1ce"; 1490 | } 1491 | .fa-ra:before, 1492 | .fa-rebel:before { 1493 | content: "\f1d0"; 1494 | } 1495 | .fa-ge:before, 1496 | .fa-empire:before { 1497 | content: "\f1d1"; 1498 | } 1499 | .fa-git-square:before { 1500 | content: "\f1d2"; 1501 | } 1502 | .fa-git:before { 1503 | content: "\f1d3"; 1504 | } 1505 | .fa-hacker-news:before { 1506 | content: "\f1d4"; 1507 | } 1508 | .fa-tencent-weibo:before { 1509 | content: "\f1d5"; 1510 | } 1511 | .fa-qq:before { 1512 | content: "\f1d6"; 1513 | } 1514 | .fa-wechat:before, 1515 | .fa-weixin:before { 1516 | content: "\f1d7"; 1517 | } 1518 | .fa-send:before, 1519 | .fa-paper-plane:before { 1520 | content: "\f1d8"; 1521 | } 1522 | .fa-send-o:before, 1523 | .fa-paper-plane-o:before { 1524 | content: "\f1d9"; 1525 | } 1526 | .fa-history:before { 1527 | content: "\f1da"; 1528 | } 1529 | .fa-circle-thin:before { 1530 | content: "\f1db"; 1531 | } 1532 | .fa-header:before { 1533 | content: "\f1dc"; 1534 | } 1535 | .fa-paragraph:before { 1536 | content: "\f1dd"; 1537 | } 1538 | .fa-sliders:before { 1539 | content: "\f1de"; 1540 | } 1541 | .fa-share-alt:before { 1542 | content: "\f1e0"; 1543 | } 1544 | .fa-share-alt-square:before { 1545 | content: "\f1e1"; 1546 | } 1547 | .fa-bomb:before { 1548 | content: "\f1e2"; 1549 | } 1550 | .fa-soccer-ball-o:before, 1551 | .fa-futbol-o:before { 1552 | content: "\f1e3"; 1553 | } 1554 | .fa-tty:before { 1555 | content: "\f1e4"; 1556 | } 1557 | .fa-binoculars:before { 1558 | content: "\f1e5"; 1559 | } 1560 | .fa-plug:before { 1561 | content: "\f1e6"; 1562 | } 1563 | .fa-slideshare:before { 1564 | content: "\f1e7"; 1565 | } 1566 | .fa-twitch:before { 1567 | content: "\f1e8"; 1568 | } 1569 | .fa-yelp:before { 1570 | content: "\f1e9"; 1571 | } 1572 | .fa-newspaper-o:before { 1573 | content: "\f1ea"; 1574 | } 1575 | .fa-wifi:before { 1576 | content: "\f1eb"; 1577 | } 1578 | .fa-calculator:before { 1579 | content: "\f1ec"; 1580 | } 1581 | .fa-paypal:before { 1582 | content: "\f1ed"; 1583 | } 1584 | .fa-google-wallet:before { 1585 | content: "\f1ee"; 1586 | } 1587 | .fa-cc-visa:before { 1588 | content: "\f1f0"; 1589 | } 1590 | .fa-cc-mastercard:before { 1591 | content: "\f1f1"; 1592 | } 1593 | .fa-cc-discover:before { 1594 | content: "\f1f2"; 1595 | } 1596 | .fa-cc-amex:before { 1597 | content: "\f1f3"; 1598 | } 1599 | .fa-cc-paypal:before { 1600 | content: "\f1f4"; 1601 | } 1602 | .fa-cc-stripe:before { 1603 | content: "\f1f5"; 1604 | } 1605 | .fa-bell-slash:before { 1606 | content: "\f1f6"; 1607 | } 1608 | .fa-bell-slash-o:before { 1609 | content: "\f1f7"; 1610 | } 1611 | .fa-trash:before { 1612 | content: "\f1f8"; 1613 | } 1614 | .fa-copyright:before { 1615 | content: "\f1f9"; 1616 | } 1617 | .fa-at:before { 1618 | content: "\f1fa"; 1619 | } 1620 | .fa-eyedropper:before { 1621 | content: "\f1fb"; 1622 | } 1623 | .fa-paint-brush:before { 1624 | content: "\f1fc"; 1625 | } 1626 | .fa-birthday-cake:before { 1627 | content: "\f1fd"; 1628 | } 1629 | .fa-area-chart:before { 1630 | content: "\f1fe"; 1631 | } 1632 | .fa-pie-chart:before { 1633 | content: "\f200"; 1634 | } 1635 | .fa-line-chart:before { 1636 | content: "\f201"; 1637 | } 1638 | .fa-lastfm:before { 1639 | content: "\f202"; 1640 | } 1641 | .fa-lastfm-square:before { 1642 | content: "\f203"; 1643 | } 1644 | .fa-toggle-off:before { 1645 | content: "\f204"; 1646 | } 1647 | .fa-toggle-on:before { 1648 | content: "\f205"; 1649 | } 1650 | .fa-bicycle:before { 1651 | content: "\f206"; 1652 | } 1653 | .fa-bus:before { 1654 | content: "\f207"; 1655 | } 1656 | .fa-ioxhost:before { 1657 | content: "\f208"; 1658 | } 1659 | .fa-angellist:before { 1660 | content: "\f209"; 1661 | } 1662 | .fa-cc:before { 1663 | content: "\f20a"; 1664 | } 1665 | .fa-shekel:before, 1666 | .fa-sheqel:before, 1667 | .fa-ils:before { 1668 | content: "\f20b"; 1669 | } 1670 | .fa-meanpath:before { 1671 | content: "\f20c"; 1672 | } 1673 | --------------------------------------------------------------------------------