diff --git a/app/helpers/api.js b/app/helpers/api.js index 76e6d21..140b046 100644 --- a/app/helpers/api.js +++ b/app/helpers/api.js @@ -4,12 +4,20 @@ */ const schema = require('../util/schema'); +// ID for next task +let nextTaskId = 0; + /** * Add api stuff * @param api {Api} Api class */ module.exports = (api) => { api.addPlugin({ + + /** + * Sends a notification to the client + * @param notification {Object} notification to send + */ fireNotification: function (notification) { // Validate schema({ @@ -22,24 +30,129 @@ module.exports = (api) => { } // Fire along api.sockets.use((socket) => { - socket.emit('notification', notification); + socket.emit( + 'notification', + notification + ); }); }); + }, + + /** + * Creates a tasks and sends it to the client + * @param task {Object} inital task state + * @return Object + */ + createTask: function (task) { + const taskToSend = Object.assign({ + id: nextTaskId, + status: "0%", + percentage: 0 + }, task); + + schema({ + app: { required: true, type: 'string' }, + status: 'string', + percentage: 'number' + }, task, (err) => { + if (err) { + this.logger.throw_noexit(err); + } + // Emit + api.sockets.use((socket) => { + socket.emit( + 'task:new', + taskToSend + ); + }); + }); + // Increase next task id + nextTaskId++; + + // Task actions + return { + task: taskToSend, + + /** + * Updates the status of a task + * @param newStatus {Object} new status of the task + */ + update(newStatus = {}) { + this.task = Object.assign(this.task, newStatus); + api.sockets.use((socket) => { + socket.emit( + 'task:update', + this.task + ); + }); + }, + + /** + * Ends a task + * @param newStatus {Object} new status of the task + */ + end(newStatus = {}) { + this.task = Object.assign( + this.task, + { status: 'Done!' }, + newStatus, + { percentage: '1' } + ); + // Send + api.sockets.use((socket) => { + socket.emit( + 'task:end', + this.task + ); + }); + }, + + /** + * Sets what to do in the case of the user cancelling the task + * @param cb {Function} what to do on canel + */ + onCancel(cb) { + const taskTmp = this.task; + api.sockets.use((socket) => { + socket.on( + 'task:cancel', + (task) => { + if (task === taskTmp) { + return cb(() => { + // Tell it to end + socket.emit('task:end', taskTmp); + }); + } + } + ); + }); + + } + }; } }); // For testing api.fireNotification({ app: 'Test', - body: 'test', + body: Math.round(Math.random() * 100).toString(), icon: '/img/home-icon.png' }); + const task = api.createTask({ + app: 'Task', + status: 'Doing some stuff...', + percentage: '0' + }); + task.update({ + percentage: '0.6' + }); + // For testing. Remove for final copy api.app.get('/api/dev/fire/notification', (req, res) => { api.io.emit('notification', { app: 'Test', - body: 'test', + body: Math.round(Math.random() * 100).toString(), icon: '/img/home-icon.png' }); res.status(200); diff --git a/client/base/components/notifications.jsx b/client/base/components/notifications.jsx index 345876f..2f777e7 100644 --- a/client/base/components/notifications.jsx +++ b/client/base/components/notifications.jsx @@ -7,6 +7,9 @@ import { SidebarItem } from './navbar/sidebar'; import io from 'socket.io-client'; import '../../sass/notifications.scss'; +// Socket +const socket = io.connect('/'); + // Component export const Notifications = React.createClass({ propTypes: { @@ -17,9 +20,8 @@ export const Notifications = React.createClass({ updateStatus: PropTypes.func.isRequired }, componentDidMount() { - this.socket = io.connect('/'); // Wacth for notification event - this.socket.on('notification', (notification) => { + socket.on('notification', (notification) => { const date = new Date(); // From http://stackoverflow.com/questions/1760250/how-to-tell-if-browser-tab-is-active (document.hidden) if (document.hidden) { @@ -41,12 +43,16 @@ export const Notifications = React.createClass({ const notificationShown = Object.assign({ date: dateString }, notification); - this.props.add(notificationShown); + // Check if not in already + console.log(notification); + if (!this.props.notifications.includes(notificationShown)) { + this.props.add(notificationShown); + } }); }, render() { return ( -
+

+ + ); + } +} + +export class TabsHeader extends Component { + render() { + return ( +
+
    + {this.props.children} +
+
+ ); + } +} + +export class TabsBody extends Component { + constructor() { + super(); + this.state = { + current + }; + setInterval(this.updateState.bind(this), 100); + } + updateState() { + // Update the state + if (current !== this.state.current) { + this.setState({ + current + }); + } + } + render() { + if (this.state.current === this.props.id) { + return ( +
+ {this.props.children} +
+ ); + } else { + return ( +
+ ); + } + + } +} + +Tabs.propTypes = { + children: PropTypes.object, + defaultTab: PropTypes.number +}; + +Tab.propTypes = { + children: PropTypes.object, + id: PropTypes.number.isRequired +}; + +TabsHeader.propTypes = { + children: PropTypes.object, + count: PropTypes.number.isRequired +}; + +TabsBody.propTypes = { + children: PropTypes.object, + id: PropTypes.number.isRequired +}; diff --git a/client/base/reducers/Notifications.js b/client/base/reducers/Notifications.js index d4979b1..7daa3ca 100644 --- a/client/base/reducers/Notifications.js +++ b/client/base/reducers/Notifications.js @@ -2,7 +2,7 @@ import { ADD_NOTIFY, REMOVE_NOTIFY, REMOVE_ALL_NOTIFY } from '../util/constants'; // ID for next notification -let nextNotificationId = 1; +let nextNotificationId = 0; // Reducer export const notifications = (state = [], action) => { @@ -17,7 +17,8 @@ export const notifications = (state = [], action) => { return n.id !== action.notification.id; }); case REMOVE_ALL_NOTIFY: - return []; + nextNotificationId = 0; + return []; default: return state; diff --git a/client/sass/notifications.scss b/client/sass/notifications.scss index 56e945a..7e7e306 100644 --- a/client/sass/notifications.scss +++ b/client/sass/notifications.scss @@ -15,7 +15,6 @@ // Sidebar .notifications-bar { - overflow-y: scroll; @media screen and (min-width: 845px) { width: 300px; } @@ -91,3 +90,9 @@ $dismiss-notify-pad-top: 6px; padding-top: $dismiss-notify-pad-top + 2px !important; opacity: 0.5; } + +// Notifications +.notifications-bar-body { + overflow-y: scroll; + height: 100%; +} diff --git a/client/sass/tabs.scss b/client/sass/tabs.scss new file mode 100644 index 0000000..0604f86 --- /dev/null +++ b/client/sass/tabs.scss @@ -0,0 +1,39 @@ +// Styles for tabs +@import 'dashboard'; + +.tabs-header { + height: 35px; + width: 100%; + border-bottom-color: $navbar-text; + border-bottom-style: solid; + border-bottom-width: 1px; + text-align: center; + ul { + height: 100%; + width: 100%; + padding: 0; + list-style-type: none; + li { + float: left; + border-right-color: $navbar-text; + border-right-style: solid; + border-right-width: 1px; + height: 100% !important; + width: 50%; + button { + height: 100%; + width: 100%; + border: 0; + background: transparent; + } + } + li:hover { + background-color: $navbar-background-hover; + } + } +} + +// Wrapper around tabs body +.tab-wrap { + height: 100%; +} diff --git a/package.json b/package.json index 848e136..5862a1e 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/Gum-Joe/retis-ci.git" + "url": "git+https://github.com/Gum-Joe/bedel.git" }, "keywords": [ "retis", @@ -27,15 +27,15 @@ "author": "Gum-Joe", "license": "MIT", "bugs": { - "url": "https://github.com/Gum-Joe/retis-ci/issues" + "url": "https://github.com/Gum-Joe/bedel/issues" }, - "homepage": "https://github.com/Gum-Joe/retis-ci#readme", + "homepage": "https://github.com/Gum-Joe/bedel#readme", "engines": { "node": ">=6.2.0" }, "dependencies": { "@fdaciuk/ajax": "^2.1.2", - "babel-cli": "^6.9.0", + "babel-polyfill": "^6.9.1", "bcryptjs": "^2.3.0", "body-parser": "^1.15.2", "bootstrap": "^3.3.6", @@ -84,9 +84,9 @@ }, "devDependencies": { "atom-ui-reporter": "0.0.1", + "babel-cli": "^6.9.0", "babel-core": "^6.10.4", "babel-loader": "^6.2.4", - "babel-polyfill": "^6.9.1", "babel-preset-es2015": "^6.9.0", "babel-preset-es2015-loose": "^7.0.0", "babel-preset-react": "^6.11.1",