@@ -16,7 +16,7 @@ git clone https://forge.cadoles.com/wpetit/pitaya.git | |||
cd pitaya | |||
git checkout develop | |||
npm install | |||
npm start | |||
DEBUG=pitaya* npm start | |||
``` | |||
## Options |
@@ -6,7 +6,6 @@ html, body { | |||
padding: 0; | |||
margin: 0; | |||
font-family: 'Droid Sans', 'Ubuntu Sans', sans-serif; | |||
background: url('../img/background.png') no-repeat; | |||
background-size: contain; | |||
background-position: center; | |||
background-color: rgb(34, 107, 160); | |||
@@ -16,6 +15,11 @@ html, body { | |||
overflow-x: hidden; | |||
} | |||
.alert.alert-default { | |||
border-radius: 4px; | |||
border: 1px solid #ddd; | |||
} | |||
/* Launcher View */ | |||
.launcher { | |||
@@ -23,6 +27,7 @@ html, body { | |||
width: 100%; | |||
height: 100%; | |||
flex-direction: column; | |||
background: url('../img/background.png') no-repeat; | |||
} | |||
.launcher .category-header { | |||
@@ -109,27 +114,80 @@ html, body { | |||
width: 100%; | |||
height: 100%; | |||
flex-direction: column; | |||
align-items: flex-start; | |||
} | |||
.edit ul.desktop-apps { | |||
.edit .workspace { | |||
display: flex; | |||
flex-direction: row; | |||
padding: 10px; | |||
} | |||
.edit .left-menu { | |||
display: flex; | |||
flex-direction: column; | |||
flex: 1; | |||
} | |||
.edit .item-form { | |||
display: flex; | |||
flex-direction: row; | |||
flex: 1; | |||
} | |||
.edit .apps-list .icon-theme-selector > select { | |||
width: 100%; | |||
} | |||
.edit .apps-list ul.desktop-apps { | |||
list-style: none; | |||
padding: 0; | |||
overflow-y: auto; | |||
height: 100%; | |||
margin: 10px 0 0 0; | |||
padding: 0 10px 0 0; | |||
} | |||
.edit li.desktop-app { | |||
.edit .apps-list li.desktop-app { | |||
} | |||
.edit .desktop-app > .app-icon { | |||
height: 50px; | |||
width: 50px; | |||
height: 35px; | |||
width: 35px; | |||
display: inline-block; | |||
vertical-align: middle; | |||
margin-right: 10px; | |||
} | |||
.edit .profile-tree { | |||
flex: 3; | |||
padding: 0 5px; | |||
} | |||
.edit .profile-tree ul { | |||
list-style: none; | |||
margin: 0; | |||
} | |||
.edit .profile-tree > .tree-item > ul { | |||
padding: 0; | |||
} | |||
.edit .profile-tree .tree-item .alert { | |||
padding: 5px; | |||
margin-bottom: 2px; | |||
} | |||
.edit .profile-tree .tree-item .app-icon { | |||
height: 25px; | |||
margin-right: 5px; | |||
} | |||
.edit .app-item-edit { | |||
flex: 1; | |||
padding: 0 5px; | |||
} | |||
/* Animations */ | |||
.pulse { |
@@ -9,7 +9,7 @@ | |||
"icon": "chromium-browser", | |||
"items": [ | |||
{ | |||
"label": "Chromium Browser", | |||
"label": "Chromium Browser 1", | |||
"icon": "chromium-browser", | |||
"exec": "/usr/bin/chromium-browser" | |||
} | |||
@@ -24,7 +24,7 @@ | |||
"icon": "chromium-browser", | |||
"items": [ | |||
{ | |||
"label": "Chromium Browser", | |||
"label": "Chromium Browser 2", | |||
"icon": "chromium-browser", | |||
"exec": "/usr/bin/chromium-browser" | |||
} |
@@ -1 +1,61 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" ><path d="M0 0h512v512H0z" fill="transparent" stroke="#fff" stroke-width="0"></path><path d="M256 16C123.45 16 16 123.45 16 256s107.45 240 240 240 240-107.45 240-240S388.55 16 256 16zm0 60c99.41 0 180 80.59 180 180s-80.59 180-180 180S76 355.41 76 256 156.59 76 256 76zm-80.625 60c-.97-.005-2.006.112-3.063.313v-.032c-18.297 3.436-45.264 34.743-33.375 46.626l73.157 73.125-73.156 73.126c-14.63 14.625 29.275 58.534 43.906 43.906L256 299.906l73.156 73.156c14.63 14.628 58.537-29.28 43.906-43.906l-73.156-73.125 73.156-73.124c14.63-14.625-29.275-58.5-43.906-43.875L256 212.157l-73.156-73.125c-2.06-2.046-4.56-3.015-7.47-3.03z" fill="#fff"></path></svg> | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
viewBox="0 0 512 512" | |||
id="svg2" | |||
version="1.1" | |||
inkscape:version="0.48.4 r9939" | |||
width="100%" | |||
height="100%" | |||
sodipodi:docname="default-icon.svg"> | |||
<metadata | |||
id="metadata12"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<defs | |||
id="defs10" /> | |||
<sodipodi:namedview | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1" | |||
objecttolerance="10" | |||
gridtolerance="10" | |||
guidetolerance="10" | |||
inkscape:pageopacity="0" | |||
inkscape:pageshadow="2" | |||
inkscape:window-width="1600" | |||
inkscape:window-height="850" | |||
id="namedview8" | |||
showgrid="false" | |||
inkscape:zoom="0.65186406" | |||
inkscape:cx="59.383922" | |||
inkscape:cy="222.95767" | |||
inkscape:window-x="0" | |||
inkscape:window-y="0" | |||
inkscape:window-maximized="1" | |||
inkscape:current-layer="svg2" /> | |||
<path | |||
d="M0 0h512v512H0z" | |||
fill="transparent" | |||
stroke="#fff" | |||
stroke-width="0" | |||
id="path4" /> | |||
<path | |||
d="M256 16C123.45 16 16 123.45 16 256s107.45 240 240 240 240-107.45 240-240S388.55 16 256 16zm0 60c99.41 0 180 80.59 180 180s-80.59 180-180 180S76 355.41 76 256 156.59 76 256 76zm-80.625 60c-.97-.005-2.006.112-3.063.313v-.032c-18.297 3.436-45.264 34.743-33.375 46.626l73.157 73.125-73.156 73.126c-14.63 14.625 29.275 58.534 43.906 43.906L256 299.906l73.156 73.156c14.63 14.628 58.537-29.28 43.906-43.906l-73.156-73.125 73.156-73.124c14.63-14.625-29.275-58.5-43.906-43.875L256 212.157l-73.156-73.125c-2.06-2.046-4.56-3.015-7.47-3.03z" | |||
fill="#fff" | |||
id="path6" | |||
style="fill:#a4a4a4;fill-opacity:1" /> | |||
</svg> |
@@ -1 +1,154 @@ | |||
<?xml version="1.0" encoding="utf-8"?><svg width='50px' height='50px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-hourglass"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><g><path fill="none" stroke="#ffffff" stroke-width="5" stroke-miterlimit="10" d="M58.4,51.7c-0.9-0.9-1.4-2-1.4-2.3s0.5-0.4,1.4-1.4 C70.8,43.8,79.8,30.5,80,15.5H70H30H20c0.2,15,9.2,28.1,21.6,32.3c0.9,0.9,1.4,1.2,1.4,1.5s-0.5,1.6-1.4,2.5 C29.2,56.1,20.2,69.5,20,85.5h10h40h10C79.8,69.5,70.8,55.9,58.4,51.7z" class="glass"></path><clipPath id="uil-hourglass-clip1"><rect x="15" y="20" width="70" height="25" class="clip"><animate attributeName="height" from="25" to="0" dur="1s" repeatCount="indefinite" vlaues="25;0;0" keyTimes="0;0.5;1"></animate><animate attributeName="y" from="20" to="45" dur="1s" repeatCount="indefinite" vlaues="20;45;45" keyTimes="0;0.5;1"></animate></rect></clipPath><clipPath id="uil-hourglass-clip2"><rect x="15" y="55" width="70" height="25" class="clip"><animate attributeName="height" from="0" to="25" dur="1s" repeatCount="indefinite" vlaues="0;25;25" keyTimes="0;0.5;1"></animate><animate attributeName="y" from="80" to="55" dur="1s" repeatCount="indefinite" vlaues="80;55;55" keyTimes="0;0.5;1"></animate></rect></clipPath><path d="M29,23c3.1,11.4,11.3,19.5,21,19.5S67.9,34.4,71,23H29z" clip-path="url(#uil-hourglass-clip1)" fill="#d2d2d2" class="sand"></path><path d="M71.6,78c-3-11.6-11.5-20-21.5-20s-18.5,8.4-21.5,20H71.6z" clip-path="url(#uil-hourglass-clip2)" fill="#d2d2d2" class="sand"></path><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="180 50 50" repeatCount="indefinite" dur="1s" values="0 50 50;0 50 50;180 50 50" keyTimes="0;0.7;1"></animateTransform></g></svg> | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="50px" | |||
height="50px" | |||
viewBox="0 0 100 100" | |||
preserveAspectRatio="xMidYMid" | |||
class="uil-hourglass" | |||
id="svg2" | |||
version="1.1" | |||
inkscape:version="0.48.4 r9939" | |||
sodipodi:docname="hourglass.svg"> | |||
<metadata | |||
id="metadata34"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<defs | |||
id="defs32" /> | |||
<sodipodi:namedview | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1" | |||
objecttolerance="10" | |||
gridtolerance="10" | |||
guidetolerance="10" | |||
inkscape:pageopacity="0" | |||
inkscape:pageshadow="2" | |||
inkscape:window-width="1600" | |||
inkscape:window-height="850" | |||
id="namedview30" | |||
showgrid="false" | |||
inkscape:zoom="13.350176" | |||
inkscape:cx="-10.530194" | |||
inkscape:cy="27.889305" | |||
inkscape:window-x="0" | |||
inkscape:window-y="0" | |||
inkscape:window-maximized="1" | |||
inkscape:current-layer="g6" /> | |||
<rect | |||
x="0" | |||
y="0" | |||
width="100" | |||
height="100" | |||
fill="none" | |||
class="bk" | |||
id="rect4" /> | |||
<g | |||
id="g6"> | |||
<path | |||
fill="none" | |||
stroke="#ffffff" | |||
stroke-width="5" | |||
stroke-miterlimit="10" | |||
d="M58.4,51.7c-0.9-0.9-1.4-2-1.4-2.3s0.5-0.4,1.4-1.4 C70.8,43.8,79.8,30.5,80,15.5H70H30H20c0.2,15,9.2,28.1,21.6,32.3c0.9,0.9,1.4,1.2,1.4,1.5s-0.5,1.6-1.4,2.5 C29.2,56.1,20.2,69.5,20,85.5h10h40h10C79.8,69.5,70.8,55.9,58.4,51.7z" | |||
class="glass" | |||
id="path8" | |||
style="fill:#a4a4a4;fill-opacity:1" /> | |||
<clipPath | |||
id="uil-hourglass-clip1"> | |||
<rect | |||
x="15" | |||
y="20" | |||
width="70" | |||
height="25" | |||
class="clip" | |||
id="rect11"> | |||
<animate | |||
attributeName="height" | |||
from="25" | |||
to="0" | |||
dur="1s" | |||
repeatCount="indefinite" | |||
vlaues="25;0;0" | |||
keyTimes="0;0.5;1" | |||
id="animate13" /> | |||
<animate | |||
attributeName="y" | |||
from="20" | |||
to="45" | |||
dur="1s" | |||
repeatCount="indefinite" | |||
vlaues="20;45;45" | |||
keyTimes="0;0.5;1" | |||
id="animate15" /> | |||
</rect> | |||
</clipPath> | |||
<clipPath | |||
id="uil-hourglass-clip2"> | |||
<rect | |||
x="15" | |||
y="55" | |||
width="70" | |||
height="25" | |||
class="clip" | |||
id="rect18"> | |||
<animate | |||
attributeName="height" | |||
from="0" | |||
to="25" | |||
dur="1s" | |||
repeatCount="indefinite" | |||
vlaues="0;25;25" | |||
keyTimes="0;0.5;1" | |||
id="animate20" /> | |||
<animate | |||
attributeName="y" | |||
from="80" | |||
to="55" | |||
dur="1s" | |||
repeatCount="indefinite" | |||
vlaues="80;55;55" | |||
keyTimes="0;0.5;1" | |||
id="animate22" /> | |||
</rect> | |||
</clipPath> | |||
<path | |||
d="M29,23c3.1,11.4,11.3,19.5,21,19.5S67.9,34.4,71,23H29z" | |||
clip-path="url(#uil-hourglass-clip1)" | |||
fill="#d2d2d2" | |||
class="sand" | |||
id="path24" | |||
style="fill:#ffffff;fill-opacity:1" /> | |||
<path | |||
d="M71.6,78c-3-11.6-11.5-20-21.5-20s-18.5,8.4-21.5,20H71.6z" | |||
clip-path="url(#uil-hourglass-clip2)" | |||
fill="#d2d2d2" | |||
class="sand" | |||
id="path26" | |||
style="fill:#ffffff;fill-opacity:1" /> | |||
<animateTransform | |||
attributeName="transform" | |||
type="rotate" | |||
from="0 50 50" | |||
to="180 50 50" | |||
repeatCount="indefinite" | |||
dur="1s" | |||
values="0 50 50;0 50 50;180 50 50" | |||
keyTimes="0;0.7;1" | |||
id="animateTransform28" /> | |||
</g> | |||
</svg> |
@@ -2,12 +2,13 @@ | |||
<head> | |||
<title>Lanceur - Pitaya</title> | |||
<link rel="stylesheet" href="css/style.css" /> | |||
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css"> | |||
</head> | |||
<body> | |||
<!-- Application root element --> | |||
<div id="pitaya"></div> | |||
<!-- Scripts --> | |||
<script type="text/javascript"> | |||
// React context detection workaround |
@@ -12,14 +12,7 @@ var App = React.createClass({ | |||
var editMode = this.props.processOpts.edit || false; | |||
var view = editMode ? | |||
<Provider store={store}> | |||
{ function() { return <EditView />; }.bind(this) } | |||
</Provider> : | |||
<Provider store={store}> | |||
{ function() { return <LauncherView />; }.bind(this) } | |||
</Provider> | |||
; | |||
var view = editMode ? <EditView /> : <LauncherView />; | |||
return ( | |||
<div id="pitaya"> |
@@ -11,7 +11,7 @@ module.exports = React.createClass({ | |||
mixins: [LazyLoad], | |||
getInitialState: function() { | |||
return { icon: DEFAULT_ICON, currentTheme: undefined }; | |||
return { icon: DEFAULT_ICON, currentTheme: '' }; | |||
}, | |||
onInViewport: function() { | |||
@@ -23,7 +23,7 @@ module.exports = React.createClass({ | |||
var currentTheme = this.state.currentTheme; | |||
var newTheme = this.props.theme; | |||
if( !this.isInViewport() || newTheme === currentTheme ) return; | |||
if( !this.isInViewport() || newTheme === currentTheme ) return; | |||
this.setState({ icon: LOADING_ICON, currentTheme: newTheme }); | |||
@@ -1,8 +1,9 @@ | |||
var React = require('react'); | |||
var Util = require('../../util'); | |||
var AppIcon = require('../common/app-icon.jsx'); | |||
var DragSource = require('react-dnd').DragSource; | |||
module.exports = React.createClass({ | |||
var DesktopAppItem = React.createClass({ | |||
render: function() { | |||
@@ -11,8 +12,10 @@ module.exports = React.createClass({ | |||
var category = desktopEntry.Categories; | |||
var icon = desktopEntry.Icon; | |||
return ( | |||
<li className="desktop-app"> | |||
var connectDragSource = this.props.connectDragSource; | |||
return connectDragSource( | |||
<li className="desktop-app list-group-item"> | |||
<AppIcon className="desktop-app-icon" icon={icon} theme={this.props.theme} /> | |||
<span className="desktop-app-label">{label}</span> | |||
</li> | |||
@@ -21,3 +24,32 @@ module.exports = React.createClass({ | |||
} | |||
}); | |||
var dragSourceSpec = { | |||
beginDrag: function(props) { | |||
return props; | |||
}, | |||
endDrag: function(props, monitor) { | |||
if (!monitor.didDrop()) { | |||
return; | |||
} | |||
var dropResult = monitor.getDropResult(); | |||
return props.onItemDropped(props.desktopEntry, dropResult); | |||
} | |||
}; | |||
function dragSourceCollect(connect, monitor) { | |||
return { | |||
connectDragSource: connect.dragSource(), | |||
isDragging: monitor.isDragging() | |||
}; | |||
} | |||
module.exports = DragSource('NEW_ITEM', dragSourceSpec, dragSourceCollect)(DesktopAppItem); |
@@ -1,39 +1,33 @@ | |||
var React = require('react'); | |||
var Util = require('../../util'); | |||
var DesktopAppItem = require('./desktop-app-item.jsx'); | |||
var IconThemeSelector = require('./icon-theme-selector.jsx'); | |||
var path = require('path'); | |||
var debug = require('debug')('pitaya:desktop-app-list'); | |||
var debug = require('../../util/debug')('pitaya:desktop-app-list'); | |||
module.exports = React.createClass({ | |||
getInitialState: function() { | |||
return { | |||
selectedTheme: null | |||
}; | |||
}, | |||
var DesktopAppList = React.createClass({ | |||
render: function() { | |||
var items = this.props.desktopApps.map(function(desktopApp, i) { | |||
var desktopEntry = desktopApp.content['Desktop Entry']; | |||
return <DesktopAppItem theme={this.state.selectedTheme} key={desktopApp.path} desktopEntry={desktopEntry} />; | |||
return ( | |||
<DesktopAppItem theme={this.props.selectedTheme} | |||
key={desktopApp.path} | |||
desktopEntry={desktopEntry} | |||
onItemDropped={this.props.onItemDropped} /> | |||
); | |||
}.bind(this)); | |||
return ( | |||
<div> | |||
<IconThemeSelector onThemeSelected={this.onThemeSelected} /> | |||
<ul className="desktop-apps"> | |||
<div className="apps-list"> | |||
<ul className="desktop-apps list-group"> | |||
{items} | |||
</ul> | |||
</div> | |||
); | |||
}, | |||
onThemeSelected: function(theme) { | |||
console.log('Selected theme %s', theme); | |||
this.setState({ selectedTheme: theme }); | |||
} | |||
}); | |||
module.exports = DesktopAppList; |
@@ -1,7 +1,13 @@ | |||
var React = require('react'); | |||
var connect = require('react-redux').connect; | |||
var ProfileTree = require('./profile-tree.jsx'); | |||
var DesktopAppList = require('./desktop-app-list.jsx'); | |||
var actions = require('../../actions'); | |||
var ItemForm = require('./item-form.jsx'); | |||
var IconThemeSelector = require('./icon-theme-selector.jsx'); | |||
var actions = require('../../store/actions'); | |||
var DragDropContext = require('react-dnd').DragDropContext; | |||
var HTML5Backend = require('react-dnd/modules/backends/HTML5'); | |||
var EditView = React.createClass({ | |||
@@ -13,18 +19,49 @@ var EditView = React.createClass({ | |||
return ( | |||
<div className="edit"> | |||
<DesktopAppList desktopApps={this.props.desktopApps} /> | |||
<div className="menu-bar"> | |||
</div> | |||
<div className="workspace"> | |||
<div className="left-menu"> | |||
<IconThemeSelector onThemeSelected={this.onThemeSelected} /> | |||
<DesktopAppList | |||
theme={this.props.theme} | |||
desktopApps={this.props.desktopApps} | |||
onItemDropped={this.onItemDropped} /> | |||
</div> | |||
<ProfileTree /> | |||
<ItemForm /> | |||
</div> | |||
</div> | |||
); | |||
}, | |||
onItemDropped: function(desktopEntry, targetItem) { | |||
var newProfileItem = { | |||
label: desktopEntry.Name, | |||
icon: desktopEntry.Icon, | |||
exec: desktopEntry.Exec | |||
}; | |||
this.props.dispatch(actions.edit.addProfileItem(newProfileItem, targetItem)); | |||
}, | |||
onThemeSelected: function(theme) { | |||
this.props.dispatch(actions.edit.selectTheme(theme)); | |||
} | |||
}); | |||
function select(state) { | |||
return { | |||
desktopApps: state.desktopApps | |||
desktopApps: state.desktopApps, | |||
profile: state.profile, | |||
theme: state.theme | |||
}; | |||
} | |||
module.exports = connect(select)(EditView); | |||
module.exports = DragDropContext(HTML5Backend)(connect(select)(EditView)); |
@@ -39,9 +39,11 @@ module.exports = React.createClass({ | |||
); | |||
return ( | |||
<select value={selectedTheme} onChange={this.onChange}> | |||
{options} | |||
</select> | |||
<div className="icon-theme-selector"> | |||
<select className="form-control" value={selectedTheme} onChange={this.onChange}> | |||
{options} | |||
</select> | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,16 @@ | |||
var React = require('react'); | |||
var ItemForm = React.createClass({ | |||
render: function() { | |||
return ( | |||
<div className="item-form"> | |||
</div> | |||
); | |||
} | |||
}); | |||
module.exports = ItemForm; |
@@ -0,0 +1,67 @@ | |||
var React = require('react'); | |||
var connect = require('react-redux').connect; | |||
var actions = require('../../store/actions'); | |||
var TreeItem = require('./tree-item.jsx'); | |||
var TreeNode = React.createClass({ | |||
render: function() { | |||
var data = this.props.data || {}; | |||
var subItems = data.items || []; | |||
var listElements = subItems.map(function(subItem, i) { | |||
return ( | |||
<li key={i} > | |||
<TreeNode data={subItem} onItemMoved={this.props.onItemMoved} /> | |||
</li> | |||
); | |||
}.bind(this)); | |||
var appEntry = data.icon || data.label ? | |||
<TreeItem data={data} onItemMoved={this.props.onItemMoved} /> : | |||
null | |||
; | |||
return ( | |||
<div className="tree-item"> | |||
{appEntry} | |||
<ul> | |||
{listElements} | |||
</ul> | |||
</div> | |||
); | |||
} | |||
}); | |||
var ProfileTree = React.createClass({ | |||
componentDidMount: function() { | |||
this.props.dispatch(actions.launcher.loadProfile('./default-profile.json')); | |||
}, | |||
render: function() { | |||
return ( | |||
<div className="profile-tree"> | |||
<TreeNode data={this.props.profile} onItemMoved={this.onItemMoved} /> | |||
</div> | |||
); | |||
}, | |||
onItemMoved: function(movedItem, targetItem) { | |||
this.props.dispatch(actions.edit.moveProfileItem(movedItem, targetItem)); | |||
} | |||
}); | |||
function select(state) { | |||
return { | |||
profile: state.profile | |||
}; | |||
} | |||
module.exports = connect(select)(ProfileTree); |
@@ -0,0 +1,86 @@ | |||
var React = require('react/addons'); | |||
var classNames = require('classnames'); | |||
var AppIcon = require('../common/app-icon.jsx'); | |||
var DragSource = require('react-dnd').DragSource; | |||
var DropTarget = require('react-dnd').DropTarget; | |||
var _ = require('lodash'); | |||
var TreeItem = React.createClass({ | |||
render: function() { | |||
var data = this.props.data; | |||
var appIcon = data.icon ? <AppIcon icon={data.icon} /> : null; | |||
var connectDragSource = this.props.connectDragSource; | |||
var connectDropTarget = this.props.connectDropTarget; | |||
var classes = classNames({ | |||
'alert': true, | |||
'alert-default': !this.props.isDragging && !this.props.isOver, | |||
'alert-info': this.props.isDragging, | |||
'alert-success': this.props.isOver | |||
}); | |||
return connectDropTarget(connectDragSource( | |||
<div className={classes}> | |||
{appIcon} | |||
<span className="app-label">{data.label}</span> | |||
</div> | |||
)); | |||
}, | |||
}); | |||
var dragSourceSpec = { | |||
beginDrag: function(props) { | |||
return props.data; | |||
}, | |||
endDrag: function(props, monitor) { | |||
if (!monitor.didDrop()) { | |||
return; | |||
} | |||
var dropResult = monitor.getDropResult(); | |||
return props.onItemMoved(props.data, dropResult); | |||
} | |||
}; | |||
var dropTargetSpec = { | |||
drop: function(props, monitor, component) { | |||
return props.data; | |||
}, | |||
canDrop: function(props, monitor) { | |||
var draggedItem = monitor.getItem(); | |||
return !_.isEqual(draggedItem, props.data); | |||
} | |||
}; | |||
function dragSourceCollect(connect, monitor) { | |||
return { | |||
connectDragSource: connect.dragSource(), | |||
isDragging: monitor.isDragging() | |||
}; | |||
} | |||
function dropTargetCollect(connect, monitor) { | |||
return { | |||
connectDropTarget: connect.dropTarget(), | |||
isOver: monitor.isOver() | |||
}; | |||
} | |||
module.exports = DropTarget(['ITEM', 'NEW_ITEM'], dropTargetSpec, dropTargetCollect)( | |||
DragSource('ITEM', dragSourceSpec, dragSourceCollect)(TreeItem) | |||
); |
@@ -2,7 +2,7 @@ var React = require('react'); | |||
var CategoryHeader = require('./category-header.jsx'); | |||
var AppList = require('./app-list.jsx'); | |||
var AnimateMixin = require('../mixins/animate'); | |||
var actions = require('../../actions'); | |||
var actions = require('../../store/actions'); | |||
var connect = require('react-redux').connect; | |||
var debug = require('../../util/debug')('launcher-view'); | |||
@@ -46,7 +46,7 @@ module.exports = { | |||
}; | |||
var computeComponentsVisibilityDebounced = debounce(computeComponentsVisibility, 250); | |||
var computeComponentsVisibilityDebounced = debounce(computeComponentsVisibility, 100); | |||
// Start listening for changes | |||
window.document.addEventListener('scroll', computeComponentsVisibilityDebounced, true); |
@@ -1,12 +1,15 @@ | |||
var Util = require('../util'); | |||
var Util = require('../../util'); | |||
var path = require('path'); | |||
// Action types | |||
var LOAD_DESKTOP_APPS = exports.LOAD_PROFILE = 'LOAD_DESKTOP_APPS'; | |||
var LOAD_DESKTOP_APPS_SUCCESS = exports.LOAD_DESKTOP_APPS_SUCCESS = 'LOAD_DESKTOP_APPS_SUCCESS'; | |||
var LOAD_DESKTOP_APPS_FAILED = exports.LOAD_DESKTOP_APPS_FAILED = 'LOAD_DESKTOP_APPS_FAILED'; | |||
var MOVE_PROFILE_ITEM = exports.MOVE_PROFILE_ITEM = 'MOVE_PROFILE_ITEM'; | |||
var ADD_PROFILE_ITEM = exports.ADD_PROFILE_ITEM = 'ADD_PROFILE_ITEM'; | |||
// Actions creators | |||
// Actions creator | |||
exports.loadDesktopApps = function() { | |||
return function(dispatch, getState) { | |||
@@ -27,3 +30,20 @@ exports.loadDesktopApps = function() { | |||
}; | |||
}; | |||
exports.moveProfileItem = function(movedItem, targetItem) { | |||
return { | |||
type: 'MOVE_PROFILE_ITEM', | |||
movedItem: movedItem, | |||
targetItem: targetItem | |||
}; | |||
}; | |||
exports.addProfileItem = function(newItem, targetItem) { | |||
return { | |||
type: 'ADD_PROFILE_ITEM', | |||
newItem: newItem, | |||
targetItem: targetItem | |||
}; | |||
}; |
@@ -1,4 +1,4 @@ | |||
var Util = require('../util'); | |||
var Util = require('../../util'); | |||
var LOAD_PROFILE = exports.LOAD_PROFILE = 'LOAD_PROFILE'; | |||
var LOAD_PROFILE_SUCCESS = exports.LOAD_PROFILE_SUCCESS = 'LOAD_PROFILE_SUCCESS'; |
@@ -2,9 +2,11 @@ var redux = require('redux'); | |||
var thunkMiddleware = require('redux-thunk'); | |||
var loggerMiddleware = require('redux-logger'); | |||
var reducers = require('./reducers'); | |||
var loggerMiddleware = require('./middlewares/logger'); | |||
var createStore = redux.applyMiddleware( | |||
thunkMiddleware | |||
thunkMiddleware, | |||
loggerMiddleware | |||
)(redux.createStore); | |||
var appReducer = redux.combineReducers({ |
@@ -0,0 +1,15 @@ | |||
var debug = require('../../util/debug')('store:logger'); | |||
module.exports = function loggerMiddleware(store) { | |||
return function(next) { | |||
return function(action) { | |||
debug('Action %j', action); | |||
debug('Store current state %j', store.getState()); | |||
next(action); | |||
debug('Store new state %j', store.getState()); | |||
if(action.error) { | |||
console.error(action.error.stack || action.error); | |||
} | |||
}; | |||
}; | |||
}; |
@@ -1,8 +1,8 @@ | |||
var actions = require('../../actions'); | |||
var actions = require('../actions'); | |||
module.exports = function(state, action) { | |||
var desktopApps = []; | |||
var desktopApps = state || []; | |||
if( action.type === actions.edit.LOAD_DESKTOP_APPS_SUCCESS ) { | |||
desktopApps = action.desktopApps; |
@@ -1,17 +1,72 @@ | |||
var actions = require('../../actions'); | |||
var _ = require('lodash'); | |||
var actions = require('../actions'); | |||
module.exports = function(oldProfile, action) { | |||
var newProfile = oldProfile || null; | |||
switch(action.type) { | |||
case actions.launcher.LOAD_PROFILE_SUCCESS: | |||
newProfile = action.profile; | |||
break; | |||
return _.cloneDeep(action.profile); | |||
case actions.edit.MOVE_PROFILE_ITEM: | |||
return moveProfileItem(oldProfile, action.movedItem, action.targetItem); | |||
case actions.edit.ADD_PROFILE_ITEM: | |||
return addProfileItem(oldProfile, action.newItem, action.targetItem); | |||
default: | |||
return oldProfile || null; | |||
} | |||
}; | |||
function moveProfileItem(oldProfile, movedItem, targetItem) { | |||
var newProfile = _.cloneDeep(oldProfile); | |||
var previousParent = treeFind(newProfile, movedItem).parent; | |||
var newParent = treeFind(newProfile, targetItem).item; | |||
previousParent.items = _.reject(previousParent.items, function(item) { | |||
return _.isEqual(item, movedItem); | |||
}); | |||
newParent.items = newParent.items || []; | |||
newParent.items.push(_.cloneDeep(movedItem)); | |||
return newProfile; | |||
}; | |||
} | |||
function addProfileItem(oldProfile, newItem, targetItem) { | |||
var newProfile = _.cloneDeep(oldProfile); | |||
var newParent = treeFind(newProfile, targetItem).item; | |||
newParent.items = newParent.items || []; | |||
newParent.items.push(_.cloneDeep(newItem)); | |||
return newProfile; | |||
} | |||
function treeFind(branch, obj) { | |||
var items = branch.items; | |||
if(!items) return; | |||
for( var i = 0, item = items[i]; (item = items[i]); i++ ) { | |||
if( _.isEqual(item, obj) ) { | |||
return {item: item, parent: branch}; | |||
} | |||
if(item.items) { | |||
var result = treeFind(item, obj); | |||
if(result) return result; | |||
} | |||
} | |||
} |
@@ -3,10 +3,15 @@ var util = require('util'); | |||
module.exports = function createLogger(namespace) { | |||
var logger = debug('pitaya:'+namespace); | |||
var console = global.window ? global.window.console : global.console; | |||
var isNWContext = 'window' in global; | |||
var console = isNWContext ? global.window.console : global.console; | |||
logger.log = function() { | |||
var str = util.format.apply(util, arguments); | |||
console.log(str); | |||
if(isNWContext) { | |||
console.log.apply(console, arguments); | |||
} else { | |||
var str = util.format.apply(util, arguments); | |||
console.log(str); | |||
} | |||
}; | |||
return logger; | |||
}; |
@@ -24,9 +24,11 @@ | |||
}, | |||
"dependencies": { | |||
"bootstrap": "^3.3.5", | |||
"classnames": "^2.1.3", | |||
"debug": "^2.2.0", | |||
"glob": "^5.0.14", | |||
"ini": "^1.3.4", | |||
"lodash": "^3.10.1", | |||
"minimist": "^1.1.3", | |||
"node-jsx": "^0.13.3", | |||
"react": "^0.13.3", |