diff --git a/src/components/Board/Board.container.js b/src/components/Board/Board.container.js
index 62a6aad54..533e568eb 100644
--- a/src/components/Board/Board.container.js
+++ b/src/components/Board/Board.container.js
@@ -1541,6 +1541,17 @@ export class BoardContainer extends Component {
}
};
+ handleAddApiBoard = async boardId => {
+ if (!this.props.boards.find(board => board.id === boardId)) {
+ try {
+ const board = await API.getBoard(boardId);
+ this.props.addBoards([board]);
+ } catch (err) {
+ console.log(err.message);
+ }
+ }
+ };
+
render() {
const {
navHistory,
@@ -1695,6 +1706,8 @@ export class BoardContainer extends Component {
this.props.communicator.boards.includes(board.id)
)}
userData={this.props.userData}
+ folders={this.props.boards}
+ onAddApiBoard={this.handleAddApiBoard}
/>
);
diff --git a/src/components/Board/TileEditor/LoadBoardEditor/LoadBoardEditor.js b/src/components/Board/TileEditor/LoadBoardEditor/LoadBoardEditor.js
new file mode 100644
index 000000000..3b8609b2d
--- /dev/null
+++ b/src/components/Board/TileEditor/LoadBoardEditor/LoadBoardEditor.js
@@ -0,0 +1,365 @@
+import React, { Fragment, useMemo } from 'react';
+import { alpha, makeStyles } from '@material-ui/core/styles';
+import Button from '@material-ui/core/Button';
+import Dialog from '@material-ui/core/Dialog';
+import ListItemText from '@material-ui/core/ListItemText';
+import ListItem from '@material-ui/core/ListItem';
+import List from '@material-ui/core/List';
+import Divider from '@material-ui/core/Divider';
+import AppBar from '@material-ui/core/AppBar';
+import Toolbar from '@material-ui/core/Toolbar';
+import IconButton from '@material-ui/core/IconButton';
+import Typography from '@material-ui/core/Typography';
+import CloseIcon from '@material-ui/icons/Close';
+import Slide from '@material-ui/core/Slide';
+import {
+ CircularProgress,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+ InputBase
+} from '@material-ui/core';
+import { Edit, Search as SearchIcon, Visibility } from '@material-ui/icons';
+import useAllBoardsFetcher from './useAllBoardsFetcher';
+import styles from './LoadBoardEditor.module.css';
+import { Alert, AlertTitle, Pagination } from '@material-ui/lab';
+import PropTypes from 'prop-types';
+import { intlShape } from 'react-intl';
+import communicatorMessages from '../../../Communicator/CommunicatorDialog/CommunicatorDialog.messages';
+import messages from './LoadBoardEditor.messages';
+import moment from 'moment';
+import { isCordova } from '../../../../cordova-util';
+import { debounce } from 'lodash';
+
+const useStyles = makeStyles(theme => ({
+ appBar: {
+ position: 'sticky'
+ },
+ title: {
+ marginLeft: theme.spacing(2),
+ flex: 1
+ },
+ search: {
+ position: 'relative',
+ borderRadius: theme.shape.borderRadius,
+ backgroundColor: alpha(theme.palette.common.white, 0.15),
+ '&:hover': {
+ backgroundColor: alpha(theme.palette.common.white, 0.25)
+ },
+ marginLeft: 0,
+ width: '100%',
+ [theme.breakpoints.up('sm')]: {
+ marginLeft: theme.spacing(1),
+ width: 'auto'
+ }
+ },
+ searchIcon: {
+ padding: theme.spacing(0, 2),
+ height: '100%',
+ position: 'absolute',
+ pointerEvents: 'none',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center'
+ },
+ inputRoot: {
+ color: 'inherit'
+ },
+ inputInput: {
+ padding: theme.spacing(1, 1, 1, 0),
+ // vertical padding + font size from searchIcon
+ paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
+ transition: theme.transitions.create('width'),
+ width: '100%',
+ [theme.breakpoints.up('sm')]: {
+ width: '12ch',
+ '&:focus': {
+ width: '20ch'
+ }
+ }
+ }
+}));
+
+const Transition = React.forwardRef(function Transition(props, ref) {
+ return ;
+});
+
+const BoardPagination = ({ pagesCount, currentPage, handleChange }) => {
+ return (
+
+ {pagesCount >= 1 && (
+
+ )}
+
+ );
+};
+
+const BoardInfoContent = ({ intl, pageBoards, selectedBoardId }) => {
+ const board = pageBoards.find(({ id }) => id === selectedBoardId);
+ const boardUrl =
+ window.location.origin +
+ '/' +
+ window.location.pathname.split('/')[1] +
+ '/' +
+ board.id;
+
+ return (
+
+
+
+ {intl.formatMessage(communicatorMessages.boardInfoName)}: {' '}
+ {board.name}
+
+
+ {intl.formatMessage(communicatorMessages.boardDescription)}: {' '}
+ {board.description}
+
+
+ {intl.formatMessage(communicatorMessages.boardInfoDate)}: {' '}
+ {moment(board.lastEdited).format('DD/MM/YYYY')}
+
+
+ {intl.formatMessage(communicatorMessages.boardInfoTiles)}: {' '}
+ {board.tiles.length}
+
+
+ {intl.formatMessage(communicatorMessages.boardInfoId)}: {' '}
+ {board.id}
+
+ {!isCordova() && (
+ }
+ >
+ {intl.formatMessage(messages.openBoardInNewTab)}
+
+ )}
+
+
+ );
+};
+
+const LoadBoardEditor = ({ intl, onLoadBoardChange, isLostedFolder }) => {
+ const classes = useStyles();
+ const [open, setOpen] = React.useState(false);
+ const {
+ pageBoards,
+ totalPages,
+ loading,
+ error,
+ fetchBoards
+ } = useAllBoardsFetcher();
+ const [currentPage, setCurrentPage] = React.useState(1);
+
+ const BoardsList = ({ onItemClick }) => {
+ return (
+
+ {pageBoards?.map(({ id, name, lastEdited }) => (
+
+ onItemClick(id)}>
+
+
+
+
+ ))}
+
+ );
+ };
+
+ const handleClickOpen = () => {
+ fetchBoards({});
+ setSearchValue('');
+ setCurrentPage(1);
+ setOpen(true);
+ };
+
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ const handleChangeOnPage = (event, page) => {
+ setCurrentPage(page);
+ fetchBoards({ page, search: searchValue ?? null });
+ };
+
+ const [openConfirmationDialog, setOpenConfirmationDialog] = React.useState(
+ false
+ );
+ const [selectedBoardId, setSelectedBoardId] = React.useState(null);
+
+ const handleOnItemClick = boardId => {
+ setSelectedBoardId(boardId);
+ setOpenConfirmationDialog(true);
+ };
+
+ const [searchValue, setSearchValue] = React.useState('');
+ const debounceSearch = useMemo(
+ () =>
+ debounce(value => {
+ setSearchValue(value);
+ setCurrentPage(1);
+ fetchBoards({ page: 1, search: value });
+ }, 500),
+ [fetchBoards]
+ );
+
+ const onSearchChange = e => {
+ const searchValue = e.target.value;
+ debounceSearch(searchValue);
+ };
+
+ return (
+ <>
+ {!isLostedFolder ? (
+
+
+
+ ) : (
+ }
+ variant="outlined"
+ color="primary"
+ onClick={handleClickOpen}
+ className={styles.searchButton}
+ style={{ marginTop: '8px' }}
+ >
+ {intl.formatMessage(messages.searchFolder)}
+
+ )}
+
+
+
+
+
+
+
+ {intl.formatMessage(messages.searchForAFolder)}
+
+
+
+
+
+ {!loading && !error && (
+
+ )}
+ {loading && (
+
+
+
+ )}
+ {error && (
+
+
+ {intl.formatMessage(messages.errorGettingFolders)}
+
+
+ {intl.formatMessage(messages.tryAgain)}
+
+
+ )}
+ {!loading && !error && totalPages >= 1 && (
+
+ )}
+ {!loading && !error && totalPages === 0 && (
+
+ {intl.formatMessage(messages.noBoardsFound)}'{searchValue}'
+
+ )}
+ {!loading && !error && (
+
+ )}
+
+
+ setOpenConfirmationDialog(false)}
+ aria-labelledby="alert-dialog-title"
+ aria-describedby="alert-dialog-description"
+ >
+
+ {intl.formatMessage(messages.confirmationTitle)}
+
+
+
+ setOpenConfirmationDialog(false)}
+ >
+ {intl.formatMessage(messages.cancel)}
+
+ {
+ onLoadBoardChange({ boardId: selectedBoardId });
+ setOpenConfirmationDialog(false);
+ setOpen(false);
+ }}
+ >
+ {intl.formatMessage(messages.accept)}
+
+
+
+ >
+ );
+};
+
+LoadBoardEditor.propTypes = {
+ intl: intlShape,
+ onLoadBoardChange: PropTypes.func,
+ isLostedFolder: PropTypes.bool
+};
+
+export default LoadBoardEditor;
diff --git a/src/components/Board/TileEditor/LoadBoardEditor/LoadBoardEditor.messages.js b/src/components/Board/TileEditor/LoadBoardEditor/LoadBoardEditor.messages.js
new file mode 100644
index 000000000..32c84e812
--- /dev/null
+++ b/src/components/Board/TileEditor/LoadBoardEditor/LoadBoardEditor.messages.js
@@ -0,0 +1,46 @@
+import { defineMessages } from 'react-intl';
+
+export default defineMessages({
+ confirmationTitle: {
+ id: 'cboard.components.LoadBoardEditor.confirmationTitle',
+ defaultMessage:
+ 'Are you sure you want change the board to open by this tile?'
+ },
+ openBoardInNewTab: {
+ id: 'cboard.components.LoadBoardEditor.openBoardInNewTab',
+ defaultMessage: 'view in new tab'
+ },
+ accept: {
+ id: 'cboard.components.LoadBoardEditor.accept',
+ defaultMessage: 'Accept'
+ },
+ cancel: {
+ id: 'cboard.components.LoadBoardEditor.cancel',
+ defaultMessage: 'Cancel'
+ },
+ searchFolder: {
+ id: 'cboard.components.LoadBoardEditor.searchFolder',
+ defaultMessage: 'Search folder'
+ },
+ searchForAFolder: {
+ id: 'cboard.components.LoadBoardEditor.searchForAFolder',
+ defaultMessage: 'Search for a folder'
+ },
+ searchPlaceholder: {
+ id: 'cboard.components.LoadBoardEditor.searchPlaceholder',
+ defaultMessage: 'Search…'
+ },
+ errorGettingFolders: {
+ id: 'cboard.components.LoadBoardEditor.errorGettingFolders',
+ defaultMessage:
+ 'Error getting all your folders. Please be sure that you have internet connection to use this feature.'
+ },
+ tryAgain: {
+ id: 'cboard.components.LoadBoardEditor.tryAgain',
+ defaultMessage: 'Try Again'
+ },
+ noBoardsFound: {
+ id: 'cboard.components.LoadBoardEditor.noBoardsFound',
+ defaultMessage: 'No boards found for: '
+ }
+});
diff --git a/src/components/Board/TileEditor/LoadBoardEditor/LoadBoardEditor.module.css b/src/components/Board/TileEditor/LoadBoardEditor/LoadBoardEditor.module.css
new file mode 100644
index 000000000..40a8ac3d3
--- /dev/null
+++ b/src/components/Board/TileEditor/LoadBoardEditor/LoadBoardEditor.module.css
@@ -0,0 +1,28 @@
+.loaderContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ margin: 16px;
+}
+
+.boardsListContainer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+}
+
+.pagination {
+ padding: 8px;
+}
+
+.boardsList {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+}
+
+.searchButton {
+ margin-top: 8px;
+}
diff --git a/src/components/Board/TileEditor/LoadBoardEditor/useAllBoardsFetcher.js b/src/components/Board/TileEditor/LoadBoardEditor/useAllBoardsFetcher.js
new file mode 100644
index 000000000..72f00af68
--- /dev/null
+++ b/src/components/Board/TileEditor/LoadBoardEditor/useAllBoardsFetcher.js
@@ -0,0 +1,34 @@
+import { useState } from 'react';
+import API from '../../../../api';
+
+const useBoardsFetcher = () => {
+ const [pageBoards, setPageBoards] = useState(null);
+ const [totalPages, setTotalBoards] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const fetchBoards = async ({ page = 1, search = '' }) => {
+ try {
+ const LIMIT = 10;
+ setLoading(true);
+ setError(null);
+ const response = await API.getMyBoards({
+ limit: LIMIT,
+ sort: 'name',
+ page: page,
+ search
+ });
+ setPageBoards(response.data);
+ const totalPages = Math.ceil(response.total / LIMIT);
+ setTotalBoards(totalPages);
+ setLoading(false);
+ } catch (error) {
+ setError(error);
+ setLoading(false);
+ }
+ };
+
+ return { fetchBoards, pageBoards, totalPages, loading, error };
+};
+
+export default useBoardsFetcher;
diff --git a/src/components/Board/TileEditor/TileEditor.component.js b/src/components/Board/TileEditor/TileEditor.component.js
index 7d8bce603..00b4fd6ef 100644
--- a/src/components/Board/TileEditor/TileEditor.component.js
+++ b/src/components/Board/TileEditor/TileEditor.component.js
@@ -42,6 +42,9 @@ import {
} from '../../../cordova-util';
import { convertImageUrlToCatchable } from '../../../helpers';
import PremiumFeature from '../../PremiumFeature';
+import LoadBoardEditor from './LoadBoardEditor/LoadBoardEditor';
+import { Typography } from '@material-ui/core';
+import { Alert, AlertTitle } from '@material-ui/lab';
export class TileEditor extends Component {
static propTypes = {
@@ -70,7 +73,9 @@ export class TileEditor extends Component {
*/
onAddSubmit: PropTypes.func.isRequired,
boards: PropTypes.array,
- userData: PropTypes.object
+ userData: PropTypes.object,
+ folders: PropTypes.array,
+ onAddApiBoard: PropTypes.func
};
static defaultProps = {
@@ -424,6 +429,13 @@ export class TileEditor extends Component {
}
};
+ handleLoadBoardChange = ({ boardId }) => {
+ if (boardId) {
+ this.props.onAddApiBoard(boardId);
+ this.updateTileProperty('loadBoard', boardId);
+ }
+ };
+
handleOnClickImageEditor = () => {
this.setState({ openImageEditor: true });
};
@@ -451,7 +463,7 @@ export class TileEditor extends Component {
};
render() {
- const { open, intl, boards } = this.props;
+ const { open, intl, boards, folders } = this.props;
const currentLabel = this.currentTileProp('labelKey')
? intl.formatMessage({ id: this.currentTileProp('labelKey') })
: this.currentTileProp('label');
@@ -465,7 +477,7 @@ export class TileEditor extends Component {
);
const selectBoardElement = (
-
+
{intl.formatMessage(messages.existingBoards)}
@@ -498,6 +510,27 @@ export class TileEditor extends Component {
? this.editingTile()
: this.state.tile;
+ const loadBoard = this.currentTileProp('loadBoard');
+ const loadBoardName = loadBoard
+ ? folders.find(({ id }) => id === loadBoard)?.name
+ : null;
+ const SHORT_ID_MAX_LENGTH = 14;
+ const isLocalId = loadBoard.length < SHORT_ID_MAX_LENGTH;
+
+ const LostedFolderAlert = ({ isLocalId }) => {
+ const alertDescription = !isLocalId
+ ? intl.formatMessage(messages.loadBoardAlertDescription)
+ : intl.formatMessage(messages.loadBoardAlertDescriptionLocalId);
+ return (
+
+
+ {intl.formatMessage(messages.loadBoardAlertTitle)}
+
+ {alertDescription}
+
+ );
+ };
+
return (
)}
+
+ {this.currentTileProp('loadBoard')?.length > 0 && (
+ <>
+
+ {intl.formatMessage(messages.loadBoard)}
+
+
+ {loadBoardName ? (
+
+ {loadBoardName}
+
+ ) : (
+
+ )}
+
+
+ >
+ )}
{this.currentTileProp('type') === 'folder' &&
selectBoardElement}
diff --git a/src/components/Board/TileEditor/TileEditor.css b/src/components/Board/TileEditor/TileEditor.css
index 76ce606f4..ce08cad89 100644
--- a/src/components/Board/TileEditor/TileEditor.css
+++ b/src/components/Board/TileEditor/TileEditor.css
@@ -76,3 +76,14 @@
.TileEditor__radiogroup__formcontrollabel {
margin-top: 5px;
}
+
+.TileEditor__loadBoard_section {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ margin-top: 2px;
+}
+.TileEditor__loadBoard_Alert {
+ width: 100%;
+ margin-top: 4px;
+}
diff --git a/src/components/Board/TileEditor/TileEditor.messages.js b/src/components/Board/TileEditor/TileEditor.messages.js
index 300789b86..ea3f3d147 100644
--- a/src/components/Board/TileEditor/TileEditor.messages.js
+++ b/src/components/Board/TileEditor/TileEditor.messages.js
@@ -68,5 +68,28 @@ export default defineMessages({
editImage: {
id: 'cboard.components.Board.TileEditor.editImage',
defaultMessage: 'Edit image'
+ },
+ loadBoard: {
+ id: 'cboard.components.Board.TileEditor.loadBoard',
+ defaultMessage: 'Load folder'
+ },
+ loadBoardAlertTitle: {
+ id: 'cboard.components.Board.TileEditor.loadBoardAlertTitle',
+ defaultMessage: "We can't find this folder"
+ },
+ loadBoardAlertDescription: {
+ id: 'cboard.components.Board.TileEditor.loadBoardAlertDescription',
+ defaultMessage:
+ 'Try to find it manualy on your remote folders by clicking on the search button.'
+ },
+ loadBoardAlertDescriptionLocalId: {
+ id: 'cboard.components.Board.TileEditor.loadBoardAlertDescriptionLocalId',
+ defaultMessage: `It's looks like this folder is localy stored on the device that you
+ create it. If you want to use it, please make a change in it connected
+ to the internet. Or edit this value to use another folder.`
+ },
+ loadBoardAlertSearch: {
+ id: 'cboard.components.Board.TileEditor.loadBoardAlertSearch',
+ defaultMessage: 'Search folder'
}
});
diff --git a/src/translations/src/cboard.json b/src/translations/src/cboard.json
index 3cba6b95c..d6c376209 100644
--- a/src/translations/src/cboard.json
+++ b/src/translations/src/cboard.json
@@ -164,6 +164,18 @@
"cboard.components.Board.TileEditor.none": "None",
"cboard.components.Board.TileEditor.symbols": "Symbols",
"cboard.components.Board.TileEditor.editImage": "Edit image",
+ "cboard.components.Board.TileEditor.loadBoard": "Load folder",
+ "cboard.components.Board.TileEditor.loadBoardAlertTitle": "We can't find this folder",
+ "cboard.components.Board.TileEditor.loadBoardAlertDescription": "Try to find it manualy on your remote folders by clicking on the search button.",
+ "cboard.components.Board.TileEditor.loadBoardAlertDescriptionLocalId": "It's looks like this folder is localy stored on the device that you create it. If you want to use it, please make a change in it connected to the internet. Or edit this value to use another folder.",
+ "cboard.components.Board.TileEditor.loadBoardAlertSearch": "Search folder",
+ "cboard.components.LoadBoardEditor.searchFolder": "Search folder",
+ "cboard.components.LoadBoardEditor.searchForAFolder": "Search for a folder",
+ "cboard.components.LoadBoardEditor.searchPlaceholder": "Search…",
+ "cboard.components.LoadBoardEditor.errorGettingFolders": "Error getting all your folders. Please be sure that you have internet connection to use this feature.",
+ "cboard.components.LoadBoardEditor.tryAgain": "Try Again",
+ "cboard.components.LoadBoardEditor.noBoardsFound": "No boards found for '{searchValue}'",
+ "cboard.components.LoadBoardEditor.openBoardInNewTab": "Open in new tab",
"cboard.components.Board.boardEditTitleCancel": "Cancel",
"cboard.components.Board.boardEditTitleAccept": "Accept",
"cboard.components.Board.userProfileLocked": "User Profile is locked, please unlock settings to see your user profile.",
@@ -611,6 +623,9 @@
"cboard.components.UI.Downloader.processing": "Processing...",
"cboard.components.UI.Downloader.processingDone": "Process Done!",
"cboard.components.UI.Downloader.processingError": "There was an error processing the data, please try again.",
+ "cboard.components.LoadBoardEditor.confirmationTitle": "Are you sure you want change the board to open by this tile?",
+ "cboard.components.LoadBoardEditor.accept": "Accept",
+ "cboard.components.LoadBoardEditor.cancel": "Cancel",
"cboard.board.home": "home",
"cboard.vocalization.myNameIsAmberley": "my name is Amberley",
"cboard.vocalization.niceToMeetYou": "nice to meet you",