diff --git a/.circleci/config.yml b/.circleci/config.yml index 8614db82a..cf9725bc3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,7 @@ jobs: # Build Docker Image # https://circleci.com/docs/2.0/building-docker-images/#overview - setup_remote_docker: # (2) - docker_layer_caching: true # (3) + docker_layer_caching: false # (3) # build and push Docker image - run: | TAG=0.1.$CIRCLE_BUILD_NUM diff --git a/README.md b/README.md index 9bf60b353..dc34eb94f 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ _Note: These keys/secrets are *not* required to run or develop Cboard._ They are ## Contributors This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. - + ## Backers diff --git a/public/index.html b/public/index.html index 21b0e6c95..7792d2fbc 100644 --- a/public/index.html +++ b/public/index.html @@ -5,24 +5,26 @@ @@ -42,7 +44,6 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - diff --git a/src/api/api.js b/src/api/api.js index 7e9560de3..4bf29ba0b 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -1,4 +1,6 @@ import axios from 'axios'; +import { alpha2ToAlpha3T } from '@cospired/i18n-iso-languages'; + import { API_URL, ARASAAC_BASE_PATH_API, @@ -78,7 +80,14 @@ class API { } async globalsymbolsPictogramsSearch(locale, searchText) { - const pictogSearchTextPath = `${GLOBALSYMBOLS_BASE_PATH_API}concepts/suggest/?query=${searchText}&language=${locale}&language_iso_format=639-1&limit=20`; + let language = 'eng'; + if (locale.length === 3) { + language = locale; + } + if (locale.length === 2) { + language = alpha2ToAlpha3T(locale); + } + const pictogSearchTextPath = `${GLOBALSYMBOLS_BASE_PATH_API}labels/search/?query=${searchText}&language=${language}&language_iso_format=639-3&limit=20`; try { const { status, data } = await this.axiosInstance.get( pictogSearchTextPath diff --git a/src/components/Account/SignUp/SignUp.actions.js b/src/components/Account/SignUp/SignUp.actions.js index 4c689493e..5d1df09e4 100644 --- a/src/components/Account/SignUp/SignUp.actions.js +++ b/src/components/Account/SignUp/SignUp.actions.js @@ -4,8 +4,10 @@ import get from 'lodash/fp/get'; import { API_URL } from '../../../constants'; export function signUp(formValues) { + const endpoint = + API_URL[API_URL.length - 1] === '/' ? `${API_URL}user` : `${API_URL}/user`; return axios - .post(`${API_URL}/user`, formValues) + .post(endpoint, formValues) .then(get('data')) .catch(get('response.data')); } diff --git a/src/components/AuthScreen/AuthScreen.component.js b/src/components/AuthScreen/AuthScreen.component.js index 442dd27e0..24c70d4ff 100644 --- a/src/components/AuthScreen/AuthScreen.component.js +++ b/src/components/AuthScreen/AuthScreen.component.js @@ -16,6 +16,7 @@ import Information from './Information'; import CboardLogo from '../WelcomeScreen/CboardLogo/CboardLogo.component'; import './AuthScreen.css'; import { API_URL } from '../../constants'; +import { isCordova } from '../../cordova-util'; class AuthScreen extends Component { state = { @@ -68,23 +69,27 @@ class AuthScreen extends Component { - { - window.location = `${API_URL}/login/google`; - }} - > - - + {!isCordova() && ( +
+ { + window.location = `${API_URL}/login/google`; + }} + > + + - { - window.location = `${API_URL}/login/facebook`; - }} - > - - + { + window.location = `${API_URL}/login/facebook`; + }} + > + + +
+ )} - - - - - - + + + + + + + = 2 && - nextProps.match.params.id === navHistory[navHistory.length - 2] - ) { - this.props.previousBoard(); + const { + navHistory, + boards, + changeBoard, + previousBoard, + replaceBoard + } = this.props; + + const boardExists = boards.find(b => b.id === nextProps.match.params.id); + if (boardExists) { + changeBoard(nextProps.match.params.id); + // Was a browser back action? + if ( + navHistory.length >= 2 && + nextProps.match.params.id === navHistory[navHistory.length - 2] + ) { + previousBoard(); + } + } else { + // Was a browser back action? + if ( + navHistory.length >= 2 && + nextProps.match.params.id === navHistory[navHistory.length - 2] + ) { + for (let i = navHistory.length - 2; i >= 0; i--) { + previousBoard(); + const boardExists = boards.find(b => b.id === navHistory[i]); + if (boardExists) { + changeBoard(navHistory[i]); + replaceBoard(boardExists, boardExists); + break; + } + } + } } } @@ -343,12 +366,13 @@ export class BoardContainer extends Component { }; handleEditTileEditorSubmit = tiles => { - const { board, editTiles } = this.props; + const { board, editTiles, userData } = this.props; editTiles(tiles, board.id); + // Loggedin user? + if ('name' in userData && 'email' in userData) { + this.handleApiUpdates(null, null, tiles); + } this.toggleSelectMode(); - this.setState({ - isApiRequired: true - }); }; handleAddTileEditorSubmit = tile => { @@ -357,6 +381,7 @@ export class BoardContainer extends Component { createTile, board, createBoard, + switchBoard, addBoardCommunicator, history } = this.props; @@ -375,26 +400,13 @@ export class BoardContainer extends Component { if (tile.type !== 'board') { createTile(tile, board.id); } else { + switchBoard(boardData.id); history.replace(`/board/${boardData.id}`, []); } // Loggedin user? if ('name' in userData && 'email' in userData) { - this.setState({ - isApiRequired: true - }); - //update new board to be an own board - if (tile.loadBoard) { - this.setState({ - newOwnBoard: { - ...boardData, - author: userData.name, - email: userData.email, - locale: userData.locale, - caption: tile.image - } - }); - } + this.handleApiUpdates(tile); } }; @@ -437,12 +449,35 @@ export class BoardContainer extends Component { return; } - const { changeBoard, changeOutput, speak } = this.props; + const { + changeBoard, + changeOutput, + speak, + intl, + boards, + showNotification + } = this.props; const hasAction = tile.action && tile.action.startsWith('+'); if (tile.loadBoard) { - changeBoard(tile.loadBoard); - this.props.history.push(tile.loadBoard); + try { + const boardExists = boards.find(b => b.id === tile.loadBoard); + if (boardExists) { + changeBoard(tile.loadBoard); + this.props.history.push(tile.loadBoard); + } else { + const rboardExists = boards.find(b => b.name === tile.label); + if (rboardExists) { + changeBoard(rboardExists.id); + this.props.history.push(rboardExists.id); + } else { + showNotification(intl.formatMessage(messages.boardMissed)); + } + } + } catch (error) { + console.log(error.message); + showNotification(intl.formatMessage(messages.boardMissed)); + } } else { changeOutput([...this.props.output, tile]); @@ -470,12 +505,13 @@ export class BoardContainer extends Component { showNotification, board, userData, - communicator + communicator, + deleteBoardCommunicator, + deleteApiBoard } = this.props; deleteTiles(this.state.selectedTileIds, board.id); this.setState({ - selectedTileIds: [], - isApiRequired: true + selectedTileIds: [] }); showNotification(intl.formatMessage(messages.tilesDeleted)); @@ -490,8 +526,8 @@ export class BoardContainer extends Component { board.tiles[j].loadBoard.length > 14 ) { if (board.tiles[j].loadBoard !== communicator.rootBoard) { - this.props.deleteBoardCommunicator(board.tiles[j].loadBoard); - this.props.deleteApiBoard(board.tiles[j].loadBoard); + deleteBoardCommunicator(board.tiles[j].loadBoard); + deleteApiBoard(board.tiles[j].loadBoard); } else { showNotification( intl.formatMessage(messages.rootBoardNotDeleted) @@ -500,7 +536,9 @@ export class BoardContainer extends Component { } } } + this.handleApiUpdates(null, this.state.selectedTileIds, null); } + this.toggleSelectMode(); }; handleLockNotify = countdown => { @@ -547,13 +585,30 @@ export class BoardContainer extends Component { handleUpdateBoard = board => { this.props.replaceBoard(this.props.board, board); - const { userData, communicator } = this.props; + }; + + handleApiUpdates = ( + tile = null, + deletedTilesiIds = null, + editedTiles = null + ) => { + const { + userData, + communicator, + board, + upsertCommunicator, + changeCommunicator, + updateApiObjectsNoChild, + updateApiObjects, + historyRemovePreviousBoard, + updateBoard, + switchBoard + } = this.props; // Loggedin user? - if (this.state.isApiRequired && 'name' in userData && 'email' in userData) { + if ('name' in userData && 'email' in userData) { this.setState({ - isSaving: true, - isApiRequired: false + isSaving: true }); var createCommunicator = false; @@ -561,99 +616,118 @@ export class BoardContainer extends Component { var createChildBoard = false; var childBoardData = null; - const communicatorData = { - ...communicator, - author: userData.name, - email: userData.email, - id: shortid.generate(), - boards: [] - }; + let uTiles = []; + if (deletedTilesiIds) { + uTiles = board.tiles.filter( + cTile => !deletedTilesiIds.includes(cTile.id) + ); + } + if (editedTiles) { + uTiles = board.tiles.map( + cTile => editedTiles.find(s => s.id === cTile.id) || cTile + ); + } + if (tile && tile.type !== 'board') { + uTiles = [...board.tiles, tile]; + } + if (tile && tile.type === 'board') { + uTiles = [...board.tiles]; + } + const parentBoardData = { ...board, + tiles: uTiles, author: userData.name, email: userData.email, - locale: userData.locale, - isPublic: false + isPublic: false, + hidden: false }; - //check if user has an own communicator + let communicatorData = { ...communicator }; if (communicator.email !== userData.email) { - this.props.upsertCommunicator(communicatorData); - this.props.changeCommunicator(communicatorData.id); + //need to create a new communicator + communicatorData = { + ...communicator, + author: userData.name, + email: userData.email, + id: shortid.generate() + }; + upsertCommunicator(communicatorData); + changeCommunicator(communicatorData.id); createCommunicator = true; } //check for a new own board - if (this.state.newOwnBoard) { - childBoardData = { ...this.state.newOwnBoard }; - this.setState({ - newOwnBoard: null - }); + if (tile && tile.loadBoard) { + const boardData = { + id: tile.loadBoard, + name: tile.label, + nameKey: tile.labelKey, + hidden: false, + tiles: [], + isPublic: false, + author: userData.name, + email: userData.email, + locale: userData.locale, + caption: tile.image + }; + childBoardData = { ...boardData }; createChildBoard = true; - this.props.updateBoard(childBoardData); - this.props.addBoardCommunicator(childBoardData.id); + updateBoard(childBoardData); } //check if we have to create a copy of the parent - if (board.isPublic && board.email !== userData.email) { - if (communicatorData.rootBoard === board.id) { - parentBoardData.id = shortid.generate(); - this.props.createBoard(parentBoardData); - this.props.addBoardCommunicator(parentBoardData.id); - createParentBoard = true; - //parent board needs to be the root board - communicatorData.rootBoard = parentBoardData.id; - communicatorData.activeBoardId = parentBoardData.id; - this.props.upsertCommunicator(communicatorData); - } else { - //update the parent - this.props.updateBoard(parentBoardData); - this.props.addBoardCommunicator(parentBoardData.id); - if (parentBoardData.id.length < 15) { - createParentBoard = true; - } - //if it is a new communicator , need to set as root - if (createCommunicator === true) { - communicatorData.rootBoard = parentBoardData.id; - communicatorData.activeBoardId = parentBoardData.id; - this.props.upsertCommunicator(communicatorData); - } - } + if (parentBoardData.id.length < 14) { + createParentBoard = true; + } else { + //update the parent + updateBoard(parentBoardData); } //api updates - if (!createChildBoard) { - this.props - .updateApiObjectsNoChild( - parentBoardData, - createCommunicator, - createParentBoard - ) + if (tile && tile.type === 'board') { + //child becomes parent + updateApiObjectsNoChild(childBoardData, createCommunicator, true) .then(parentBoardId => { - if (createParentBoard) { - this.props.historyRemovePreviousBoard(parentBoardId); - } - this.props.history.replace(`/board/${parentBoardId}`); + switchBoard(parentBoardId); + this.props.history.replace(`/board/${parentBoardId}`, []); this.setState({ isSaving: false }); }) .catch(e => { this.setState({ isSaving: false }); }); } else { - this.props - .updateApiObjects( + if (!createChildBoard) { + updateApiObjectsNoChild( + parentBoardData, + createCommunicator, + createParentBoard + ) + .then(parentBoardId => { + if (createParentBoard) { + historyRemovePreviousBoard(parentBoardId); + } + this.props.history.replace(`/board/${parentBoardId}`); + this.setState({ isSaving: false }); + }) + .catch(e => { + this.setState({ isSaving: false }); + }); + } else { + updateApiObjects( childBoardData, parentBoardData, createCommunicator, createParentBoard ) - .then(parentBoardId => { - if (createParentBoard) { - this.props.historyRemovePreviousBoard(parentBoardId); - } - this.props.history.replace(`/board/${parentBoardId}`); - this.setState({ isSaving: false }); - }) - .catch(e => { - this.setState({ isSaving: false }); - }); + .then(parentBoardId => { + if (createParentBoard) { + historyRemovePreviousBoard(parentBoardId); + } + this.props.history.replace(`/board/${parentBoardId}`); + this.setState({ isSaving: false }); + }) + .catch(e => { + this.setState({ isSaving: false }); + }); + } } } }; @@ -791,6 +865,7 @@ const mapDispatchToProps = { historyRemovePreviousBoard, createBoard, updateBoard, + switchBoard, createTile, deleteTiles, editTiles, diff --git a/src/components/Board/Board.messages.js b/src/components/Board/Board.messages.js index d0f75604f..50fb991a9 100644 --- a/src/components/Board/Board.messages.js +++ b/src/components/Board/Board.messages.js @@ -68,5 +68,10 @@ export default defineMessages({ id: 'cboard.components.Board.userProfileLocked', defaultMessage: 'User Profile is locked, please unlock settings to see your user profile.' + }, + boardMissed: { + id: 'cboard.components.Board.boardMissed', + defaultMessage: + 'We are sorry but we have missed this folder / board. We recommend you to create it again.' } }); diff --git a/src/components/Board/Board.reducer.js b/src/components/Board/Board.reducer.js index 84cb1163a..2f14bd024 100644 --- a/src/components/Board/Board.reducer.js +++ b/src/components/Board/Board.reducer.js @@ -66,7 +66,7 @@ function tileReducer(board, action) { } function boardReducer(state = initialState, action) { - //fix to prevent for null board + //fix to prevent for null board state.boards = state.boards.filter(board => board !== null); switch (action.type) { case LOGIN_SUCCESS: @@ -166,9 +166,10 @@ function boardReducer(state = initialState, action) { navHistory, activeBoardId: navHistory[navHistory.length - 1] }; + // this will not work for anonymous users case HISTORY_REMOVE_PREVIOUS_BOARD: - const rnavHistory = [ ...state.navHistory ]; - if (rnavHistory.length === 1) { + const rnavHistory = [...state.navHistory]; + if (rnavHistory.length === 1 && rnavHistory[0] !== 'root') { return state; } rnavHistory.pop(); @@ -187,7 +188,9 @@ function boardReducer(state = initialState, action) { case DELETE_BOARD: return { ...state, - boards: state.boards.filter(board => action.boardId.indexOf(board.id) === -1) + boards: state.boards.filter( + board => action.boardId.indexOf(board.id) === -1 + ) }; case CREATE_TILE: @@ -239,12 +242,13 @@ function boardReducer(state = initialState, action) { for (let i = 0; i < creadBoards.length; i++) { let tiles = creadBoards[i].tiles; for (let j = 0; j < tiles.length; j++) { - if (tiles[j] != null && - tiles[j].loadBoard === action.boardId) { + if (tiles[j] != null && tiles[j].loadBoard === action.boardId) { tiles[j].loadBoard = action.board.id; - if (!creadBoards[i].isPublic && + if ( + !creadBoards[i].isPublic && creadBoards[i].id.length > 14 && - creadBoards[i].hasOwnProperty('email')) { + creadBoards[i].hasOwnProperty('email') + ) { creadBoards[i].markToUpdate = true; } } diff --git a/src/components/Board/SymbolSearch/SymbolSearch.component.js b/src/components/Board/SymbolSearch/SymbolSearch.component.js index 57ff6b37c..c00a61da9 100644 --- a/src/components/Board/SymbolSearch/SymbolSearch.component.js +++ b/src/components/Board/SymbolSearch/SymbolSearch.component.js @@ -172,7 +172,11 @@ export class SymbolSearch extends PureComponent { intl: { locale } } = this.props; try { - const data = await API.globalsymbolsPictogramsSearch(locale, searchText); + let language = locale !== 'me' ? locale : 'cnr'; + const data = await API.globalsymbolsPictogramsSearch( + language, + searchText + ); if (data.length) { const suggestions = [ ...this.state.suggestions.filter( @@ -180,15 +184,13 @@ export class SymbolSearch extends PureComponent { ) ]; let globalsymbolsSuggestions = []; - data.forEach(function(concept) { - for (let i = 0; i < concept.pictos.length; i++) { - globalsymbolsSuggestions.push({ - id: concept.subject, - src: concept.pictos[i].image_url, - translatedId: concept.subject, - fromGlobalsymbols: true - }); - } + data.forEach(function(element) { + globalsymbolsSuggestions.push({ + id: element.text, + src: element.picto.image_url, + translatedId: element.text, + fromGlobalsymbols: true + }); }); this.setState({ suggestions: [...suggestions, ...globalsymbolsSuggestions] diff --git a/src/components/Board/TileEditor/TileEditor.component.js b/src/components/Board/TileEditor/TileEditor.component.js index 3d789ae79..8d7d59418 100644 --- a/src/components/Board/TileEditor/TileEditor.component.js +++ b/src/components/Board/TileEditor/TileEditor.component.js @@ -218,13 +218,23 @@ export class TileEditor extends Component { handleColorChange = event => { const color = event ? event.target.value : ''; this.setState({ selectedBackgroundColor: color }); + if (event) { + this.updateTileProperty('backgroundColor', event.target.value); + } else { + this.updateTileProperty('backgroundColor', this.getDefaultColor()); + } }; getDefaultColor = () => { - if (this.currentTileProp('loadBoard')) { + if (this.currentTileProp('type') === 'folder') { return this.defaultTileColors.folder; } - return this.defaultTileColors.button; + if (this.currentTileProp('type') === 'button') { + return this.defaultTileColors.button; + } + if (this.currentTileProp('type') === 'board') { + return this.defaultTileColors.board; + } }; render() { @@ -332,12 +342,14 @@ export class TileEditor extends Component { /> - )} +
+ +
{this.currentTileProp('type') !== 'board' && (
diff --git a/src/components/Board/TileEditor/TileEditor.css b/src/components/Board/TileEditor/TileEditor.css index e78dc8265..8f0e8d34a 100644 --- a/src/components/Board/TileEditor/TileEditor.css +++ b/src/components/Board/TileEditor/TileEditor.css @@ -38,3 +38,7 @@ .TileEditor__radiogroup { margin-top: 15px; } + +.TileEditor__colorselect { + margin-top: 15px; +} diff --git a/src/components/Board/__tests__/Board.reducer.test.js b/src/components/Board/__tests__/Board.reducer.test.js index ef86ffdfb..991aa5ff2 100644 --- a/src/components/Board/__tests__/Board.reducer.test.js +++ b/src/components/Board/__tests__/Board.reducer.test.js @@ -266,7 +266,8 @@ describe('reducer', () => { ) ).toEqual({ ...initialState, - navHistory: ['root'] + activeBoardId: 'root', + navHistory: [] }); }); it('should handle replaceBoard', () => { diff --git a/src/components/Communicator/CommunicatorDialog/CommunicatorBoardItem.component.js b/src/components/Communicator/CommunicatorDialog/CommunicatorBoardItem.component.js index 007b03afa..45e037380 100644 --- a/src/components/Communicator/CommunicatorDialog/CommunicatorBoardItem.component.js +++ b/src/components/Communicator/CommunicatorDialog/CommunicatorBoardItem.component.js @@ -3,30 +3,32 @@ import PropTypes from 'prop-types'; import { intlShape } from 'react-intl'; import PublicIcon from '@material-ui/icons/Public'; import KeyIcon from '@material-ui/icons/VpnKey'; -import ViewModuleIcon from '@material-ui/icons/ViewModule'; +import ViewModuleIcon from '@material-ui/icons/ViewModule'; import DeleteIcon from '@material-ui/icons/Delete'; import InputIcon from '@material-ui/icons/Input'; import ClearIcon from '@material-ui/icons/Clear'; import HomeIcon from '@material-ui/icons/Home'; import InfoIcon from '@material-ui/icons/Info'; -import IconButton from '../../UI/IconButton'; +import IconButton from '../../UI/IconButton'; import ListItem from '@material-ui/core/ListItem'; -import ListItemText from '@material-ui/core/ListItemText'; -import Dialog from '@material-ui/core/Dialog'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import DialogContent from '@material-ui/core/DialogContent'; +import ListItemText from '@material-ui/core/ListItemText'; +import Dialog from '@material-ui/core/Dialog'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogContent from '@material-ui/core/DialogContent'; import DialogActions from '@material-ui/core/DialogActions'; import { TAB_INDEXES } from './CommunicatorDialog.constants'; import messages from './CommunicatorDialog.messages'; import Button from '@material-ui/core/Button'; -import Typography from '@material-ui/core/Typography'; +import Typography from '@material-ui/core/Typography'; + +import { isCordova } from '../../../cordova-util'; class CommunicatorBoardItem extends React.Component { constructor(props) { super(props); this.state = { - menu: null, + menu: null, openBoardInfo: false }; } @@ -39,17 +41,17 @@ class CommunicatorBoardItem extends React.Component { this.setState({ menu: null }); } - handleBoardInfoOpen() { - this.setState({ - openBoardInfo: true - }); + handleBoardInfoOpen() { + this.setState({ + openBoardInfo: true + }); } - - handleBoardInfoClose() { + + handleBoardInfoClose() { this.setState({ openBoardInfo: false - }); - }; + }); + } async publishBoardAction(board) { await this.props.publishBoardAction(board); @@ -76,11 +78,17 @@ class CommunicatorBoardItem extends React.Component { selectedTab === TAB_INDEXES.MY_BOARDS || selectedTab === TAB_INDEXES.PUBLIC_BOARDS || (selectedTab === TAB_INDEXES.COMMUNICATOR_BOARDS && !!userData.authToken); + // Cordova path cannot be absolute + const boardCaption = + isCordova() && board.caption && board.caption.search('/') === 0 + ? `.${board.caption}` + : board.caption; + return (
- {!!board.caption && {title}} - {!board.caption && ( + {!!boardCaption && {title}} + {!boardCaption && (
@@ -88,12 +96,12 @@ class CommunicatorBoardItem extends React.Component {
- +
@@ -101,28 +109,26 @@ class CommunicatorBoardItem extends React.Component { {intl.formatMessage(messages.author, { author: board.author })}
- {selectedTab === TAB_INDEXES.PUBLIC_BOARDS && - } - {selectedTab === TAB_INDEXES.MY_BOARDS && - board.isPublic && - } - {selectedTab === TAB_INDEXES.MY_BOARDS && - !board.isPublic && - } + {selectedTab === TAB_INDEXES.PUBLIC_BOARDS && } + {selectedTab === TAB_INDEXES.MY_BOARDS && board.isPublic && ( + + )} + {selectedTab === TAB_INDEXES.MY_BOARDS && !board.isPublic && ( + + )} {selectedTab === TAB_INDEXES.COMMUNICATOR_BOARDS && - communicator.rootBoard === board.id && - < HomeIcon />} + communicator.rootBoard === board.id && }
- {displayActions && - ( -
+ {displayActions && ( +
{selectedTab === TAB_INDEXES.COMMUNICATOR_BOARDS && (
- { addOrRemoveBoard(board); @@ -130,101 +136,114 @@ class CommunicatorBoardItem extends React.Component { > - { - this.setRootBoard(board); + { + this.setRootBoard(board); }} label={intl.formatMessage(messages.menuRootBoardOption)} >
- )} + )} {selectedTab === TAB_INDEXES.PUBLIC_BOARDS && (
{ addOrRemoveBoard(board); }} - label={communicator.boards.includes(board.id) - ? intl.formatMessage(messages.removeBoard) - : intl.formatMessage(messages.addBoard)} + label={ + communicator.boards.includes(board.id) + ? intl.formatMessage(messages.removeBoard) + : intl.formatMessage(messages.addBoard) + } > - {communicator.boards.includes(board.id) - ? - : } + {communicator.boards.includes(board.id) ? ( + + ) : ( + + )} + > - - + + {board.name} - - + > + {board.name} + + - {intl.formatMessage(messages.boardInfoName)}: {board.name} - + {intl.formatMessage(messages.boardInfoName)}:{' '} + {board.name} + - {intl.formatMessage(messages.boardInfoAuthor)}: {board.author} - + {intl.formatMessage(messages.boardInfoAuthor)}:{' '} + {board.author} + - {intl.formatMessage(messages.boardInfoTiles)}: {board.tiles.length} - + {intl.formatMessage(messages.boardInfoTiles)}:{' '} + {board.tiles.length} + - {intl.formatMessage(messages.boardInfoId)}: {board.id} + {intl.formatMessage(messages.boardInfoId)}:{' '} + {board.id} - + +
- )} + )} {selectedTab === TAB_INDEXES.MY_BOARDS && (
{ addOrRemoveBoard(board); }} > - {communicator.boards.includes(board.id) - ? - : } + {communicator.boards.includes(board.id) ? ( + + ) : ( + + )} { - this.publishBoardAction(board); + label={ + board.isPublic + ? intl.formatMessage(messages.menuUnpublishOption) + : intl.formatMessage(messages.menuPublishOption) + } + onClick={() => { + this.publishBoardAction(board); }} > - {board.isPublic - ? - : } + {board.isPublic ? : } { deleteBoard(board.id); @@ -235,9 +254,8 @@ class CommunicatorBoardItem extends React.Component {
)}
- )} + )}
-
); } diff --git a/src/components/Communicator/CommunicatorToolbar/CommunicatorToolbar.component.js b/src/components/Communicator/CommunicatorToolbar/CommunicatorToolbar.component.js index 658288ca2..f6246918c 100644 --- a/src/components/Communicator/CommunicatorToolbar/CommunicatorToolbar.component.js +++ b/src/components/Communicator/CommunicatorToolbar/CommunicatorToolbar.component.js @@ -3,11 +3,11 @@ import PropTypes from 'prop-types'; import { FormattedMessage, intlShape } from 'react-intl'; import classNames from 'classnames'; import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; +import TextField from '@material-ui/core/TextField'; import ListItem from '@material-ui/core/ListItem'; import Menu from '@material-ui/core/Menu'; -import ListItemText from '@material-ui/core/ListItemText'; -import ListItemAvatar from '@material-ui/core/ListItemAvatar'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListItemAvatar from '@material-ui/core/ListItemAvatar'; import Avatar from '@material-ui/core/Avatar'; import LayersIcon from '@material-ui/icons/Layers'; import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; @@ -17,9 +17,9 @@ import AddCircleIcon from '@material-ui/icons/AddCircle'; import FormDialog from '../../UI/FormDialog'; import messages from './CommunicatorToolbar.messages'; import './CommunicatorToolbar.css'; +import { isCordova } from '../../../cordova-util'; class CommunicatorToolbar extends React.Component { - constructor(props) { super(props); this.state = { @@ -90,8 +90,15 @@ class CommunicatorToolbar extends React.Component { }); }; - handleNewBoardClick = () => { - + handleNewBoardClick = () => {}; + + boardCaption = board => { + // Cordova path cannot be absolute + if (isCordova() && board.caption && board.caption.search('/') === 0) { + return `.${board.caption}`; + } else { + return board.caption; + } }; render() { @@ -105,7 +112,6 @@ class CommunicatorToolbar extends React.Component { return (
-
-
+
{false && ( -
+
diff --git a/src/components/Communicator/CommunicatorToolbar/__snapshots__/CommunicatorToolbar.test.js.snap b/src/components/Communicator/CommunicatorToolbar/__snapshots__/CommunicatorToolbar.test.js.snap index cb5b035cb..85bdd077c 100644 --- a/src/components/Communicator/CommunicatorToolbar/__snapshots__/CommunicatorToolbar.test.js.snap +++ b/src/components/Communicator/CommunicatorToolbar/__snapshots__/CommunicatorToolbar.test.js.snap @@ -36,7 +36,7 @@ exports[`Communicator tests default renderer 1`] = ` @@ -142,7 +142,7 @@ exports[`Communicator tests menu behavior 1`] = ` @@ -249,7 +249,7 @@ exports[`Communicator tests menu behavior 2`] = ` @@ -356,7 +356,7 @@ exports[`Communicator tests switching boards behavior 1`] = ` @@ -463,7 +463,7 @@ exports[`Communicator tests switching boards behavior 2`] = ` diff --git a/src/components/Settings/Export/Export.container.js b/src/components/Settings/Export/Export.container.js index 39c03be7a..a77a447b7 100644 --- a/src/components/Settings/Export/Export.container.js +++ b/src/components/Settings/Export/Export.container.js @@ -6,12 +6,12 @@ import { injectIntl, intlShape } from 'react-intl'; import { showNotification } from '../../Notifications/Notifications.actions'; import Export from './Export.component'; import { EXPORT_CONFIG_BY_TYPE } from './Export.constants'; +import messages from './Export.messages'; export class ExportContainer extends PureComponent { static propTypes = { boards: PropTypes.array.isRequired, history: PropTypes.object.isRequired, - activeBoardId: PropTypes.string.isRequired, intl: intlShape.isRequired }; @@ -27,9 +27,15 @@ export class ExportContainer extends PureComponent { return false; } - const { boards, activeBoardId, intl } = this.props; + const { boards, intl, activeBoardId, showNotification } = this.props; - await EXPORT_HELPERS[exportConfig.callback](boards, intl, activeBoardId); + if (type !== 'pdf') { + await EXPORT_HELPERS[exportConfig.callback](boards, intl); + } else { + const currentBoard = boards.filter(board => board.id === activeBoardId); + await EXPORT_HELPERS[exportConfig.callback](currentBoard, intl); + showNotification(intl.formatMessage(messages.boardDownloaded)); + } doneCallback(); }; @@ -39,12 +45,11 @@ export class ExportContainer extends PureComponent { } render() { - const { boards, history, activeBoardId } = this.props; + const { boards, history } = this.props; return ( diff --git a/src/components/Settings/Export/Export.helpers.js b/src/components/Settings/Export/Export.helpers.js index b4692476f..9ebfa553c 100644 --- a/src/components/Settings/Export/Export.helpers.js +++ b/src/components/Settings/Export/Export.helpers.js @@ -11,6 +11,11 @@ import { CBOARD_EXT_PROPERTIES, CBOARD_ZIP_OPTIONS } from './Export.constants'; +import { + isCordova, + requestCvaWritePermissions, + writeCvaFile +} from '../../../cordova-util'; pdfMake.vfs = pdfFonts.pdfMake.vfs; @@ -237,14 +242,17 @@ async function toDataURL(url, styles = {}, outputFormat = 'image/jpeg') { ctx.lineWidth = 3; ctx.strokeRect(0, 0, 150, 150); } - const dataURL = canvas.toDataURL(outputFormat); resolve(dataURL); }; - imageElement.src = url; + // Cordova path cannot be absolute + const imageUrl = + isCordova() && url && url.search('/') === 0 ? `.${url}` : url; + imageElement.src = imageUrl; if (imageElement.complete || imageElement.complete === undefined) { - imageElement.src = url; + console.log(imageUrl); + imageElement.src = imageUrl; } }); } @@ -410,15 +418,14 @@ export async function cboardExportAdapter(boards = []) { } } -export async function pdfExportAdapter(boards = [], intl, activeBoardId) { +export async function pdfExportAdapter(boards = [], intl) { const docDefinition = { pageSize: 'A4', pageOrientation: 'landscape', content: [] }; - const fboards = boards.filter(board => board.id === activeBoardId ); - const lastBoardIndex = fboards.length - 1; - const content = await fboards.reduce(async (prev, board, i) => { + const lastBoardIndex = boards.length - 1; + const content = await boards.reduce(async (prev, board, i) => { const prevContent = await prev; const breakPage = i !== lastBoardIndex; const boardPDFData = await generatePDFBoard(board, intl, breakPage); @@ -426,8 +433,21 @@ export async function pdfExportAdapter(boards = [], intl, activeBoardId) { }, Promise.resolve([])); docDefinition.content = content; - - pdfMake.createPdf(docDefinition).download(EXPORT_CONFIG_BY_TYPE.pdf.filename); + const pdfObj = pdfMake.createPdf(docDefinition); + + if (pdfObj) { + if (isCordova()) { + requestCvaWritePermissions(); + pdfObj.getBuffer(buffer => { + var blob = new Blob([buffer], { type: 'application/pdf' }); + const name = 'board.pdf'; + writeCvaFile(name, blob); + }); + } else { + // On a browser simply use download! + pdfObj.download(EXPORT_CONFIG_BY_TYPE.pdf.filename); + } + } } export default { diff --git a/src/components/Settings/Export/Export.messages.js b/src/components/Settings/Export/Export.messages.js index 3854df3e2..71092f5a7 100644 --- a/src/components/Settings/Export/Export.messages.js +++ b/src/components/Settings/Export/Export.messages.js @@ -8,5 +8,9 @@ export default defineMessages({ exportSecondary: { id: 'cboard.components.Settings.Export.exportSecondary', defaultMessage: 'Export your board to Cboard format or {link} format' + }, + boardDownloaded: { + id: 'cboard.components.Settings.Export.boardDownloaded', + defaultMessage: 'Your board was downloaded' } }); diff --git a/src/components/UI/ColorSelect/ColorSelect.js b/src/components/UI/ColorSelect/ColorSelect.js index 3a2efe14e..2e6c3a357 100644 --- a/src/components/UI/ColorSelect/ColorSelect.js +++ b/src/components/UI/ColorSelect/ColorSelect.js @@ -6,65 +6,137 @@ import FormLabel from '@material-ui/core/FormLabel'; import Radio from '@material-ui/core/Radio'; import RadioGroup from '@material-ui/core/RadioGroup'; import CloseIcon from '@material-ui/icons/Close'; +import Button from '@material-ui/core/Button'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; import IconButton from '../IconButton'; import Circle from './Circle'; import messages from './ColorSelect.messages'; +const colorSchemes = [ + { + name: 'Cboard', + colors: ['#bbdefb', '#fff176', '#CE93D8', '#2196F3', '#4CAF50', '#E57373'] + }, + { + name: 'Fitzgerald', + colors: [ + '#2196F3', + '#4CAF50', + '#fff176', + '#ff6600', + '#ffffff', + '#ffc0cb', + '#800080', + '#a52a2a', + '#ff0000', + '#808080' + ] + }, + { + name: 'Goossens', + colors: ['#ffc0cb', '#2196F3', '#4CAF50', '#fff176', '#ff6600'] + } +]; + const propTypes = { - colors: PropTypes.arrayOf(PropTypes.string), intl: intlShape.isRequired, onChange: PropTypes.func.isRequired, selectedColor: PropTypes.string.isRequired }; -const defaultProps = { - colors: ['#CE93D8', '#2196F3', '#4CAF50', '#E57373'] -}; +class ColorSelect extends React.Component { + constructor(props) { + super(props); -const ColorSelect = props => { - const { colors, intl, onChange, selectedColor } = props; + this.state = { + colorMenu: null, + colors: colorSchemes[0].colors + }; + } + handleOpenColorSchemeMenu(event) { + this.setState({ colorMenu: event.currentTarget }); + } + handleColorSchemeClose(colorScheme) { + this.setState({ colors: colorScheme.colors, colorMenu: null }); + } - const colorLabel = intl.formatMessage(messages.color); - const radioGroupStyle = { flexDirection: 'row' }; - const circleStrokeWidth = 2; + render() { + const { intl, onChange, selectedColor } = this.props; + const colorLabel = intl.formatMessage(messages.color); + const radioGroupStyle = { flexDirection: 'row' }; + const radioItemStyle = { padding: '4px' }; + const circleStrokeWidth = 2; - return ( - - {colorLabel} - - {colors.map(color => ( - } - checkedIcon={ - - } - /> - ))} - {selectedColor && ( - { - onChange(); - }} + return ( + + {colorLabel} +
+ + + + {colorSchemes[0].name} + + + {colorSchemes[1].name} + + + {colorSchemes[2].name} + + +
+ + {this.state.colors.map(color => ( + } + checkedIcon={ + + } + /> + ))} + {selectedColor && ( + { + onChange(); + }} + > + + + )} + +
+ ); + } +} ColorSelect.propTypes = propTypes; -ColorSelect.defaultProps = defaultProps; - export default injectIntl(ColorSelect); diff --git a/src/components/UI/ColorSelect/ColorSelect.messages.js b/src/components/UI/ColorSelect/ColorSelect.messages.js index 3eb7c6604..9019b3f4b 100644 --- a/src/components/UI/ColorSelect/ColorSelect.messages.js +++ b/src/components/UI/ColorSelect/ColorSelect.messages.js @@ -8,5 +8,9 @@ export default defineMessages({ clearSelection: { id: 'cboard.components.ColorSelect.clearSelection', defaultMessage: 'Clear selection' + }, + colorScheme: { + id: 'cboard.components.ColorSelect.colorScheme', + defaultMessage: 'Color Scheme' } }); diff --git a/src/components/UI/ColorSelect/ColorSelect.test.js b/src/components/UI/ColorSelect/ColorSelect.test.js index d17d74c79..c0302ba4e 100644 --- a/src/components/UI/ColorSelect/ColorSelect.test.js +++ b/src/components/UI/ColorSelect/ColorSelect.test.js @@ -11,6 +11,10 @@ jest.mock('./ColorSelect.messages', () => { clearSelection: { id: 'cboard.components.ColorSelect.clearSelection', defaultMessage: 'Clear selection' + }, + colorScheme: { + id: 'cboard.components.ColorSelect.colorScheme', + defaultMessage: 'Color Scheme' } }; }); diff --git a/src/components/UI/PrintBoardButton/PrintBoardButton.container.js b/src/components/UI/PrintBoardButton/PrintBoardButton.container.js index 22b5ef261..affc60b16 100644 --- a/src/components/UI/PrintBoardButton/PrintBoardButton.container.js +++ b/src/components/UI/PrintBoardButton/PrintBoardButton.container.js @@ -5,7 +5,7 @@ import { intlShape, injectIntl } from 'react-intl'; import PrintBoardButton from './PrintBoardButton.component'; import PrintBoardDialog from './PrintBoardDialog.component'; import messages from './PrintBoardButton.messages'; -// import { pdfExportAdapter } from '../../Settings/Export/Export.helpers'; +import { showNotification } from '../../Notifications/Notifications.actions'; class PrintBoardButtonContainer extends React.Component { constructor(props) { @@ -31,22 +31,16 @@ class PrintBoardButtonContainer extends React.Component { async onPrintCurrentBoard() { this.setState({ loading: true }); - const { boardData, intl } = this.props; + const { boardData, intl, showNotification } = this.props; const currentBoard = boardData.boards.find( board => board.id === boardData.activeBoardId ); const { pdfExportAdapter } = await this.exportHelpers; - pdfExportAdapter([currentBoard], intl); - this.setState({ loading: false }); - } - - async onPrintFullBoardSet() { - this.setState({ loading: true }); - const { boardData, intl } = this.props; - const { pdfExportAdapter } = await this.exportHelpers; - pdfExportAdapter(boardData.boards, intl); - this.setState({ loading: false }); + pdfExportAdapter([currentBoard], intl).then(() => { + this.setState({ loading: false }); + showNotification(intl.formatMessage(messages.boardDownloaded)); + }); } render() { @@ -66,7 +60,6 @@ class PrintBoardButtonContainer extends React.Component { open={this.state.openDialog} onClose={this.closePrintBoardDialog.bind(this)} onPrintCurrentBoard={this.onPrintCurrentBoard.bind(this)} - onPrintFullBoardSet={this.onPrintFullBoardSet.bind(this)} />
); @@ -83,4 +76,11 @@ const mapStateToProps = state => ({ boardData: state.board }); -export default connect(mapStateToProps)(injectIntl(PrintBoardButtonContainer)); +const mapDispatchToProps = { + showNotification +}; + +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(PrintBoardButtonContainer)); diff --git a/src/components/UI/PrintBoardButton/PrintBoardButton.messages.js b/src/components/UI/PrintBoardButton/PrintBoardButton.messages.js index 5add7f9b9..23718d58e 100644 --- a/src/components/UI/PrintBoardButton/PrintBoardButton.messages.js +++ b/src/components/UI/PrintBoardButton/PrintBoardButton.messages.js @@ -7,8 +7,7 @@ export default defineMessages({ }, printBoardSecondary: { id: 'cboard.components.PrintBoardButton.printBoardSecondary', - defaultMessage: - 'Print current board or full board set (it can take a while).' + defaultMessage: 'Print current board (it can take a while).' }, printCurrentBoard: { id: 'cboard.components.PrintBoardButton.printCurrentBoard', @@ -17,5 +16,9 @@ export default defineMessages({ printFullBoardSet: { id: 'cboard.components.PrintBoardButton.printFullBoardSet', defaultMessage: 'Full Board Set' + }, + boardDownloaded: { + id: 'cboard.components.PrintBoardButton.boardDownloaded', + defaultMessage: 'Your board was downloaded' } }); diff --git a/src/components/UI/PrintBoardButton/PrintBoardDialog.component.js b/src/components/UI/PrintBoardButton/PrintBoardDialog.component.js index f548b7129..d1b3c6dfb 100644 --- a/src/components/UI/PrintBoardButton/PrintBoardDialog.component.js +++ b/src/components/UI/PrintBoardButton/PrintBoardDialog.component.js @@ -18,8 +18,7 @@ const PrintBoardDialog = ({ open, loading, onClose, - onPrintCurrentBoard, - onPrintFullBoardSet + onPrintCurrentBoard }) => ( - @@ -67,8 +63,7 @@ PrintBoardDialog.defaultProps = { open: false, loading: false, onClose: () => {}, - onPrintCurrentBoard: () => {}, - onPrintFullBoardSet: () => {} + onPrintCurrentBoard: () => {} }; PrintBoardDialog.propTypes = { @@ -76,8 +71,7 @@ PrintBoardDialog.propTypes = { open: PropTypes.bool, loading: PropTypes.bool, onClose: PropTypes.func, - onPrintCurrentBoard: PropTypes.func, - onPrintFullBoardSet: PropTypes.func + onPrintCurrentBoard: PropTypes.func }; export default PrintBoardDialog; diff --git a/src/components/UI/PrintBoardButton/__snapshots__/PrintBoardDialog.test.js.snap b/src/components/UI/PrintBoardButton/__snapshots__/PrintBoardDialog.test.js.snap index b05f0590a..093e156ac 100644 --- a/src/components/UI/PrintBoardButton/__snapshots__/PrintBoardDialog.test.js.snap +++ b/src/components/UI/PrintBoardButton/__snapshots__/PrintBoardDialog.test.js.snap @@ -43,16 +43,6 @@ exports[`PrintBoardDialog tests default renderer 1`] = ` values={Object {}} /> - - - @@ -109,16 +99,6 @@ exports[`PrintBoardDialog tests loading renderer 1`] = ` values={Object {}} /> - - - @@ -170,16 +150,6 @@ exports[`PrintBoardDialog tests open dialog 1`] = ` values={Object {}} /> - - - diff --git a/src/components/UI/UserIcon/__snapshots__/UserIcon.test.js.snap b/src/components/UI/UserIcon/__snapshots__/UserIcon.test.js.snap index c902f436b..7ab45abed 100644 --- a/src/components/UI/UserIcon/__snapshots__/UserIcon.test.js.snap +++ b/src/components/UI/UserIcon/__snapshots__/UserIcon.test.js.snap @@ -7,7 +7,6 @@ exports[`UserIcon tests default renderer 1`] = ` diff --git a/src/components/WelcomeScreen/WelcomeScreen.container.js b/src/components/WelcomeScreen/WelcomeScreen.container.js index cb5dd8d3f..ba43934f6 100644 --- a/src/components/WelcomeScreen/WelcomeScreen.container.js +++ b/src/components/WelcomeScreen/WelcomeScreen.container.js @@ -16,6 +16,7 @@ import SignUp from '../Account/SignUp'; import CboardLogo from './CboardLogo/CboardLogo.component'; import './WelcomeScreen.css'; import { API_URL } from '../../constants'; +import { isCordova } from '../../cordova-util'; export class WelcomeScreen extends Component { state = { @@ -67,24 +68,27 @@ export class WelcomeScreen extends Component { > + {!isCordova() && ( +
+ { + window.location = `${API_URL}/login/google`; + }} + > + + - { - window.location = `${API_URL}/login/google`; - }} - > - - - - { - window.location = `${API_URL}/login/facebook`; - }} - > - - + { + window.location = `${API_URL}/login/facebook`; + }} + > + + +
+ )}