diff --git a/packages/webapp/lang/en.json b/packages/webapp/lang/en.json index 7e9e4cf7..a54c4a1b 100644 --- a/packages/webapp/lang/en.json +++ b/packages/webapp/lang/en.json @@ -134,6 +134,15 @@ "history.no-changes": { "defaultMessage": "There is no changes available" }, + "import.button": { + "defaultMessage": "Create" + }, + "import.description": { + "defaultMessage": "You can import FreeMind 1.0.1 and WiseMapping maps to your list of maps. Select the file you want to import." + }, + "import.title": { + "defaultMessage": "Import existing mindmap" + }, "login.desc": { "defaultMessage": "Log into your account" }, @@ -201,6 +210,30 @@ "menu.signout": { "defaultMessage": "Sign Out" }, + "publish.button": { + "defaultMessage": "Accept" + }, + "publish.checkbox": { + "defaultMessage": "Enable public sharing" + }, + "publish.description": { + "defaultMessage": "By publishing the map you make it visible to everyone on the Internet." + }, + "publish.embedded": { + "defaultMessage": "Embedded" + }, + "publish.embedded-msg": { + "defaultMessage": "Copy this snippet of code to embed in your blog or page:" + }, + "publish.public-url": { + "defaultMessage": "Public URL" + }, + "publish.public-url-msg": { + "defaultMessage": "Copy and paste the link below to share your map with colleagues:" + }, + "publish.title": { + "defaultMessage": "Publish" + }, "registration.desc": { "defaultMessage": "Signing up is free and just take a moment" }, diff --git a/packages/webapp/src/client/index.ts b/packages/webapp/src/client/index.ts index 02800612..0c8909fd 100644 --- a/packages/webapp/src/client/index.ts +++ b/packages/webapp/src/client/index.ts @@ -55,6 +55,7 @@ interface Client { duplicateMap(id: number, basicInfo: BasicMapInfo): Promise; fetchMapInfo(id: number): Promise; changeStarred(id: number, starred: boolean): Promise; + updateMapToPublic(id: number, starred: boolean): Promise; fetchLabels(): Promise; // createLabel(label: Label): Promise; diff --git a/packages/webapp/src/client/mock-client/index.ts b/packages/webapp/src/client/mock-client/index.ts index 91dedbc9..93d6b991 100644 --- a/packages/webapp/src/client/mock-client/index.ts +++ b/packages/webapp/src/client/mock-client/index.ts @@ -54,15 +54,21 @@ class MockClient implements Client { return Promise.resolve(this.labels); } + updateMapToPublic(id: number, isPublic: boolean): Promise { + const mapInfo = this.maps.find(m => m.id == id); + if (mapInfo) { + mapInfo.isPublic = isPublic; + } + return Promise.resolve(); + } + changeStarred(id: number, starred: boolean): Promise { const mapInfo = this.maps.find(m => m.id == id); if (!mapInfo) { console.log(`Could not find the map iwth id ${id}`); return Promise.reject(); } - const newStarredValue = !mapInfo?.starred; - mapInfo.starred = newStarredValue; - + mapInfo.starred = starred; return Promise.resolve(); } diff --git a/packages/webapp/src/client/rest-client/index.ts b/packages/webapp/src/client/rest-client/index.ts index 30b7d161..fb752259 100644 --- a/packages/webapp/src/client/rest-client/index.ts +++ b/packages/webapp/src/client/rest-client/index.ts @@ -11,6 +11,35 @@ export default class RestClient implements Client { this.baseUrl = baseUrl; this.sessionExpired = sessionExpired; } + + updateMapToPublic(id: number, isPublic: boolean): Promise { + /* + jQuery.ajax("c/restful/maps/${mindmap.id}/publish", { + async:false, + dataType:'json', + data:$('#dialogMainForm #enablePublicView')[0].checked ? 'true' : 'false', + type:'PUT', + contentType:"text/plain", + success:function (data, textStatus, jqXHR) { + $('#publish-dialog-modal').modal('hide'); + }, + */ + + const handler = (success: () => void, reject: (error: ErrorInfo) => void) => { + axios.put(`${this.baseUrl}/c/restful/maps/${id}/publish`, + isPublic, + { headers: { 'Content-Type': 'text/plain' } } + ).then(response => { + // All was ok, let's sent to success page ...; + success(); + }).catch(error => { + const response = error.response; + const errorInfo = this.parseResponseOnError(response); + reject(errorInfo); + }); + } + return new Promise(handler); + } revertHistory(id: number, cid: number): Promise { // '/c/restful/maps/${mindmapId}/history' @@ -37,7 +66,7 @@ export default class RestClient implements Client { case 401: case 302: this.sessionExpired(); - result = { msg: intl.formatMessage({ id: "expired.description", 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 39e085a9..79296854 100644 --- a/packages/webapp/src/compiled-lang/en.json +++ b/packages/webapp/src/compiled-lang/en.json @@ -269,6 +269,24 @@ "value": "There is no changes available" } ], + "import.button": [ + { + "type": 0, + "value": "Create" + } + ], + "import.description": [ + { + "type": 0, + "value": "You can import FreeMind 1.0.1 and WiseMapping maps to your list of maps. Select the file you want to import." + } + ], + "import.title": [ + { + "type": 0, + "value": "Import existing mindmap" + } + ], "login.desc": [ { "type": 0, @@ -401,6 +419,54 @@ "value": "Sign Out" } ], + "publish.button": [ + { + "type": 0, + "value": "Accept" + } + ], + "publish.checkbox": [ + { + "type": 0, + "value": "Enable public sharing" + } + ], + "publish.description": [ + { + "type": 0, + "value": "By publishing the map you make it visible to everyone on the Internet." + } + ], + "publish.embedded": [ + { + "type": 0, + "value": "Embedded" + } + ], + "publish.embedded-msg": [ + { + "type": 0, + "value": "Copy this snippet of code to embed in your blog or page:" + } + ], + "publish.public-url": [ + { + "type": 0, + "value": "Public URL" + } + ], + "publish.public-url-msg": [ + { + "type": 0, + "value": "Copy and paste the link below to share your map with colleagues:" + } + ], + "publish.title": [ + { + "type": 0, + "value": "Publish" + } + ], "registration.desc": [ { "type": 0, diff --git a/packages/webapp/src/components/maps-page/action-chooser/index.tsx b/packages/webapp/src/components/maps-page/action-chooser/index.tsx index abf0d829..f9e607ae 100644 --- a/packages/webapp/src/components/maps-page/action-chooser/index.tsx +++ b/packages/webapp/src/components/maps-page/action-chooser/index.tsx @@ -12,15 +12,16 @@ import ShareOutlinedIcon from '@material-ui/icons/ShareOutlined'; import { FormattedMessage } from 'react-intl'; import { LabelOutlined } from '@material-ui/icons'; -export type ActionType = 'open' | 'share' | 'delete' | 'info' | 'create'| 'duplicate' | 'export' | 'label' | 'rename' | 'print' | 'info' | 'publish' | 'history' | undefined; +export type ActionType = 'open' | 'share' | 'import' | 'delete' | 'info' | 'create' | 'duplicate' | 'export' | 'label' | 'rename' | 'print' | 'info' | 'publish' | 'history' | undefined; interface ActionProps { onClose: (action: ActionType) => void; anchor: undefined | HTMLElement; + role: 'owner' | 'editor' | 'viewer' } const ActionChooser = (props: ActionProps) => { - const { anchor, onClose } = props; + const { anchor, onClose, role } = props; const handleOnClose = (action: ActionType): ((event: React.MouseEvent) => void) => { return (event): void => { @@ -38,12 +39,13 @@ const ActionChooser = (props: ActionProps) => { onClose={handleOnClose(undefined)} elevation={1} > - + + @@ -53,12 +55,14 @@ const ActionChooser = (props: ActionProps) => { - - - - - - + {role == 'owner' && + + + + + + + } @@ -89,19 +93,23 @@ const ActionChooser = (props: ActionProps) => { - - - - - - + {role != 'viewer' && + + + + + + + } - - - - - - + {role != 'viewer' && + + + + + + + } @@ -111,12 +119,14 @@ const ActionChooser = (props: ActionProps) => { - - - - - - + {role != 'viewer' && + + + + + + + } ); } diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/base-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/base-dialog/index.tsx index 8643b0d7..b3eee02a 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/base-dialog/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/base-dialog/index.tsx @@ -58,7 +58,7 @@ const BaseDialog = (props: DialogProps) => { () } - {onSubmit ? ( + {onSubmit && ) : null + } diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/import-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/import-dialog/index.tsx new file mode 100644 index 00000000..1c1b7a4c --- /dev/null +++ b/packages/webapp/src/components/maps-page/action-dispatcher/import-dialog/index.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; +import { useMutation } from 'react-query'; +import { useSelector } from 'react-redux'; +import { Button, FormControl } from '@material-ui/core'; + + +import Client, { BasicMapInfo, ErrorInfo } from '../../../../client'; +import { activeInstance } from '../../../../redux/clientSlice'; +import Input from '../../../form/input'; +import BaseDialog from '../base-dialog'; + +export type ImportModel = { + title: string; + description?: string; +} + +export type CreateProps = { + open: boolean, + onClose: () => void +} + +const defaultModel: ImportModel = { title: '', description: '' }; +const ImportDialog = (props: CreateProps) => { + const client: Client = useSelector(activeInstance); + const [model, setModel] = React.useState(defaultModel); + const [error, setError] = React.useState(); + const intl = useIntl(); + + const mutation = useMutation((model: ImportModel) => { + return client.createMap(model); + }, + { + onSuccess: (mapId: number) => { + window.location.href = `/c/maps/${mapId}/edit`; + }, + onError: (error) => { + setError(error); + } + } + ); + + const handleOnClose = (): void => { + props.onClose(); + setModel(defaultModel); + setError(undefined); + }; + + const handleOnSubmit = (event: React.FormEvent): void => { + event.preventDefault(); + mutation.mutate(model); + }; + + const handleOnChange = (event: React.ChangeEvent): void => { + event.preventDefault(); + + const name = event.target.name; + const value = event.target.value; + setModel({ ...model, [name as keyof BasicMapInfo]: value }); + } + + return ( +
+ + + + + + + + + + + +
+ ); +} + +export default ImportDialog; \ 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 f47b4878..2a982bbc 100644 --- a/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx +++ b/packages/webapp/src/components/maps-page/action-dispatcher/index.tsx @@ -11,6 +11,8 @@ import DuplicateDialog from './duplicate-dialog'; import { useHistory } from 'react-router-dom'; import CreateDialog from './create-dialog'; import HistoryDialog from './history-dialog'; +import ImportDialog from './import-dialog'; +import PublishDialog from './publish-dialog'; export type BasicMapInfo = { name: string; @@ -44,11 +46,14 @@ const ActionDispatcher = (props: ActionDialogProps) => { return ( - {action === 'create' ? : null} - {action === 'delete' ? : null} - {action === 'rename' ? : null} - {action === 'duplicate' ? : null} - {action === 'history' ? : null} + {action === 'create' && } + {action === 'delete' &&} + {action === 'rename' && } + {action === 'duplicate' && } + {action === 'history' && } + {action === 'import' && } + {action === 'publish' && } + ); } diff --git a/packages/webapp/src/components/maps-page/action-dispatcher/publish-dialog/index.tsx b/packages/webapp/src/components/maps-page/action-dispatcher/publish-dialog/index.tsx new file mode 100644 index 00000000..1149879d --- /dev/null +++ b/packages/webapp/src/components/maps-page/action-dispatcher/publish-dialog/index.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { useMutation, useQueryClient } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppBar, Checkbox, FormControl, FormControlLabel, Tab, Typography } from '@material-ui/core'; + + +import Client, { ErrorInfo } from '../../../../client'; +import { activeInstance } from '../../../../redux/clientSlice'; +import BaseDialog from '../base-dialog'; +import { TabContext, TabList, TabPanel } from '@material-ui/lab'; +import { fetchMapById, handleOnMutationSuccess } from '..'; + +export type PublishProps = { + mapId: number, + onClose: () => void +} + +const PublishDialog = (props: PublishProps) => { + const { mapId, onClose } = props; + const { map } = fetchMapById(mapId); + + const client: Client = useSelector(activeInstance); + const [model, setModel] = React.useState(map ? map.isPublic : false); + const [error, setError] = React.useState(); + const [value, setValue] = React.useState('1'); + const queryClient = useQueryClient(); + const intl = useIntl(); + + const mutation = useMutation((model: boolean) => { + return client.updateMapToPublic(mapId, model); + }, + { + onSuccess: () => { + setModel(model); + handleOnMutationSuccess(onClose, queryClient); + }, + onError: (error) => { + setError(error); + } + } + ); + + const handleOnClose = (): void => { + props.onClose(); + setError(undefined); + }; + + const handleOnSubmit = (event: React.FormEvent): void => { + event.preventDefault(); + mutation.mutate(model); + }; + + const handleOnChange = (event: React.ChangeEvent, checked: boolean): void => { + event.preventDefault(); + setModel(checked); + } + + const handleTabChange = (event, newValue) => { + setValue(newValue); + }; + + return ( +
+ + + + + } + label={intl.formatMessage({ id: 'publish.checkbox', defaultMessage: 'Enable public sharing' })} + /> + + +
+ + + + + + + + + + + +

+ +

+
+ + + + +

+ +

+
+
+
+
+
+ ); +} + + + +export default PublishDialog; \ No newline at end of file diff --git a/packages/webapp/src/components/maps-page/index.tsx b/packages/webapp/src/components/maps-page/index.tsx index 53e842c1..5633d20c 100644 --- a/packages/webapp/src/components/maps-page/index.tsx +++ b/packages/webapp/src/components/maps-page/index.tsx @@ -105,7 +105,7 @@ const MapsPage = () => { return (
- + { - @@ -228,12 +235,13 @@ const StyleListItem = (props: ListItemProps) => { {icon} - {filter.type == 'label' ? - ( + {filter.type == 'label' && + handleOnDelete(e, filter)}> - ) : null} + + } ); } @@ -254,7 +262,7 @@ const HandleClientStatus = () => { fullWidth={true}> - + 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 ef5ac856..f5327e48 100644 --- a/packages/webapp/src/components/maps-page/maps-list/index.tsx +++ b/packages/webapp/src/components/maps-page/maps-list/index.tsx @@ -125,11 +125,13 @@ function EnhancedTableHead(props: EnhancedTableProps) { direction={orderBy === headCell.id ? order : 'asc'} onClick={createSortHandler(headCell.id)}> {headCell.label} - {orderBy === headCell.id ? ( + + {orderBy === headCell.id && ( {order === 'desc' ? 'sorted descending' : 'sorted ascending'} - ) : null} + )} + ) })} @@ -321,7 +323,7 @@ export const MapsList = (props: MapsListProps) => {
- {selected.length > 0 ? ( + {selected.length > 0 && - ) : null} + } - {selected.length > 0 ? ( + {selected.length > 0 && - ) : null} + }
@@ -465,7 +467,7 @@ export const MapsList = (props: MapsListProps) => { - + );