diff --git a/packages/webapp/lang/en.json b/packages/webapp/lang/en.json index af47f8ad..fd3a1edc 100644 --- a/packages/webapp/lang/en.json +++ b/packages/webapp/lang/en.json @@ -23,15 +23,18 @@ "action.history": { "defaultMessage": "History" }, + "action.history-description": { + "defaultMessage": "List of changes introduced in the last 90 days." + }, + "action.history-title": { + "defaultMessage": "Version history" + }, "action.import": { "defaultMessage": "Import" }, "action.info": { "defaultMessage": "Info" }, - "action.info-title": { - "defaultMessage": "Info" - }, "action.label": { "defaultMessage": "Add Label" }, @@ -50,6 +53,12 @@ "action.rename": { "defaultMessage": "Rename" }, + "action.rename-description-placeholder": { + "defaultMessage": "Description" + }, + "action.rename-name-placeholder": { + "defaultMessage": "Name" + }, "action.share": { "defaultMessage": "Share" }, @@ -68,6 +77,12 @@ "duplicate.title": { "defaultMessage": "Duplicate" }, + "expired.description": { + "defaultMessage": "Your current session has expired. Please, sign in and try again." + }, + "expired.title": { + "defaultMessage": "Your session has expired" + }, "footer.aboutus": { "defaultMessage": "About Us" }, @@ -116,6 +131,9 @@ "login.desc": { "defaultMessage": "Log into your account" }, + "login.email": { + "defaultMessage": "Email" + }, "login.error": { "defaultMessage": "The email address or password you entered is not valid." }, @@ -126,6 +144,9 @@ "defaultMessage": "Although HSQLDB is bundled with WiseMapping by default during the installation, we do not recommend this database for production use. Please consider using MySQL 5.7 instead. You can find more information how to configure MySQL", "description": "Missing production database configured" }, + "login.password": { + "defaultMessage": "Password" + }, "login.remberme": { "defaultMessage": "Remember me" }, @@ -141,6 +162,33 @@ "login.userinactive": { "defaultMessage": "Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!." }, + "map.creator": { + "defaultMessage": "Creator" + }, + "map.last-update": { + "defaultMessage": "Last Update" + }, + "map.more-actions": { + "defaultMessage": "More Actions" + }, + "map.name": { + "defaultMessage": "Name" + }, + "maps.empty-result": { + "defaultMessage": "No matching record found with the current filter criteria." + }, + "maps.modified": { + "defaultMessage": "Modified" + }, + "maps.modified-by": { + "defaultMessage": "Modified By" + }, + "maps.revert": { + "defaultMessage": "Revert" + }, + "maps.view": { + "defaultMessage": "View" + }, "menu.account": { "defaultMessage": "Account" }, diff --git a/packages/webapp/src/client/index.ts b/packages/webapp/src/client/index.ts index 622f2115..02800612 100644 --- a/packages/webapp/src/client/index.ts +++ b/packages/webapp/src/client/index.ts @@ -25,7 +25,7 @@ export type MapInfo = { role: 'owner' | 'editor' | 'viewer' } -export type HistoryChange = { +export type ChangeHistory = { id: number; creator: string; modified: string; @@ -62,6 +62,9 @@ interface Client { registerNewUser(user: NewUser): Promise; resetPassword(email: string): Promise; + + fetchHistory(id:number):Promise; + revertHistory(id:number,cid:number): Promise } diff --git a/packages/webapp/src/client/mock-client/index.ts b/packages/webapp/src/client/mock-client/index.ts index 6be49f56..91dedbc9 100644 --- a/packages/webapp/src/client/mock-client/index.ts +++ b/packages/webapp/src/client/mock-client/index.ts @@ -1,4 +1,4 @@ -import Client, { BasicMapInfo, Label, MapInfo, NewUser } from '..'; +import Client, { BasicMapInfo, ChangeHistory, Label, MapInfo, NewUser } from '..'; class MockClient implements Client { private maps: MapInfo[] = []; @@ -41,6 +41,9 @@ class MockClient implements Client { ]; } + revertHistory(id: number, cid: number): Promise { + return Promise.resolve(); + } createMap(map: BasicMapInfo): Promise { throw new Error("Method not implemented."); @@ -91,6 +94,46 @@ class MockClient implements Client { }) }; } + fetchHistory(id: number): Promise { + const result = [{ + id: 1, + creator: 'Paulo', + modified: '2008-06-02T00:00:00Z' + }, + { + id: 2, + creator: 'Paulo', + modified: '2008-06-02T00:00:00Z' + } + , + { + id: 3, + creator: 'Paulo', + modified: '2008-06-02T00:00:00Z' + }, + { + id: 4, + creator: 'Paulo', + modified: '2008-06-02T00:00:00Z' + }, + { + id: 5, + creator: 'Paulo', + modified: '2008-06-02T00:00:00Z' + }, + { + id: 6, + creator: 'Paulo', + modified: '2008-06-02T00:00:00Z' + }, + { + id: 7, + creator: 'Paulo', + modified: '2008-06-02T00:00:00Z' + } + ] + return Promise.resolve(result); + } duplicateMap(id: number, basicInfo: BasicMapInfo): Promise { diff --git a/packages/webapp/src/client/rest-client/index.ts b/packages/webapp/src/client/rest-client/index.ts index c310aef5..30b7d161 100644 --- a/packages/webapp/src/client/rest-client/index.ts +++ b/packages/webapp/src/client/rest-client/index.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { useIntl } from 'react-intl'; -import Client, { ErrorInfo, MapInfo, BasicMapInfo, NewUser, Label } from '..'; +import Client, { ErrorInfo, MapInfo, BasicMapInfo, NewUser, Label, ChangeHistory } from '..'; export default class RestClient implements Client { private baseUrl: string; @@ -11,6 +11,14 @@ export default class RestClient implements Client { this.baseUrl = baseUrl; this.sessionExpired = sessionExpired; } + revertHistory(id: number, cid: number): Promise { + // '/c/restful/maps/${mindmapId}/history' + + throw new Error('Method not implemented.'); + } + fetchHistory(id: number): Promise { + throw new Error('Method not implemented.'); + } fetchMapInfo(id: number): Promise { throw new Error('Method not implemented.'); @@ -29,7 +37,7 @@ export default class RestClient implements Client { case 401: case 302: this.sessionExpired(); - result = { msg: intl.formatMessage({ id: "expired.title", defaultMessage: "Your current session has expired. Please, sign in and try again." })} + result = { msg: intl.formatMessage({ id: "expired.description", defaultMessage: "Your current session has expired. Please, sign in and try again." })} break; default: if (data) { diff --git a/packages/webapp/src/compiled-lang/en.json b/packages/webapp/src/compiled-lang/en.json index fb385ae8..422a080a 100644 --- a/packages/webapp/src/compiled-lang/en.json +++ b/packages/webapp/src/compiled-lang/en.json @@ -47,6 +47,18 @@ "value": "History" } ], + "action.history-description": [ + { + "type": 0, + "value": "List of changes introduced in the last 90 days." + } + ], + "action.history-title": [ + { + "type": 0, + "value": "Version history" + } + ], "action.import": [ { "type": 0, @@ -59,12 +71,6 @@ "value": "Info" } ], - "action.info-title": [ - { - "type": 0, - "value": "Info" - } - ], "action.label": [ { "type": 0, @@ -101,6 +107,18 @@ "value": "Rename" } ], + "action.rename-description-placeholder": [ + { + "type": 0, + "value": "Description" + } + ], + "action.rename-name-placeholder": [ + { + "type": 0, + "value": "Name" + } + ], "action.share": [ { "type": 0, @@ -137,6 +155,18 @@ "value": "Duplicate" } ], + "expired.description": [ + { + "type": 0, + "value": "Your current session has expired. Please, sign in and try again." + } + ], + "expired.title": [ + { + "type": 0, + "value": "Your session has expired" + } + ], "footer.aboutus": [ { "type": 0, @@ -233,6 +263,12 @@ "value": "Log into your account" } ], + "login.email": [ + { + "type": 0, + "value": "Email" + } + ], "login.error": [ { "type": 0, @@ -251,6 +287,12 @@ "value": "Although HSQLDB is bundled with WiseMapping by default during the installation, we do not recommend this database for production use. Please consider using MySQL 5.7 instead. You can find more information how to configure MySQL" } ], + "login.password": [ + { + "type": 0, + "value": "Password" + } + ], "login.remberme": [ { "type": 0, @@ -281,6 +323,60 @@ "value": "Sorry, your account has not been activated yet. You'll receive a notification email when it becomes active. Stay tuned!." } ], + "map.creator": [ + { + "type": 0, + "value": "Creator" + } + ], + "map.last-update": [ + { + "type": 0, + "value": "Last Update" + } + ], + "map.more-actions": [ + { + "type": 0, + "value": "More Actions" + } + ], + "map.name": [ + { + "type": 0, + "value": "Name" + } + ], + "maps.empty-result": [ + { + "type": 0, + "value": "No matching record found with the current filter criteria." + } + ], + "maps.modified": [ + { + "type": 0, + "value": "Modified" + } + ], + "maps.modified-by": [ + { + "type": 0, + "value": "Modified By" + } + ], + "maps.revert": [ + { + "type": 0, + "value": "Revert" + } + ], + "maps.view": [ + { + "type": 0, + "value": "View" + } + ], "menu.account": [ { "type": 0, diff --git a/packages/webapp/src/components/forgot-password-page/index.tsx b/packages/webapp/src/components/forgot-password-page/index.tsx index 6469cb9f..c42003be 100644 --- a/packages/webapp/src/components/forgot-password-page/index.tsx +++ b/packages/webapp/src/components/forgot-password-page/index.tsx @@ -49,7 +49,7 @@ const ForgotPassword = () => {
- setEmail(e.target.value)} error={error}/> diff --git a/packages/webapp/src/components/form/input/index.tsx b/packages/webapp/src/components/form/input/index.tsx index 09f9106a..781436b6 100644 --- a/packages/webapp/src/components/form/input/index.tsx +++ b/packages/webapp/src/components/form/input/index.tsx @@ -7,7 +7,7 @@ type InputProps = { name: string; error?: ErrorInfo; onChange?: (event: ChangeEvent) => void; - label: MessageDescriptor; + label: string; required?: boolean; type: string; value?: string @@ -27,7 +27,7 @@ const Input = (props: InputProps) => { const fullWidth = props.fullWidth != undefined ? props.required : true; return ( - diff --git a/packages/webapp/src/components/login-page/index.tsx b/packages/webapp/src/components/login-page/index.tsx index 104c522a..10bca522 100644 --- a/packages/webapp/src/components/login-page/index.tsx +++ b/packages/webapp/src/components/login-page/index.tsx @@ -67,8 +67,8 @@ const LoginPage = () => { - - + +
diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/create-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/create-dialog/index.tsx index a51bcbb1..6b4c1060 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/create-dialog/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/create-dialog/index.tsx @@ -68,10 +68,10 @@ const CreateDialog = (props: CreateProps) => { submitButton={intl.formatMessage({ id: 'create.button', defaultMessage: 'Create' })}> - - diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/duplicate-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/duplicate-dialog/index.tsx index ed9b20dd..0c2dbbd6 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/duplicate-dialog/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/duplicate-dialog/index.tsx @@ -77,10 +77,10 @@ const DuplicateDialog = (props: DialogProps) => { submitButton={intl.formatMessage({ id: 'duplicate.title', defaultMessage: 'Duplicate' })}> - - diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/history-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/history-dialog/index.tsx new file mode 100644 index 00000000..64439cf4 --- /dev/null +++ b/packages/webapp/src/components/maps-page/action-dispatcher/history-dialog/index.tsx @@ -0,0 +1,75 @@ +import React, { ErrorInfo } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; +import { useMutation, useQuery, useQueryClient } from "react-query"; +import { useSelector } from "react-redux"; +import Client, { ChangeHistory } from "../../../../client"; +import { activeInstance } from '../../../../redux/clientSlice'; +import { DialogProps, fetchMapById, handleOnMutationSuccess } from ".."; +import BaseDialog from "../base-dialog"; +import { Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip } from "@material-ui/core"; +import moment from "moment"; + + +const HistoryDialog = (props: DialogProps) => { + const intl = useIntl(); + const mapId = props.mapId; + + const client: Client = useSelector(activeInstance); + const { isLoading, error, data } = useQuery('history', () => { + return client.fetchHistory(mapId); + }); + const changeHistory: ChangeHistory[] = data ? data : []; + + const handleOnClose = (): void => { + props.onClose(); + }; + + const handleOnClick = (event,vid): void => { + event.preventDefault(); + client.revertHistory(mapId,vid) + .then((mapId)=>{ + handleOnClose(); + }) + }; + + + return ( +
+ + + + + + + + + + + + + {changeHistory.map((row) => ( + + {row.creator} + + + {moment(row.modified).fromNow()} + + + + handleOnClick(e,row.id)}> + + ))} + +
+
+ + +
+
+ ); +} + +export default HistoryDialog; \ No newline at end of file diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx index 162750db..6957da02 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import RenameDialog from './rename'; +import RenameDialog from './rename-dialog'; import DeleteDialog from './delete-dialog'; import { ActionType } from '../action-chooser'; import { ErrorInfo, MapInfo } from '../../../client'; @@ -10,6 +10,7 @@ import { activeInstance } from '../../../redux/clientSlice'; import DuplicateDialog from './duplicate-dialog'; import { useHistory } from 'react-router-dom'; import CreateDialog from './create-dialog'; +import HistoryDialog from './history-dialog'; export type BasicMapInfo = { name: string; @@ -46,6 +47,7 @@ const ActionDispatcher = (props: ActionDialogProps) => { + ); } diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/rename/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/rename-dialog/index.tsx similarity index 91% rename from packages/webapp/src/components/maps-page/action-dispatcher/rename/index.tsx rename to packages/webapp/src/components/maps-page/action-dispatcher/rename-dialog/index.tsx index 0cc36808..f8c9ae88 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/rename/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/rename-dialog/index.tsx @@ -76,10 +76,10 @@ const RenameDialog = (props: DialogProps) => { submitButton={intl.formatMessage({ id: 'rename.title', defaultMessage: 'Rename' })}> - - diff --git a/packages/webapp/src/components/maps-page/index.tsx b/packages/webapp/src/components/maps-page/index.tsx index bd1b1c12..53e842c1 100644 --- a/packages/webapp/src/components/maps-page/index.tsx +++ b/packages/webapp/src/components/maps-page/index.tsx @@ -259,7 +259,7 @@ const HandleClientStatus = () => { - + diff --git a/packages/webapp/src/components/maps-page/maps-list/index.tsx b/packages/webapp/src/components/maps-page/maps-list/index.tsx index 6af426f5..ef5ac856 100644 --- a/packages/webapp/src/components/maps-page/maps-list/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/index.tsx @@ -28,7 +28,7 @@ import { Button, InputBase, Link } from '@material-ui/core'; import SearchIcon from '@material-ui/icons/Search'; import moment from 'moment' import { Filter, LabelFilter } from '..'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import { DeleteOutlined, LabelTwoTone } from '@material-ui/icons'; import Alert from '@material-ui/lab/Alert'; @@ -71,12 +71,6 @@ interface HeadCell { style?: CSSProperties; } -const headCells: HeadCell[] = [ - { id: 'title', numeric: false, label: 'Name' }, - { id: 'labels', numeric: false }, - { id: 'creator', numeric: false, label: 'Creator', style: { width: '60px' } }, - { id: 'modified', numeric: true, label: 'Last Update', style: { width: '30px' } } -]; interface EnhancedTableProps { classes: ReturnType; @@ -89,12 +83,21 @@ interface EnhancedTableProps { } function EnhancedTableHead(props: EnhancedTableProps) { + const intl = useIntl(); + const { classes, onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } = props; const createSortHandler = (property: keyof MapInfo) => (event: React.MouseEvent) => { onRequestSort(event, property); }; + const headCells: HeadCell[] = [ + { id: 'title', numeric: false, label: intl.formatMessage({ id: 'map.name', defaultMessage: 'Name' }) }, + { id: 'labels', numeric: false }, + { id: 'creator', numeric: false, label: intl.formatMessage({ id: 'map.creator', defaultMessage: 'Creator' }), style: { width: '70px', whiteSpace: 'nowrap' } }, + { id: 'modified', numeric: true, label: intl.formatMessage({ id: 'map.last-update', defaultMessage: 'Last Update' }), style: { width: '70px', whiteSpace: 'nowrap' } } + ]; + return ( @@ -196,6 +199,7 @@ export const MapsList = (props: MapsListProps) => { const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); const client: Client = useSelector(activeInstance); + const intl = useIntl(); useEffect(() => { setSelected([]); @@ -456,7 +460,7 @@ export const MapsList = (props: MapsListProps) => { - + diff --git a/packages/webapp/src/components/registration-page/index.tsx b/packages/webapp/src/components/registration-page/index.tsx index 54d2e047..ec2cbcd7 100644 --- a/packages/webapp/src/components/registration-page/index.tsx +++ b/packages/webapp/src/components/registration-page/index.tsx @@ -71,16 +71,16 @@ const RegistrationForm = () => { - - - -