mirror of
https://github.com/xodio/xod.git
synced 2026-03-15 05:06:59 +01:00
Merge pull request #940 from xodio/feat-925-ui-snackbar-messages
Brand-new look of messages in the IDE
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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)}
|
||||
/>
|
||||
<client.SnackBar
|
||||
onClickMessageButton={this.onClickMessageButton}
|
||||
/>
|
||||
{this.renderPopupShowCode()}
|
||||
{this.renderPopupUploadConfig()}
|
||||
{this.renderPopupUploadProcess()}
|
||||
|
||||
23
packages/xod-client/src/core/components/CloseButton.jsx
Normal file
23
packages/xod-client/src/core/components/CloseButton.jsx
Normal file
@@ -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;
|
||||
@@ -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
|
||||
);
|
||||
|
||||
117
packages/xod-client/src/core/styles/abstracts/colors.scss
Normal file
117
packages/xod-client/src/core/styles/abstracts/colors.scss
Normal file
@@ -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);
|
||||
@@ -57,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;
|
||||
@@ -93,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import
|
||||
'abstracts/colors',
|
||||
'abstracts/variables',
|
||||
'abstracts/functions',
|
||||
'abstracts/mixins';
|
||||
@@ -18,6 +19,7 @@
|
||||
'components/BackgroundLayer',
|
||||
'components/Breadcrumbs',
|
||||
'components/Button',
|
||||
'components/CloseButton',
|
||||
'components/Comment',
|
||||
'components/CppImplementationEditor',
|
||||
'components/Debugger',
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}
|
||||
<SnackBar />
|
||||
</Workarea>
|
||||
</FocusTrap>
|
||||
<Helpbar />
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export const MESSAGE_ADD = 'MESSAGE_ADD';
|
||||
export const MESSAGE_DELETE = 'MESSAGE_DELETE';
|
||||
export const MESSAGE_BUTTON_CLICKED = 'MESSAGE_BUTTON_CLICKED';
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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 }) => (
|
||||
<button
|
||||
className="Button Button--small"
|
||||
key={id}
|
||||
onClick={() => onClickMessageButton(id, message)}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
))
|
||||
const button = R.unless(
|
||||
R.isNil,
|
||||
text => (
|
||||
<Button
|
||||
small
|
||||
light
|
||||
onClick={() => onClickMessageButton(message.id)}
|
||||
>
|
||||
{text}
|
||||
</Button>
|
||||
)
|
||||
)(message.payload.buttons);
|
||||
)(message.payload.button);
|
||||
|
||||
return (
|
||||
<div className="message-content">
|
||||
{message.payload.message}
|
||||
{buttons}
|
||||
</div>
|
||||
);
|
||||
return [
|
||||
<div className="message-text" key="text">
|
||||
<span className="title">
|
||||
{message.payload.title}
|
||||
</span>
|
||||
{message.payload.note}
|
||||
</div>,
|
||||
<div className="message-buttons" key="buttons">
|
||||
{button}
|
||||
</div>,
|
||||
];
|
||||
}
|
||||
|
||||
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 {
|
||||
<li
|
||||
className={cls}
|
||||
>
|
||||
<a tabIndex={message.id} >
|
||||
<a
|
||||
className="message-content"
|
||||
tabIndex={message.id}
|
||||
>
|
||||
<CloseButton onClick={this.onCloseMessage} />
|
||||
{messageContent}
|
||||
</a>
|
||||
</li>
|
||||
@@ -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 = {
|
||||
|
||||
5
packages/xod-client/src/messages/composeMessage.js
Normal file
5
packages/xod-client/src/messages/composeMessage.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export default (title, note = null, button = null) => ({
|
||||
title,
|
||||
note,
|
||||
button,
|
||||
});
|
||||
@@ -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}',
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
{
|
||||
|
||||
@@ -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)));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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(' ')
|
||||
),
|
||||
};
|
||||
|
||||
8
packages/xod-client/src/projectBrowser/messages.js
Normal file
8
packages/xod-client/src/projectBrowser/messages.js
Normal file
@@ -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'),
|
||||
};
|
||||
@@ -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');
|
||||
|
||||
@@ -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', () => (
|
||||
<SnackBarMessage
|
||||
message={err}
|
||||
message={errMsg}
|
||||
/>
|
||||
))
|
||||
.add('confirmation', () => (
|
||||
@@ -73,23 +94,34 @@ storiesOf('SnackBarMessage', module)
|
||||
))
|
||||
.add('notification with buttons', () => (
|
||||
<SnackBarMessage
|
||||
message={notificationWithBtns}
|
||||
message={notificationWithButton}
|
||||
onClickMessageButton={action('OnButton')}
|
||||
/>
|
||||
))
|
||||
.add('multiple messages', () => (
|
||||
<ul className="SnackBarList">
|
||||
<SnackBarMessage
|
||||
message={err}
|
||||
message={errMsg}
|
||||
onClickMessageButton={action('OnButton')}
|
||||
/>
|
||||
<SnackBarMessage
|
||||
message={errMsgWithButton}
|
||||
onClickMessageButton={action('OnButton')}
|
||||
/>
|
||||
<SnackBarMessage
|
||||
message={confirmationMsg}
|
||||
onClickMessageButton={action('OnButton')}
|
||||
/>
|
||||
<SnackBarMessage
|
||||
message={confirmationMsgWithButton}
|
||||
onClickMessageButton={action('OnButton')}
|
||||
/>
|
||||
<SnackBarMessage
|
||||
message={notificationMsg}
|
||||
onClickMessageButton={action('OnButton')}
|
||||
/>
|
||||
<SnackBarMessage
|
||||
message={notificationWithBtns}
|
||||
message={notificationWithButton}
|
||||
onClickMessageButton={action('OnButton')}
|
||||
/>
|
||||
</ul>
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user