From ebde47b66f4eba44b36013ceb8d8999d8c05bddb Mon Sep 17 00:00:00 2001 From: Kirill Shumilov Date: Tue, 5 Dec 2017 17:45:34 +0300 Subject: [PATCH 1/3] feat(xod-client): add scss with new set of colors --- .../src/core/styles/abstracts/colors.scss | 117 ++++++++++++++++++ .../src/core/styles/abstracts/variables.scss | 2 + 2 files changed, 119 insertions(+) create mode 100644 packages/xod-client/src/core/styles/abstracts/colors.scss diff --git a/packages/xod-client/src/core/styles/abstracts/colors.scss b/packages/xod-client/src/core/styles/abstracts/colors.scss new file mode 100644 index 00000000..d4472d87 --- /dev/null +++ b/packages/xod-client/src/core/styles/abstracts/colors.scss @@ -0,0 +1,117 @@ +// Monochromes +$black: #000; +$white: #fff; + +$coal: #222; +$coal-light: #252525; +$coal-bright: #2b2b2b; + +$dark: #363636; +$dark-deep: #303030; +$dark-light: #3e3e3e; +$dark-bright: #404040; + +$grey: #444; +$grey-light: #555; +$grey-bright: #666; + +$light-grey: #777; +$light-grey-bright: #999; + +$chalk: #ccc; +$chalk-light: #aaa; +$chalk-bright: #eee; + +// Colored +$blue: #2d83b4; +$magenta: #9f5497; +$cyan: #4ed5ed; +$yellow: #f6dd0d; + +$red: #c33d3d; +$red-bright: #fd6161; + +$green: #579205; +$green-bright: #86cc23; + +$violet: #6965bd; +$violet-light: #7570cb; +$violet-shadow: #5c59a6; + +$orange: #c76530; +$orange-light: #d9733c; +$orange-shadow: #af592a; + +$blackberry: #4a4a57; +$blackberry-light: #616077; +$blackberry-shadow: #8c4a85; + +$olive: #827a22; +$olive-light: #988f34; +$olive-shadow: #726b1e; + +$emerald: #2e8f6c; +$emerald-light: #3fa37e; +$emerald-shadow: #287e5f; + +$apricot: #d8a53a; +$apricot-light: #e9b445; +$apricot-shadow: #be9133; + +$brown: #584b22; +$brown-light: #8e5f35; +$brown-shadow: #69421e; + +// Associated colors +$selected: $cyan; +$error: $red; + +$chrome-bg: $dark; +$chrome-title: $coal-bright; +$chrome-title-button-hover: $dark; +$chrome-outlines: $coal; + +$chrome-light-bg: $chalk; + +$menu-bg: $dark-deep; +$menu-hover: $dark-bright; +$menu-active: $coal; +$menu-item-bg: $coal; +$menu-item-hover: $dark-deep; +$menu-separator: $grey; + +$tab-bg: $coal; +$tab-item-bg: $dark-deep; +$tab-item-hover: $grey; +$tab-button-hover: $dark; + +$canvas: $dark-deep; +$canvas-outlines: $dark-light; + +$node-label: $chalk; +$node-outlines: $light-grey-bright; +$node-hover-outlines: $chalk; +$pin-bg: $coal-light; +$pin-label: $light-grey-bright; + +$input-bg: $chrome-bg; +$input-outlines: $light-grey; +$input-disabled-outlines: $dark-bright; +$input-disabled-text: $grey-light; +$input-active-bg: $grey-light; + +$dark-button-text: $coal; +$dark-button-bg: $chrome-bg; +$dark-button-outlines: $input-outlines; +$dark-button-hover-bg: $tab-item-hover; +$dark-button-hover-outlines: $grey-light; +$dark-button-disabled-text: $grey; +$dark-button-disabled-outlines: $grey; + +$light-button-text: $coal; +$light-button-bg: $chrome-light-bg; +$light-button-outlines: $input-outlines; +$light-button-hover-bg: $light-button-bg; +$light-button-hover-outlines: $grey-bright; +$light-button-disabled-text: rgba($light-button-text, .5); +$light-button-disabled-outlines: rgba($light-button-outlines, .5); diff --git a/packages/xod-client/src/core/styles/abstracts/variables.scss b/packages/xod-client/src/core/styles/abstracts/variables.scss index f19587c3..bc3ced2d 100644 --- a/packages/xod-client/src/core/styles/abstracts/variables.scss +++ b/packages/xod-client/src/core/styles/abstracts/variables.scss @@ -1,3 +1,5 @@ +@import 'colors'; + $font-family-normal: 'Roboto', sans-serif; $font-family-condensed: 'Roboto Condensed', sans-serif; $font-family-mono: 'Roboto Mono', monospace; From dca3c96dcdbed925a5b094da237bc1c9f1b336be Mon Sep 17 00:00:00 2001 From: Kirill Shumilov Date: Tue, 5 Dec 2017 21:26:06 +0300 Subject: [PATCH 2/3] feat(xod-client): implement CloseButton component --- .../src/core/components/CloseButton.jsx | 23 ++++++++ .../core/styles/components/CloseButton.scss | 52 +++++++++++++++++++ packages/xod-client/src/core/styles/main.scss | 1 + 3 files changed, 76 insertions(+) create mode 100644 packages/xod-client/src/core/components/CloseButton.jsx create mode 100644 packages/xod-client/src/core/styles/components/CloseButton.scss diff --git a/packages/xod-client/src/core/components/CloseButton.jsx b/packages/xod-client/src/core/components/CloseButton.jsx new file mode 100644 index 00000000..22c0789b --- /dev/null +++ b/packages/xod-client/src/core/components/CloseButton.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import cn from 'classnames'; +import PropTypes from 'prop-types'; + +const CloseButton = ({ + as = 'button', + className, + ...restProps +}) => React.createElement( + as, + { + ...restProps, + className: cn('CloseButton', className), + role: as !== 'button' ? 'button' : restProps.role, + } +); + +CloseButton.propTypes = { + as: PropTypes.string, + className: PropTypes.string, +}; + +export default CloseButton; diff --git a/packages/xod-client/src/core/styles/components/CloseButton.scss b/packages/xod-client/src/core/styles/components/CloseButton.scss new file mode 100644 index 00000000..bfd80edf --- /dev/null +++ b/packages/xod-client/src/core/styles/components/CloseButton.scss @@ -0,0 +1,52 @@ +.CloseButton { + position: absolute; + right: 0; + top: 0; + width: 32px; + height: 32px; + + background: transparent; + border: 0; + + cursor: pointer; + + line-height: 0; + font-size: $font-size-xs; + color: $chalk; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + outline: none; + + &:hover, &:focus { + outline: none; + + &:before { + background-color: $black; + } + } + + &:before { + content: ''; + position: absolute; + top: 6px; + right: 6px; + width: 18px; + height: 18px; + background-color: rgba($black, .5); + border-radius: 100%; + border: 0; + } + + &:after { + content: '╳'; + display: inline; + position: absolute; + top: 15px; + width: 18px; + right: 6px; + z-index: 2; + } +} diff --git a/packages/xod-client/src/core/styles/main.scss b/packages/xod-client/src/core/styles/main.scss index 6deae435..6033fb9e 100644 --- a/packages/xod-client/src/core/styles/main.scss +++ b/packages/xod-client/src/core/styles/main.scss @@ -18,6 +18,7 @@ 'components/BackgroundLayer', 'components/Breadcrumbs', 'components/Button', + 'components/CloseButton', 'components/Comment', 'components/CppImplementationEditor', 'components/Debugger', From 12ee4657cb9e95528c0d7321f43b28f15f5852d9 Mon Sep 17 00:00:00 2001 From: Kirill Shumilov Date: Tue, 5 Dec 2017 21:28:38 +0300 Subject: [PATCH 3/3] refactor(xod-client, xod-client-electron): restyle SnackBar messages, replace string messages with objects (created by function), that contains title, note and button --- packages/xod-client-electron/src/index.jsx | 2 + .../src/shared/composeMessage.js | 8 ++ .../src/shared/errorFormatter.js | 91 ++++++++++++--- .../src/shared/messages.js | 17 ++- .../xod-client-electron/src/view/actions.js | 7 +- .../src/view/autoupdate.js | 16 ++- .../src/view/autoupdateMiddleware.js | 12 ++ .../src/view/containers/App.jsx | 29 ++--- .../xod-client/src/core/containers/App.jsx | 3 +- .../src/core/styles/abstracts/variables.scss | 7 -- .../core/styles/components/SnackBarList.scss | 10 +- .../styles/components/SnackBarMessage.scss | 110 +++++++++++------- packages/xod-client/src/core/styles/main.scss | 1 + packages/xod-client/src/editor/actions.js | 22 ++-- packages/xod-client/src/editor/constants.js | 4 - .../src/editor/containers/Editor.jsx | 2 + packages/xod-client/src/editor/messages.js | 14 ++- packages/xod-client/src/index.js | 6 + .../xod-client/src/messages/actionTypes.js | 1 + packages/xod-client/src/messages/actions.js | 10 +- .../messages/components/SnackBarMessage.jsx | 76 +++++++----- .../xod-client/src/messages/composeMessage.js | 5 + packages/xod-client/src/messages/constants.js | 47 +------- .../src/messages/containers/SnackBar.jsx | 23 ++-- packages/xod-client/src/messages/reducer.js | 2 +- packages/xod-client/src/project/actions.js | 7 +- packages/xod-client/src/project/messages.js | 34 +++++- .../xod-client/src/projectBrowser/messages.js | 8 ++ packages/xod-client/src/user/messages.js | 8 +- .../xod-client/stories/SnackBarMessage.jsx | 76 ++++++++---- packages/xod-client/test/project.spec.js | 2 +- 31 files changed, 421 insertions(+), 239 deletions(-) create mode 100644 packages/xod-client-electron/src/shared/composeMessage.js create mode 100644 packages/xod-client-electron/src/view/autoupdateMiddleware.js create mode 100644 packages/xod-client/src/messages/composeMessage.js create mode 100644 packages/xod-client/src/projectBrowser/messages.js diff --git a/packages/xod-client-electron/src/index.jsx b/packages/xod-client-electron/src/index.jsx index 1b747726..212f2787 100644 --- a/packages/xod-client-electron/src/index.jsx +++ b/packages/xod-client-electron/src/index.jsx @@ -8,6 +8,7 @@ import popupsReducer from './popups/reducer'; import uploadReducer from './upload/reducer'; import debuggerMiddleware from './debugger/middleware'; +import autoupdateMiddleware from './view/autoupdateMiddleware'; import installLibMiddleware from './view/installLibMiddleware'; const extraReducers = { @@ -18,6 +19,7 @@ const extraReducers = { const extraMiddlewares = [ debuggerMiddleware, installLibMiddleware, + autoupdateMiddleware, ]; ReactDOM.render( diff --git a/packages/xod-client-electron/src/shared/composeMessage.js b/packages/xod-client-electron/src/shared/composeMessage.js new file mode 100644 index 00000000..10441641 --- /dev/null +++ b/packages/xod-client-electron/src/shared/composeMessage.js @@ -0,0 +1,8 @@ +// Duplicate of `composeMessage` from `xod-client` +// It's necessary to prevent errors with importing `xod-client`, +// that contains styles and react components. +export default (title, note = null, button = null) => ({ + title, + note, + button, +}); diff --git a/packages/xod-client-electron/src/shared/errorFormatter.js b/packages/xod-client-electron/src/shared/errorFormatter.js index 13acecb0..60b26990 100644 --- a/packages/xod-client-electron/src/shared/errorFormatter.js +++ b/packages/xod-client-electron/src/shared/errorFormatter.js @@ -5,42 +5,95 @@ import { ERROR_CODES as XFS_EC } from 'xod-fs'; // that contains async/await functions and non-isomorphic dependencies import { COMPILATION_ERRORS as XD_EC } from 'xod-deploy/dist/constants'; +import composeMessage from './composeMessage'; import * as EC from './errorCodes'; -const UNKNOWN_ERROR = err => `Unknown error occurred: ${err.message || JSON.stringify(err)}`; +const UNKNOWN_ERROR = err => composeMessage( + 'Unknown error occurred', + err.message || JSON.stringify(err) +); const ERROR_FORMATTERS = { + // Errors that will showed in the upload popup [EC.TRANSPILE_ERROR]: err => `Error occurred during transpilation: ${err}`, - [EC.PORT_NOT_FOUND]: err => `Could not find Arduino device on port: ${err.port.comName}. Available ports: ${R.map(R.prop('comName'), err.ports)}`, [EC.UPLOAD_ERROR]: err => `Error occured during uploading: ${err}`, [EC.INDEX_LIST_ERROR]: err => `Could not connect to Arduino Packages Index at ${err.request.path}: ${err.error.message}`, [EC.CANT_INSTALL_ARCHITECTURE]: err => `Could not install tools: ${err}`, - [XFS_EC.INVALID_WORKSPACE_PATH]: err => `Invalid workspace path: "${err.path}" of type "${typeof err.path}"`, - [XFS_EC.WORKSPACE_DIR_NOT_EMPTY]: err => `Workspace directory at ${err.path} is not empty`, - [XFS_EC.WORKSPACE_DIR_NOT_EXIST_OR_EMPTY]: err => `Workspace directory at ${err.path} not exist or empty`, + // Snackbar errors + [XFS_EC.INVALID_WORKSPACE_PATH]: err => composeMessage( + 'Invalid workspace path', + `"${err.path}" of type "${typeof err.path}"` + ), + [XFS_EC.WORKSPACE_DIR_NOT_EMPTY]: err => composeMessage( + `Workspace directory at ${err.path} is not empty` + ), + [XFS_EC.WORKSPACE_DIR_NOT_EXIST_OR_EMPTY]: err => composeMessage( + `Workspace directory at ${err.path} not exist or empty` + ), - [XFS_EC.INVALID_FILE_CONTENTS]: err => `Could not open selected project: invalid contents in ${err.path}`, + [XFS_EC.INVALID_FILE_CONTENTS]: err => composeMessage( + 'Canʼt open selected project', + `Invalid contents in ${err.path}` + ), - [XFS_EC.CANT_CREATE_WORKSPACE_FILE]: err => `Could not create workspace at ${err.path}: ${err.message}`, - [XFS_EC.CANT_COPY_STDLIB]: err => `Could not copy stdlib at ${err.path}: ${err.message}`, - [XFS_EC.CANT_COPY_DEFAULT_PROJECT]: err => `Could not copy default project at ${err.path}: ${err.message}`, - [XFS_EC.CANT_ENUMERATE_PROJECTS]: err => `Could not enumerate projects at ${err.path}: ${err.message}`, - [XFS_EC.CANT_SAVE_PROJECT]: err => `Could not save the project at ${err.path}: ${err.message}`, - [XFS_EC.CANT_SAVE_LIBRARY]: err => `Could not save the library at ${err.path}: ${err.message}`, + [XFS_EC.CANT_CREATE_WORKSPACE_FILE]: err => composeMessage( + `Could not create workspace at ${err.path}`, + err.message + ), + [XFS_EC.CANT_COPY_STDLIB]: err => composeMessage( + `Could not copy stdlib at ${err.path}`, + err.message + ), + [XFS_EC.CANT_COPY_DEFAULT_PROJECT]: err => composeMessage( + `Could not copy default project at ${err.path}`, + err.message + ), + [XFS_EC.CANT_ENUMERATE_PROJECTS]: err => composeMessage( + `Could not enumerate projects at ${err.path}`, + err.message + ), + [XFS_EC.CANT_SAVE_PROJECT]: err => composeMessage( + `Could not save the project at ${err.path}`, + err.message + ), + [XFS_EC.CANT_SAVE_LIBRARY]: err => composeMessage( + `Could not save the library at ${err.path}`, + err.message + ), - [EC.CANT_CREATE_NEW_PROJECT]: err => `Could not create a new project: ${err.message}`, - [EC.CANT_OPEN_SELECTED_PROJECT]: err => `Could not open selected project: ${err.message}`, + [EC.CANT_CREATE_NEW_PROJECT]: err => composeMessage( + 'Could not create a new project', + err.message + ), + [EC.CANT_OPEN_SELECTED_PROJECT]: err => composeMessage( + 'Could not open selected project', + err.message + ), [EC.BOARD_NOT_SUPPORTED]: err => `Board ${err.boardId} is not supported yet`, [EC.CANT_GET_UPLOAD_CONFIG]: err => `Could not get upload config for board ${err.boardId}. Returned status ${err.status} ${err.statusText}`, - [EC.CLOUD_NETWORK_ERROR]: err => `Could not connect to cloud service. Probably you donʼt have an internet connection. Error: ${err.message}`, + [EC.CLOUD_NETWORK_ERROR]: err => composeMessage( + 'Could not connect to cloud service.', + `Probably you donʼt have an internet connection. Error: ${err.message}` + ), - [XD_EC.COMPILE_FAILED]: err => `Compilation failed with error: ${err.message}`, - [XD_EC.COMPILE_TIMEDOUT]: err => `Compilation timed out (${err.message}ms expired)`, - [XD_EC.CLOSED]: err => `Connection was closed with reason (${err.closeCode}) ${err.reason}`, - [XD_EC.FAILED]: err => `Canʼt establish connection with compile server: (${err.code}) ${err.message}`, + [XD_EC.COMPILE_FAILED]: err => composeMessage( + 'Compilation failed with error', + err.message + ), + [XD_EC.COMPILE_TIMEDOUT]: err => composeMessage( + `Compilation timed out (${err.message}ms expired)` + ), + [XD_EC.CLOSED]: err => composeMessage( + `Connection was closed with reason (${err.closeCode})`, + err.reason + ), + [XD_EC.FAILED]: err => composeMessage( + `Canʼt establish connection with compile server: (${err.code})`, + err.message + ), }; // :: Error -> String diff --git a/packages/xod-client-electron/src/shared/messages.js b/packages/xod-client-electron/src/shared/messages.js index 10b8bea7..ca3ef06d 100644 --- a/packages/xod-client-electron/src/shared/messages.js +++ b/packages/xod-client-electron/src/shared/messages.js @@ -1,3 +1,4 @@ +import composeMessage from './composeMessage'; import * as EVENTS from './events'; export const CODE_TRANSPILED = 'Project was successfully transpiled. Searching for device...'; @@ -18,6 +19,18 @@ export const SAVE_ALL_PROCESSED = ''; export const SAVE_ALL_FAILED = 'Failed to save project.'; export const SAVE_ALL_SUCCEED = 'Saved successfully!'; -export const DEBUG_SESSION_STOPPED_ON_CHANGE = 'Debug session was automatically stopped, cause your Project has been changed.'; -export const DEBUG_SESSION_STOPPED_ON_TAB_CLOSE = 'Debug session was automatically stopped, cause you closed Debugger tab.'; +export const DEBUG_SESSION_STOPPED_ON_CHANGE = composeMessage( + 'Debug session stopped', + 'Your Project has been changed.' +); +export const DEBUG_SESSION_STOPPED_ON_TAB_CLOSE = composeMessage( + 'Debug session stopped', + 'You closed Debugger tab.' +); export const DEBUG_LOST_CONNECTION = 'Lost connection with the device.'; + +export const updateAvailableMessage = version => composeMessage( + 'Update available', + `New version ${version} of XOD\u00A0IDE is available`, + 'Download & Install' +); diff --git a/packages/xod-client-electron/src/view/actions.js b/packages/xod-client-electron/src/view/actions.js index aab3f520..f101d00d 100644 --- a/packages/xod-client-electron/src/view/actions.js +++ b/packages/xod-client-electron/src/view/actions.js @@ -10,6 +10,7 @@ import { addConfirmation, addError, SAVE_ALL, + composeMessage, } from 'xod-client'; import * as EVENTS from '../shared/events'; import * as MESSAGES from '../shared/messages'; @@ -91,7 +92,9 @@ export const createAsyncAction = ({ ); } if (notify) { - dispatch(addConfirmation(completeMsg)); + dispatch(addConfirmation( + composeMessage(completeMsg) + )); } onComplete(payload, dispatch); @@ -119,7 +122,7 @@ export const createAsyncAction = ({ ); } if (notify) { - dispatch(addError(errorMsg)); + dispatch(addError(composeMessage(errorMsg))); } onError(err, dispatch); diff --git a/packages/xod-client-electron/src/view/autoupdate.js b/packages/xod-client-electron/src/view/autoupdate.js index cebfc804..35766461 100644 --- a/packages/xod-client-electron/src/view/autoupdate.js +++ b/packages/xod-client-electron/src/view/autoupdate.js @@ -1,5 +1,8 @@ import R from 'ramda'; import * as EVENTS from '../shared/events'; +import { updateAvailableMessage } from '../shared/messages'; + +export const UPDATE_IDE_MESSAGE_ID = 'updateIde'; // :: ipcRenderer -> AppContainer -> () export const subscribeAutoUpdaterEvents = (ipcRenderer, App) => { @@ -15,15 +18,9 @@ export const subscribeAutoUpdaterEvents = (ipcRenderer, App) => { (event, info) => { console.log('Update available: ', info); // eslint-disable-line no-console App.props.actions.addNotification( - `New version (${info.version}) of XOD\u00A0IDE is available`, - [{ - id: 'downloadAndInstall', - text: 'Download & Install', - }, { - id: 'dismiss', - text: 'Skip', - }], - true + updateAvailableMessage(info.version), + true, + UPDATE_IDE_MESSAGE_ID ); } ); @@ -42,6 +39,7 @@ export const subscribeAutoUpdaterEvents = (ipcRenderer, App) => { ipcRenderer.on( EVENTS.APP_UPDATE_DOWNLOAD_STARTED, () => { + App.setState(R.assoc('downloadProgressPopup', true)); console.log('Download has been started!'); // eslint-disable-line no-console } ); diff --git a/packages/xod-client-electron/src/view/autoupdateMiddleware.js b/packages/xod-client-electron/src/view/autoupdateMiddleware.js new file mode 100644 index 00000000..bb85f7fb --- /dev/null +++ b/packages/xod-client-electron/src/view/autoupdateMiddleware.js @@ -0,0 +1,12 @@ +import client from 'xod-client'; +import { ipcRenderer } from 'electron'; + +import { UPDATE_IDE_MESSAGE_ID, downloadUpdate } from './autoupdate'; + +export default () => next => (action) => { + if (action.type === client.MESSAGE_BUTTON_CLICKED && action.payload === UPDATE_IDE_MESSAGE_ID) { + downloadUpdate(ipcRenderer); + } + + return next(action); +}; diff --git a/packages/xod-client-electron/src/view/containers/App.jsx b/packages/xod-client-electron/src/view/containers/App.jsx index b6a21dee..68db7308 100644 --- a/packages/xod-client-electron/src/view/containers/App.jsx +++ b/packages/xod-client-electron/src/view/containers/App.jsx @@ -37,7 +37,7 @@ import * as EVENTS from '../../shared/events'; import * as MESSAGES from '../../shared/messages'; import { createSystemMessage } from '../../shared/debuggerMessages'; -import { subscribeAutoUpdaterEvents, downloadUpdate } from '../autoupdate'; +import { subscribeAutoUpdaterEvents } from '../autoupdate'; import { subscribeToTriggerMainMenuRequests } from '../../testUtils/triggerMainMenu'; const { app, dialog, Menu } = remoteElectron; @@ -85,8 +85,6 @@ class App extends client.App { this.getSidebarPaneHeight = this.getSidebarPaneHeight.bind(this); this.setSidebarPaneHeight = this.setSidebarPaneHeight.bind(this); - this.onClickMessageButton = this.onClickMessageButton.bind(this); - this.onUploadToArduinoClicked = this.onUploadToArduinoClicked.bind(this); this.onUploadToArduino = this.onUploadToArduino.bind(this); this.onArduinoTargetBoardChange = this.onArduinoTargetBoardChange.bind(this); @@ -184,13 +182,6 @@ class App extends client.App { props.actions.fetchGrant(); } - onClickMessageButton(buttonId, /* messageInfo */) { - if (buttonId === 'downloadAndInstall') { - downloadUpdate(ipcRenderer); - this.setState(R.assoc('downloadProgressPopup', true)); - } - } - onResize() { this.setState( R.set( @@ -238,7 +229,9 @@ class App extends client.App { proc.success(payload.message); if (debug) { foldEither( - error => this.props.actions.addError(error.message), + error => this.props.actions.addError( + client.composeMessage(error.message) + ), (nodeIdsMap) => { this.props.actions.startDebuggerSession( createSystemMessage('Debug session started'), @@ -271,7 +264,7 @@ class App extends client.App { this.props.actions.createProject(projectName); ipcRenderer.send(EVENTS.CREATE_PROJECT, projectName); ipcRenderer.once(EVENTS.SAVE_ALL, () => { - this.props.actions.addConfirmation(MESSAGES.PROJECT_SAVE_SUCCEED); + this.props.actions.addConfirmation(MESSAGES.SAVE_ALL_SUCCEED); }); } @@ -303,7 +296,9 @@ class App extends client.App { fs.readFile(filePaths[0], 'utf8', (err, data) => { if (err) { - this.props.actions.addError(err.message); + this.props.actions.addError( + client.composeMessage(err.message) + ); } this.onImport(data); @@ -337,7 +332,10 @@ class App extends client.App { onImport(jsonString) { foldEither( - this.props.actions.addError, + R.compose( + this.props.actions.addError, + client.composeMessage + ), (project) => { if (!this.confirmUnsavedChanges()) return; this.props.actions.importProject(project); @@ -680,9 +678,6 @@ class App extends client.App { setSidebarPaneHeight={this.setSidebarPaneHeight} stopDebuggerSession={() => debuggerIPC.sendStopDebuggerSession(ipcRenderer)} /> - {this.renderPopupShowCode()} {this.renderPopupUploadConfig()} {this.renderPopupUploadProcess()} diff --git a/packages/xod-client/src/core/containers/App.jsx b/packages/xod-client/src/core/containers/App.jsx index 2703ab33..ce416d59 100644 --- a/packages/xod-client/src/core/containers/App.jsx +++ b/packages/xod-client/src/core/containers/App.jsx @@ -23,6 +23,7 @@ import PopupProjectPreferences from '../../project/components/PopupProjectPrefer import PopupPublishProject from '../../project/components/PopupPublishProject'; import * as actions from '../actions'; +import composeMessage from '../../messages/composeMessage'; export default class App extends React.Component { componentDidMount() { @@ -37,7 +38,7 @@ export default class App extends React.Component { const eitherCode = eitherTProject.map(transpile); return foldEither( - error => this.props.actions.addError(error.message), + error => this.props.actions.addError(composeMessage(error.message)), this.props.actions.showCode, eitherCode ); diff --git a/packages/xod-client/src/core/styles/abstracts/variables.scss b/packages/xod-client/src/core/styles/abstracts/variables.scss index bc3ced2d..505de756 100644 --- a/packages/xod-client/src/core/styles/abstracts/variables.scss +++ b/packages/xod-client/src/core/styles/abstracts/variables.scss @@ -1,5 +1,3 @@ -@import 'colors'; - $font-family-normal: 'Roboto', sans-serif; $font-family-condensed: 'Roboto Condensed', sans-serif; $font-family-mono: 'Roboto Mono', monospace; @@ -59,7 +57,6 @@ $color-tabs-hover-text: #FFF; $color-tabs-debugger-background: #3d4931; $color-tabs-debugger-text: #82b948; -$snackbar-width: 220px; $sidebar-width: 200px; $sidebar-color-bg: #3d3d3d; @@ -95,7 +92,3 @@ $button-fg-color-light: black; $modal-fg-color: #cccccc; $modal-fg-color-light: black; $modal-bg-color-light: #cccccc; - -$message-border-color: #818181; -$message-text-color: #e5e5e5; -$message-bg-color: #3d3d3d; diff --git a/packages/xod-client/src/core/styles/components/SnackBarList.scss b/packages/xod-client/src/core/styles/components/SnackBarList.scss index 5327dcca..87cdd406 100644 --- a/packages/xod-client/src/core/styles/components/SnackBarList.scss +++ b/packages/xod-client/src/core/styles/components/SnackBarList.scss @@ -1,8 +1,10 @@ .SnackBarList { - position: fixed; + position: absolute; z-index: 9999; - top: 37px; // Height of a top bar. TODO: variable? - right: 7px; + left: 0; + right: 0; + bottom: 0; + width: 100%; margin: 0; - width: $snackbar-width; + padding: 0; } diff --git a/packages/xod-client/src/core/styles/components/SnackBarMessage.scss b/packages/xod-client/src/core/styles/components/SnackBarMessage.scss index 8b1bd290..0fd2f1aa 100644 --- a/packages/xod-client/src/core/styles/components/SnackBarMessage.scss +++ b/packages/xod-client/src/core/styles/components/SnackBarMessage.scss @@ -1,58 +1,82 @@ .SnackBarMessage { position: relative; display: none; - margin-top: 7px; - transition: all 0.3s ease-in; - left: 0; + transition: all 0.1s ease-in; + opacity: 1; - &.hidden { - left: $snackbar-width; - } + font-family: $font-family-normal; + font-weight: 300; + background: $chrome-bg; + color: $chalk; + border-top: 1px solid $chrome-outlines; + min-height: 60px; + box-sizing: border-box; - &.display { - display: block; - } + &.hidden { opacity: 0; } + &.display { display: block; } - &.error { - a { - color: $color-error; - border-color: $color-error; - } - } - - &.confirmation { - a { - border-color: $message-border-color; - } - } - - &.notification { - a { - border-color: $color-success; - } - } - - a { - display: block; - padding: 11px 11px; - color: $message-text-color; - background-color: $message-bg-color; - box-shadow: 0 0 7px rgba(0,0,0,.5); - - border: 2px solid $message-border-color; - border-radius: 6px; + .message-content { + display: flex; + width: 70%; + padding: 10px 0; + margin: 0 auto; cursor: default; outline: none; - - .message-content { margin: 0; } } - - .SnackBar-buttons-container { - margin-top: 7px; + .message-text { + width: 65%; + margin: 0; + color: $white; + font-size: $font-size-m; + line-height: 1.4em; + padding-right: 3em; + opacity: 0.9; + } + .message-buttons { + width: 35%; + display: flex; + flex-direction: row; + align-items: center; .Button { - margin-right: 5px; + display: block; + width: 100%; + border: none; + } + } + + .title { + display: block; + font-family: $font-family-condensed; + font-size: $font-size-l; + font-weight: 400; + margin-bottom: .5em; + } + + &.persistent { + color: $white !important; + .title { color: $white !important; } + } + + &.error { + .title { color: $red-bright; } + + &.persistent { background: $error; } + } + + &.confirmation { + .title { color: $green-bright; } + + &.persistent { + background: $green; + } + } + &.notification { + .title { color: $cyan; } + + &.persistent { + background: $blue; } } } diff --git a/packages/xod-client/src/core/styles/main.scss b/packages/xod-client/src/core/styles/main.scss index 6033fb9e..827028f3 100644 --- a/packages/xod-client/src/core/styles/main.scss +++ b/packages/xod-client/src/core/styles/main.scss @@ -1,4 +1,5 @@ @import + 'abstracts/colors', 'abstracts/variables', 'abstracts/functions', 'abstracts/mixins'; diff --git a/packages/xod-client/src/editor/actions.js b/packages/xod-client/src/editor/actions.js index 4ed5bfa1..71d72098 100644 --- a/packages/xod-client/src/editor/actions.js +++ b/packages/xod-client/src/editor/actions.js @@ -7,11 +7,13 @@ import { fetchLibsWithDependencies, stringifyLibQuery, getLibName } from 'xod-pm import { SELECTION_ENTITY_TYPE, CLIPBOARD_DATA_TYPE, - CLIPBOARD_ERRORS as CLIPBOARD_ERROR_CODES, } from './constants'; -import { LINK_ERRORS, CLIPBOARD_ERRORS } from '../messages/constants'; - -import { libInstalled } from './messages'; +import { LINK_ERRORS } from '../project/messages'; +import { + libInstalled, + CLIPBOARD_RECURSION_PASTE_ERROR, + clipboardMissingPatchPasteError, +} from './messages'; import * as ActionType from './actionTypes'; @@ -25,6 +27,7 @@ import { addError, addConfirmation, } from '../messages/actions'; +import composeMessage from '../messages/composeMessage'; import * as Selectors from './selectors'; import * as ProjectSelectors from '../project/selectors'; @@ -345,7 +348,7 @@ export const pasteEntities = event => (dispatch, getState) => { const pastedNodeTypes = R.map(XP.getNodeType, copiedEntities.nodes); if (R.contains(currentPatchPath, pastedNodeTypes)) { - dispatch(addError(CLIPBOARD_ERRORS[CLIPBOARD_ERROR_CODES.RECURSION_DETECTED])); + dispatch(addError(CLIPBOARD_RECURSION_PASTE_ERROR)); return; } @@ -358,10 +361,9 @@ export const pasteEntities = event => (dispatch, getState) => { const missingPatches = R.without(availablePatches, pastedNodeTypes); if (!R.isEmpty(missingPatches)) { - dispatch(addError(XP.formatString( - CLIPBOARD_ERRORS[CLIPBOARD_ERROR_CODES.NO_REQUIRED_PATCHES], - { missingPatches: missingPatches.join(', ') } - ))); + dispatch(addError( + clipboardMissingPatchPasteError(missingPatches.join(', ')) + )); return; } @@ -472,6 +474,6 @@ export const installLibraries = libParams => (dispatch, getState) => { errorCode: err.errorCode, }, }); - dispatch(addError(err.message)); + dispatch(addError(composeMessage(err.message))); }); }; diff --git a/packages/xod-client/src/editor/constants.js b/packages/xod-client/src/editor/constants.js index 9b0d1a55..f2b71c0f 100644 --- a/packages/xod-client/src/editor/constants.js +++ b/packages/xod-client/src/editor/constants.js @@ -48,10 +48,6 @@ export const LINK_ERRORS = { INCOMPATIBLE_TYPES: 'INCOMPATIBLE_TYPES', }; -export const PROPERTY_ERRORS = { - PIN_HAS_LINK: 'PIN_HAS_LINK', -}; - export const CLIPBOARD_ERRORS = { RECURSION_DETECTED: 'RECURSION_DETECTED', NO_REQUIRED_PATCHES: 'NO_REQUIRED_PATCHES', diff --git a/packages/xod-client/src/editor/containers/Editor.jsx b/packages/xod-client/src/editor/containers/Editor.jsx index 0cc0f290..b5ad26a5 100644 --- a/packages/xod-client/src/editor/containers/Editor.jsx +++ b/packages/xod-client/src/editor/containers/Editor.jsx @@ -33,6 +33,7 @@ import Debugger from '../../debugger/containers/Debugger'; import Breadcrumbs from '../../debugger/containers/Breadcrumbs'; import Sidebar from '../components/Sidebar'; import Workarea from '../../utils/components/Workarea'; +import SnackBar from '../../messages/containers/SnackBar'; import { RenderableSelection } from '../../types'; import sanctuaryPropType from '../../utils/sanctuaryPropType'; @@ -235,6 +236,7 @@ class Editor extends React.Component { {BreadcrumbsContainer} {this.renderOpenedImplementationEditorTabs()} {DebuggerContainer} + diff --git a/packages/xod-client/src/editor/messages.js b/packages/xod-client/src/editor/messages.js index 18689fc9..956b09d9 100644 --- a/packages/xod-client/src/editor/messages.js +++ b/packages/xod-client/src/editor/messages.js @@ -1,6 +1,18 @@ +import composeMessage from '../messages/composeMessage'; + export const PATCH_FOR_NODE_IS_MISSING = 'Patch for this node is missing.'; -export const libInstalled = (libName, version) => `${libName} @ ${version} installed successfully`; +export const libInstalled = (libName, version) => composeMessage( + `${libName} @ ${version} installed successfully` +); + +export const CLIPBOARD_RECURSION_PASTE_ERROR = composeMessage( + 'Canʼt paste a patch into itself' +); +export const clipboardMissingPatchPasteError = missingPatches => composeMessage( + 'Canʼt paste', + `Canʼt find following patches: ${missingPatches}` +); export const LIB_SUGGESTER_TYPE_TO_BEGIN = 'Type owner/libname to find a library'; export const LIB_SUGGESTER_NOTHING_FOUND = 'No library found'; diff --git a/packages/xod-client/src/index.js b/packages/xod-client/src/index.js index d6f5fbab..1c58fd8e 100644 --- a/packages/xod-client/src/index.js +++ b/packages/xod-client/src/index.js @@ -15,6 +15,7 @@ import * as ProjectBrowserActions from './projectBrowser/actions'; import * as PopupActions from './popups/actions'; import * as DebuggerActions from './debugger/actions'; +import { MESSAGE_BUTTON_CLICKED } from './messages/actionTypes'; import { TAB_CLOSE, INSTALL_LIBRARIES_COMPLETE } from './editor/actionTypes'; import { SAVE_ALL } from './project/actionTypes'; @@ -33,6 +34,7 @@ import App from './core/containers/App'; import Root from './core/containers/Root'; import { container as Editor, CreateNodeWidget } from './editor'; import SnackBar from './messages'; +import composeMessage from './messages/composeMessage'; import * as MessageConstants from './messages/constants'; import Toolbar from './utils/components/Toolbar'; import PopupShowCode from './utils/components/PopupShowCode'; @@ -53,6 +55,7 @@ export * from './projectBrowser/actions'; export * from './popups/actions'; export * from './debugger/actions'; +export { MESSAGE_BUTTON_CLICKED } from './messages/actionTypes'; export { TAB_CLOSE, INSTALL_LIBRARIES_COMPLETE } from './editor/actionTypes'; export { SAVE_ALL } from './project/actionTypes'; @@ -82,6 +85,7 @@ export { default as App } from './core/containers/App'; export { default as Root } from './core/containers/Root'; export { container as Editor, CreateNodeWidget } from './editor'; export { default as SnackBar } from './messages'; +export { default as composeMessage } from './messages/composeMessage'; export * from './messages/constants'; export { default as initialState } from './core/state'; @@ -109,9 +113,11 @@ export default Object.assign({ PopupProjectPreferences, hasUnsavedChanges, getLastSavedProject, + composeMessage, TAB_CLOSE, SAVE_ALL, INSTALL_LIBRARIES_COMPLETE, + MESSAGE_BUTTON_CLICKED, }, UserSelectors, EditorSelectors, diff --git a/packages/xod-client/src/messages/actionTypes.js b/packages/xod-client/src/messages/actionTypes.js index 36184a2c..5ec6ed45 100644 --- a/packages/xod-client/src/messages/actionTypes.js +++ b/packages/xod-client/src/messages/actionTypes.js @@ -1,2 +1,3 @@ export const MESSAGE_ADD = 'MESSAGE_ADD'; export const MESSAGE_DELETE = 'MESSAGE_DELETE'; +export const MESSAGE_BUTTON_CLICKED = 'MESSAGE_BUTTON_CLICKED'; diff --git a/packages/xod-client/src/messages/actions.js b/packages/xod-client/src/messages/actions.js index 122a3172..b30561ee 100644 --- a/packages/xod-client/src/messages/actions.js +++ b/packages/xod-client/src/messages/actions.js @@ -4,10 +4,11 @@ import { STATUS } from '../utils/constants'; const getTimestamp = () => new Date().getTime(); -export const addMessage = (type, message, buttons = [], persistent = false) => ({ +export const addMessage = (type, messageData, persistent = false, id = null) => ({ type: ActionType.MESSAGE_ADD, - payload: { message, buttons }, + payload: messageData, meta: { + id, type, persistent, timestamp: getTimestamp(), @@ -29,3 +30,8 @@ export const deleteMessage = id => ({ export const addError = (...args) => addMessage(MESSAGE_TYPE.ERROR, ...args); export const addConfirmation = (...args) => addMessage(MESSAGE_TYPE.CONFIRMATION, ...args); export const addNotification = (...args) => addMessage(MESSAGE_TYPE.NOTIFICATION, ...args); + +export const messageButtonClick = messageId => ({ + type: ActionType.MESSAGE_BUTTON_CLICKED, + payload: messageId, +}); diff --git a/packages/xod-client/src/messages/components/SnackBarMessage.jsx b/packages/xod-client/src/messages/components/SnackBarMessage.jsx index 8d3b88b2..ceb61343 100644 --- a/packages/xod-client/src/messages/components/SnackBarMessage.jsx +++ b/packages/xod-client/src/messages/components/SnackBarMessage.jsx @@ -3,8 +3,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { MESSAGE_TYPE } from '../constants'; +import Button from '../../core/components/Button'; +import CloseButton from '../../core/components/CloseButton'; -const ANIMATION_TIMEOUT = 500; +const ANIMATION_TIMEOUT = 100; class SnackBarMessage extends React.Component { constructor(props) { @@ -16,6 +18,7 @@ class SnackBarMessage extends React.Component { }; this.hide = this.hide.bind(this); + this.onCloseMessage = this.onCloseMessage.bind(this); } componentDidMount() { @@ -24,35 +27,37 @@ class SnackBarMessage extends React.Component { }); } + onCloseMessage() { + this.props.onCloseMessage(this.props.message.id); + } + getMessageContent() { const { message, onClickMessageButton } = this.props; - const buttons = R.unless( - R.isEmpty, - R.compose( - btns => React.createElement( - 'div', - { className: 'SnackBar-buttons-container' }, - btns - ), - R.map(({ id, text }) => ( - - )) + const button = R.unless( + R.isNil, + text => ( + ) - )(message.payload.buttons); + )(message.payload.button); - return ( -
- {message.payload.message} - {buttons} -
- ); + return [ +
+ + {message.payload.title} + + {message.payload.note} +
, +
+ {button} +
, + ]; } setHidden(val) { @@ -84,6 +89,7 @@ class SnackBarMessage extends React.Component { error: message.type === MESSAGE_TYPE.ERROR, confirmation: message.type === MESSAGE_TYPE.CONFIRMATION, notification: message.type === MESSAGE_TYPE.NOTIFICATION, + persistent: message.persistent, }); const messageContent = this.getMessageContent(); @@ -91,7 +97,11 @@ class SnackBarMessage extends React.Component {
  • - + + {messageContent}
  • @@ -102,19 +112,21 @@ class SnackBarMessage extends React.Component { SnackBarMessage.propTypes = { message: PropTypes.shape({ /* eslint-disable react/no-unused-prop-types */ - id: PropTypes.number, + id: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), type: PropTypes.string, persistent: PropTypes.bool, payload: PropTypes.shape({ - message: PropTypes.string.isRequired, - buttons: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string, - text: PropTypes.string, - })).isRequired, + title: PropTypes.string.isRequired, + note: PropTypes.string, + button: PropTypes.string, }), /* eslint-enable react/no-unused-prop-types */ }), onClickMessageButton: PropTypes.func, + onCloseMessage: PropTypes.func.isRequired, }; SnackBarMessage.defaultProps = { diff --git a/packages/xod-client/src/messages/composeMessage.js b/packages/xod-client/src/messages/composeMessage.js new file mode 100644 index 00000000..ace7f620 --- /dev/null +++ b/packages/xod-client/src/messages/composeMessage.js @@ -0,0 +1,5 @@ +export default (title, note = null, button = null) => ({ + title, + note, + button, +}); diff --git a/packages/xod-client/src/messages/constants.js b/packages/xod-client/src/messages/constants.js index d5682395..a9c00d4b 100644 --- a/packages/xod-client/src/messages/constants.js +++ b/packages/xod-client/src/messages/constants.js @@ -1,51 +1,6 @@ -import { - LINK_ERRORS as LE, - NODETYPE_ERROR_TYPES as NTE, - PROPERTY_ERRORS as PE, - CLIPBOARD_ERRORS as CE, -} from '../editor/constants'; - +// eslint-disable-next-line import/prefer-default-export export const MESSAGE_TYPE = { ERROR: 'ERROR', CONFIRMATION: 'CONFIRMATION', NOTIFICATION: 'NOTIFICATION', }; - -export const LINK_ERRORS = { - [LE.SAME_DIRECTION]: 'Can`t create link between pins of the same direction!', - [LE.SAME_NODE]: 'Can`t create link between pins of the same node!', - [LE.ONE_LINK_FOR_INPUT_PIN]: 'Input pin can have only one link!', - [LE.UNKNOWN_ERROR]: 'Unknown error!', - [LE.INCOMPATIBLE_TYPES]: 'Incompatible pin types!', -}; - -export const PROJECT_BROWSER_ERRORS = { - CANT_OPEN_LIBPATCH_WITHOUT_XOD_IMPL: 'This patch has only native implementation and can`t be opened', - CANT_DELETE_CURRENT_PATCH: 'Current patch cannot been deleted. Switch to another patch before!', - PATCH_NAME_TAKEN: 'This patch name is already taken!', - INVALID_PATCH_NAME: 'Invalid patch name', - INVALID_PROJECT_NAME: 'Invalid project name', -}; - -export const NODETYPE_ERRORS = { - [NTE.CANT_DELETE_USED_PATCHNODE]: ( - 'Current Patch Node is used somewhere. You should remove it first!' - ), - [NTE.CANT_DELETE_USED_PIN_OF_PATCHNODE]: [ - 'Current IO Node is represents a Pin of Patch Node.', - 'And it is used somewhere.', - 'You should remove a linkage first!', - ].join(' '), -}; - -export const PROPERTY_ERRORS = { - [PE.PIN_HAS_LINK]: [ - 'Can`t convert a pin into property, because it has a connected links.', - 'You should remove links first!', - ].join(' '), -}; - -export const CLIPBOARD_ERRORS = { - [CE.RECURSION_DETECTED]: 'Can`t paste a patch into itself!', - [CE.NO_REQUIRED_PATCHES]: 'Can`t find following patches: {missingPatches}', -}; diff --git a/packages/xod-client/src/messages/containers/SnackBar.jsx b/packages/xod-client/src/messages/containers/SnackBar.jsx index 1ddb7cfa..faecb426 100644 --- a/packages/xod-client/src/messages/containers/SnackBar.jsx +++ b/packages/xod-client/src/messages/containers/SnackBar.jsx @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; import SnackBarList from '../components/SnackBarList'; import SnackBarMessage from '../components/SnackBarMessage'; import * as ErrorSelectors from '../selectors'; -import { deleteMessage } from '../actions'; +import { deleteMessage, messageButtonClick } from '../actions'; const ERROR_TIMEOUT = 3000; @@ -24,6 +24,7 @@ class SnackBar extends React.Component { this.onMouseOver = this.onMouseOver.bind(this); this.onMouseOut = this.onMouseOut.bind(this); this.onButtonClicked = this.onButtonClicked.bind(this); + this.onCloseMessage = this.onCloseMessage.bind(this); } componentWillReceiveProps(nextProps) { @@ -48,9 +49,13 @@ class SnackBar extends React.Component { )(this.messages); } - onButtonClicked(buttonId, messageData) { - this.props.onClickMessageButton(buttonId, messageData); - this.hideMessage(messageData.id); + onButtonClicked(messageId) { + this.props.onMessageButtonClick(messageId); + this.hideMessage(messageId); + } + + onCloseMessage(messageId) { + this.hideMessage(messageId); } setHideTimeout(messageData) { @@ -69,7 +74,7 @@ class SnackBar extends React.Component { element .hide() .then(() => delete this.messages[id]) - .then(() => this.props.deleteMessage(id)); + .then(() => this.props.onDeleteMessage(id)); } addMessages(messages) { @@ -95,6 +100,7 @@ class SnackBar extends React.Component { key={messageData.id} message={messageData} onClickMessageButton={this.onButtonClicked} + onCloseMessage={this.onCloseMessage} /> ), }; @@ -121,8 +127,8 @@ class SnackBar extends React.Component { SnackBar.propTypes = { errors: PropTypes.object, - onClickMessageButton: PropTypes.func, - deleteMessage: PropTypes.func, + onDeleteMessage: PropTypes.func, + onMessageButtonClick: PropTypes.func, }; const mapStateToProps = state => ({ @@ -130,7 +136,8 @@ const mapStateToProps = state => ({ }); const mapDispatchToProps = dispatch => (bindActionCreators({ - deleteMessage, + onDeleteMessage: deleteMessage, + onMessageButtonClick: messageButtonClick, }, dispatch)); export default connect(mapStateToProps, mapDispatchToProps)(SnackBar); diff --git a/packages/xod-client/src/messages/reducer.js b/packages/xod-client/src/messages/reducer.js index 9979000b..cf8808bb 100644 --- a/packages/xod-client/src/messages/reducer.js +++ b/packages/xod-client/src/messages/reducer.js @@ -5,7 +5,7 @@ import { getNewId } from './selectors'; export default (messages = {}, action) => { switch (action.type) { case MESSAGE_ADD: { - const newId = getNewId(messages); + const newId = (action.meta.id) ? action.meta.id : getNewId(messages); return R.assoc( newId, { diff --git a/packages/xod-client/src/project/actions.js b/packages/xod-client/src/project/actions.js index 9b3fdbce..2f20ac78 100644 --- a/packages/xod-client/src/project/actions.js +++ b/packages/xod-client/src/project/actions.js @@ -6,7 +6,7 @@ import { foldMaybe, rejectWithCode } from 'xod-func-tools'; import { addConfirmation, addError } from '../messages/actions'; import { NODETYPE_ERROR_TYPES } from '../editor/constants'; -import { PROJECT_BROWSER_ERRORS, NODETYPE_ERRORS } from '../messages/constants'; +import { PROJECT_BROWSER_ERRORS } from '../projectBrowser/messages'; import * as ActionType from './actionTypes'; import { isPatchPathTaken } from './utils'; import { getCurrentPatchPath } from '../editor/selectors'; @@ -14,9 +14,10 @@ import { getGrant } from '../user/selectors'; import { fetchGrant } from '../user/actions'; import { LOG_IN_TO_CONTINUE } from '../user/messages'; import { AUTHORIZATION_NEEDED } from '../user/errorCodes'; -import { SUCCESSFULLY_PUBLISHED } from './messages'; +import { SUCCESSFULLY_PUBLISHED, NODETYPE_ERRORS } from './messages'; import { getProject } from './selectors'; import { getPmSwaggerUrl } from '../utils/urls'; +import composeMessage from '../messages/composeMessage'; // // Project @@ -114,7 +115,7 @@ export const publishProject = () => (dispatch, getState) => { }) .catch((err) => { dispatch({ type: ActionType.PROJECT_PUBLISH_FAIL }); - dispatch(addError(err.message)); + dispatch(addError(composeMessage(err.message))); }); }; diff --git a/packages/xod-client/src/project/messages.js b/packages/xod-client/src/project/messages.js index 916b9f11..e8b3fd21 100644 --- a/packages/xod-client/src/project/messages.js +++ b/packages/xod-client/src/project/messages.js @@ -1,2 +1,32 @@ -// eslint-disable-next-line import/prefer-default-export -export const SUCCESSFULLY_PUBLISHED = 'Successfully published'; +import composeMessage from '../messages/composeMessage'; +import { + LINK_ERRORS as LE, + NODETYPE_ERROR_TYPES as NTE, +} from '../editor/constants'; + +export const SUCCESSFULLY_PUBLISHED = composeMessage('Library published'); + +export const LINK_ERRORS = { + [LE.SAME_DIRECTION]: composeMessage('Canʼt create link between pins of the same direction!'), + [LE.SAME_NODE]: composeMessage('Canʼt create link between pins of the same node!'), + [LE.ONE_LINK_FOR_INPUT_PIN]: composeMessage('Input pin can have only one link!'), + [LE.UNKNOWN_ERROR]: composeMessage('Canʼt create link', 'Unknown error!'), + [LE.INCOMPATIBLE_TYPES]: composeMessage('Incompatible pin types!'), +}; + +export const NODETYPE_ERRORS = { + [NTE.CANT_DELETE_USED_PATCHNODE]: ( + composeMessage( + 'Canʼt delete Patch', + 'Current Patch Node is used somewhere. You should remove it first!' + ) + ), + [NTE.CANT_DELETE_USED_PIN_OF_PATCHNODE]: composeMessage( + 'Canʼt delete Pin', + [ + 'Current IO Node is represents a Pin of Patch Node.', + 'And it is used somewhere.', + 'You should remove a linkage first!', + ].join(' ') + ), +}; diff --git a/packages/xod-client/src/projectBrowser/messages.js b/packages/xod-client/src/projectBrowser/messages.js new file mode 100644 index 00000000..1d4fe5c8 --- /dev/null +++ b/packages/xod-client/src/projectBrowser/messages.js @@ -0,0 +1,8 @@ +import composeMessage from '../messages/composeMessage'; + +// eslint-disable-next-line import/prefer-default-export +export const PROJECT_BROWSER_ERRORS = { + PATCH_NAME_TAKEN: composeMessage('This patch name is already taken!'), + INVALID_PATCH_NAME: composeMessage('Invalid patch name'), + INVALID_PROJECT_NAME: composeMessage('Invalid project name'), +}; diff --git a/packages/xod-client/src/user/messages.js b/packages/xod-client/src/user/messages.js index 1503dc5b..ecbc0a63 100644 --- a/packages/xod-client/src/user/messages.js +++ b/packages/xod-client/src/user/messages.js @@ -1,3 +1,5 @@ -export const INCORRECT_CREDENTIALS = 'Incorrect username or password'; -export const SERVICE_UNAVAILABLE = 'Service unavailable'; -export const LOG_IN_TO_CONTINUE = 'Please log in to continue'; +import composeMessage from '../messages/composeMessage'; + +export const INCORRECT_CREDENTIALS = composeMessage('Incorrect username or password'); +export const SERVICE_UNAVAILABLE = composeMessage('Service unavailable'); +export const LOG_IN_TO_CONTINUE = composeMessage('Please log in to continue'); diff --git a/packages/xod-client/stories/SnackBarMessage.jsx b/packages/xod-client/stories/SnackBarMessage.jsx index a1fcdbee..ecc87fff 100644 --- a/packages/xod-client/stories/SnackBarMessage.jsx +++ b/packages/xod-client/stories/SnackBarMessage.jsx @@ -6,45 +6,66 @@ import '../src/core/styles/main.scss'; import SnackBarMessage from '../src/messages/components/SnackBarMessage'; import { MESSAGE_TYPE } from '../src/messages/constants'; -const err = { - id: 1, +const errMsg = { + id: 0, payload: { - message: 'Something bad just happened', - buttons: [], + title: 'Error', + note: 'Something bad just happened. And we just want to notify you. Without any actions required.', + }, + timestamp: 1234567890, + type: MESSAGE_TYPE.ERROR, +}; +const errMsgWithButton = { + id: 'errorWithButton', + persistent: true, + payload: { + title: 'Error occured. What shall we do now?', + note: 'Just showed error message, choose what to do now to fix it up?', + button: 'Retry', }, timestamp: 1234567890, type: MESSAGE_TYPE.ERROR, }; const confirmationMsg = { - id: 2, + id: 1, payload: { - message: 'Please confirm something', - buttons: [], + title: 'Confirmation', + note: 'Message that confirms something. For example, it tells User that Project was successfully saved.', + }, + timestamp: 1234567890, + type: MESSAGE_TYPE.CONFIRMATION, +}; + +const confirmationMsgWithButton = { + id: 'confirmationWithButton', + persistent: true, + payload: { + title: 'Confirmation with buttons', + note: 'We have a new update of XOD. What do you want to do with it?', + button: 'Download & Install', }, timestamp: 1234567890, type: MESSAGE_TYPE.CONFIRMATION, }; const notificationMsg = { - id: 3, + id: 2, payload: { - message: 'Something happened. Just wanted you to know.', - buttons: [], + title: 'Some notification', + note: 'Something happened. Just wanted you to know.', }, timestamp: 1234567890, type: MESSAGE_TYPE.NOTIFICATION, }; -const notificationWithBtns = { - id: 4, +const notificationWithButton = { + id: 'notificationWithButton', + persistent: true, payload: { - message: 'Something happened. What should we do with it?', - buttons: [ - { id: 'abort', text: 'Abort' }, - { id: 'retry', text: 'Retry' }, - { id: 'ignore', text: 'Ignore' }, - ], + title: 'Notification with buttons', + note: 'Something happened. What should we do with it?', + button: 'Learn more', }, timestamp: 1234567890, type: MESSAGE_TYPE.CONFIRMATION, @@ -58,7 +79,7 @@ storiesOf('SnackBarMessage', module) )) .add('error', () => ( )) .add('confirmation', () => ( @@ -73,23 +94,34 @@ storiesOf('SnackBarMessage', module) )) .add('notification with buttons', () => ( )) .add('multiple messages', () => (
      + +
    diff --git a/packages/xod-client/test/project.spec.js b/packages/xod-client/test/project.spec.js index 5812807e..d65d6e4f 100644 --- a/packages/xod-client/test/project.spec.js +++ b/packages/xod-client/test/project.spec.js @@ -13,7 +13,7 @@ import generateReducers from '../src/core/reducer'; import { getProject } from '../src/project/selectors'; import { NODETYPE_ERROR_TYPES } from '../src/editor/constants'; -import { NODETYPE_ERRORS } from '../src/messages/constants'; +import { NODETYPE_ERRORS } from '../src/project/messages'; import { addError } from '../src/messages/actions'; import { switchPatch } from '../src/editor/actions';