Integrate export and improve editor integration

This commit is contained in:
Matias Arriola 2022-01-19 11:21:35 -03:00
parent f84ccfdcbd
commit 2faeeedd1b
12 changed files with 110 additions and 34 deletions

View File

@ -58,6 +58,8 @@ import { DesignerOptions } from './DesignerOptionsBuilder';
import MainTopic from './MainTopic';
import DragTopic from './DragTopic';
export type ExportFormat = 'png' | 'svg' | 'jpg' | 'wxml';
class Designer extends Events {
private _mindmap: Mindmap;
@ -80,11 +82,11 @@ class Designer extends Events {
private _cleanScreen: any;
constructor(options: DesignerOptions, divElement: JQuery) {
super();
$assert(options, 'options must be defined');
$assert(options.zoom, 'zoom must be defined');
$assert(options.containerSize, 'size must be defined');
$assert(divElement, 'divElement must be defined');
super();
// Set up i18n location ...
Messages.init(options.locale);
@ -342,7 +344,9 @@ class Designer extends Events {
}
}
export(formatType: 'png' | 'svg' | 'jpg' | 'wxml'): Promise<string> {
EXPORT_SUPPORTED_FORMATS: ExportFormat[] = ['png', 'svg', 'jpg', 'wxml'];
export(formatType: ExportFormat): Promise<string> {
const workspace = this._workspace;
const svgElement = workspace.getSVGElement();
const size = workspace.getSize();

View File

@ -57,8 +57,8 @@ class AccountSettingsPanel extends ListToolbarPanel {
const content = $("<div class='toolbarPanel' id='accountSettingsPanel'></div>");
content[0].innerHTML = `
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
<p>pveiga@wisemapping.com</p>
<div id="${global.accountMail}" model='logout' style='text-align:center'>
<p>${global.accountEmail}</p>
<div id="account-logout" model='logout' style='text-align:center'>
Logout
</div>
`;

View File

@ -467,5 +467,11 @@
},
"share.message": {
"defaultMessage": "Message"
},
"editor.try-welcome": {
"defaultMessage": "This edition space showcases some of the mindmap editor capabilities !"
},
"editor.try-welcome-description": {
"defaultMessage": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free."
}
}

View File

@ -467,5 +467,11 @@
},
"share.message": {
"defaultMessage": "Message"
},
"editor.try-welcome": {
"defaultMessage": "This edition space showcases some of the mindmap editor capabilities !"
},
"editor.try-welcome-description": {
"defaultMessage": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free."
}
}

View File

@ -467,5 +467,11 @@
},
"share.message": {
"defaultMessage": "Message"
},
"editor.try-welcome": {
"defaultMessage": "This edition space showcases some of the mindmap editor capabilities !"
},
"editor.try-welcome-description": {
"defaultMessage": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free."
}
}

View File

@ -467,5 +467,11 @@
},
"share.message": {
"defaultMessage": "Message"
},
"editor.try-welcome": {
"defaultMessage": "This edition space showcases some of the mindmap editor capabilities !"
},
"editor.try-welcome-description": {
"defaultMessage": "Sign Up to start creating, sharing and publishing unlimited number of mindmaps for free."
}
}

View File

@ -32,6 +32,12 @@ const queryClient = new QueryClient({
const App = (): ReactElement => {
const appi18n = new AppI18n();
const locale = appi18n.getBrowserLocale();
// global variables set server-side
const memoryPersistence = global.memoryPersistence;
const readOnlyMode = global.readOnly;
const mapId = parseInt(global.mapId, 10);
return locale.message ? (
<Provider store={store}>
<QueryClientProvider client={queryClient}>
@ -66,7 +72,8 @@ const App = (): ReactElement => {
<MapsPage />
</Route>
<Route exact path="/c/maps/:id/edit">
<Editor />
<Editor memoryPersistence={memoryPersistence}
readOnlyMode={readOnlyMode} mapId={mapId} />
</Route>
</Switch>
</Router>

View File

@ -74,13 +74,30 @@ const ExportDialog = ({
useEffect(() => {
if (submit) {
// TODO: Remove usage of global "designer"
const designer = global.designer;
// Depending on the type of export. It will require differt POST.
if (
exportFormat == 'pdf' ||
exportFormat == 'svg' ||
exportFormat == 'jpg' ||
exportFormat == 'png'
designer &&
designer.EXPORT_SUPPORTED_FORMATS.includes(exportFormat)
) {
designer.export(exportFormat)
.then((url: string) => {
// Create hidden anchor to force download ...
const anchor: HTMLAnchorElement = document.createElement('a');
anchor.style.display = 'display: none';
anchor.download = `${mapId}.${exportFormat}`;
anchor.href = url;
document.body.appendChild(anchor);
// Trigger click ...
anchor.click();
// Clean up ...
URL.revokeObjectURL(url);
document.body.removeChild(anchor);
});
} else if (exportFormat === 'pdf') {
formTransformtRef?.submit();
} else {
formExportRef?.submit();
@ -99,13 +116,15 @@ const ExportDialog = ({
description={intl.formatMessage({ id: 'export.desc', defaultMessage: 'Export this map in the format that you want and start using it in your presentations or sharing by email' })}
submitButton={intl.formatMessage({ id: 'export.title', defaultMessage: 'Export' })}
>
<Alert severity="info">
<FormattedMessage
id="export.warning"
defaultMessage="Exporting to Image (SVG,PNG,JPEG,PDF) is only available in the editor toolbar."
/>
</Alert>
{
!enableImgExport &&
<Alert severity="info">
<FormattedMessage
id="export.warning"
defaultMessage="Exporting to Image (SVG,PNG,JPEG,PDF) is only available in the editor toolbar."
/>
</Alert>
}
<FormControl component="fieldset">
<RadioGroup name="export" value={exportGroup} onChange={handleOnGroupChange}>
<FormControl>
@ -124,7 +143,7 @@ const ExportDialog = ({
/>
{exportGroup == 'image' && (
<Select
onSelect={handleOnExportFormatChange}
onChange={handleOnExportFormatChange}
variant="outlined"
value={exportFormat}
className={classes.label}
@ -132,7 +151,7 @@ const ExportDialog = ({
<MenuItem value="svg" className={classes.menu}>
Scalable Vector Graphics (SVG)
</MenuItem>
<MenuItem value="pdf" className={classes.select}>
<MenuItem value="pdf" className={classes.menu}>
Portable Document Format (PDF)
</MenuItem>
<MenuItem value="png" className={classes.menu}>

View File

@ -22,9 +22,10 @@ type ActionDialogProps = {
action?: ActionType;
mapsId: number[];
onClose: () => void;
fromEditor: boolean;
};
const ActionDispatcher = ({ mapsId, action, onClose }: ActionDialogProps): React.ReactElement => {
const ActionDispatcher = ({ mapsId, action, onClose, fromEditor }: ActionDialogProps): React.ReactElement => {
const handleOnClose = (): void => {
onClose();
};
@ -57,12 +58,17 @@ const ActionDispatcher = ({ mapsId, action, onClose }: ActionDialogProps): React
{action === 'info' && <InfoDialog onClose={handleOnClose} mapId={mapsId[0]} />}
{action === 'create' && <CreateDialog onClose={handleOnClose} />}
{action === 'export' && (
<ExportDialog onClose={handleOnClose} mapId={mapsId[0]} enableImgExport={false} />
<ExportDialog onClose={handleOnClose} mapId={mapsId[0]} enableImgExport={fromEditor} />
)}
{action === 'share' && <ShareDialog onClose={handleOnClose} mapId={mapsId[0]} />}
</span>
);
};
ActionDispatcher.defaultProps = {
fromEditor: false,
};
export const handleOnMutationSuccess = (onClose: () => void, queryClient: QueryClient): void => {
queryClient.invalidateQueries('maps');
onClose();

View File

@ -2,14 +2,19 @@ import React from 'react';
import Toolbar from './toolbar';
import ActionDispatcher from '../action-dispatcher';
import { ActionType } from '../action-chooser';
import { useIntl } from 'react-intl';
// this component is a hack. In order to work, we need to load externally the "loader.js" script from mindplot
export type EditorPropsType = {
mapId: number;
memoryPersistence: boolean;
readOnlyMode: boolean;
};
// HACK. In order to work, we need to load externally the "loader.js" script from mindplot
// TODO: create the Editor component in the editor package, with its own build and include it instead
export default function Editor(): React.ReactElement {
const memoryPersistence = false;
const readOnlyMode = false;
const mapId = 1;
export default function Editor({ mapId, memoryPersistence, readOnlyMode } : EditorPropsType): React.ReactElement {
const intl = useIntl();
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
return <>
@ -42,20 +47,25 @@ export default function Editor(): React.ReactElement {
<div id="headerNotifier"></div>
{
memoryPersistence && <div id="tryInfoPanel">
<p>TRY_WELCOME</p>
<p>TRY_WELCOME_DESC</p>
<a href="/c/registration"><div className="actionButton">
SIGN_UP</div>
<p>
{ intl.formatMessage({ id: 'editor.try-welcome' }) }
</p>
<p>{ intl.formatMessage({ id: 'editor.try-welcome-description' }) }</p>
<a href="/c/registration">
<div className="actionButton">
{ intl.formatMessage({ id: 'login.signup', defaultMessage: 'Sign Up' }) }
</div>
</a>
</div>
}
{
activeDialog &&
<ActionDispatcher
action={activeDialog}
onClose={() => setActiveDialog(null)}
mapsId={[mapId]}
/>
action={activeDialog}
onClose={() => setActiveDialog(null)}
mapsId={[mapId]}
fromEditor
/>
}
</>
}

View File

@ -1,4 +1,5 @@
import React from 'react';
import { useIntl } from 'react-intl';
import { ActionType } from '../action-chooser';
export type ToolbarPropsType = {
@ -12,6 +13,7 @@ export default function Toolbar({
readOnlyMode,
onAction,
}: ToolbarPropsType): React.ReactElement {
const intl = useIntl();
return (
<div id="toolbar">
<div id="backToList">
@ -22,6 +24,9 @@ export default function Toolbar({
<div id="save" className="buttonOn">
<img src="../../images/editor/save.svg" />
</div>
<div id="discard" className="buttonOn">
<img src="../../images/editor/discard.svg" />
</div>
</div>
)}
{!readOnlyMode && (
@ -103,7 +108,7 @@ export default function Toolbar({
<img src="../../images/editor/account.svg" />
</div>
<div id="share" className="actionButton" onClick={() => onAction('share')}>
SHARE
{ intl.formatMessage({ id: 'action.share', defaultMessage: 'Share' }) }
</div>
</div>
)}

View File

@ -197,6 +197,7 @@ const MapsPage = (): ReactElement => {
action={activeDialog}
onClose={() => setActiveDialog(undefined)}
mapsId={[]}
fromEditor
/>
<div className={classes.rightButtonGroup}>