mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-11-11 01:43:23 +01:00
Integrate export and improve editor integration
This commit is contained in:
parent
f84ccfdcbd
commit
2faeeedd1b
@ -58,6 +58,8 @@ import { DesignerOptions } from './DesignerOptionsBuilder';
|
|||||||
import MainTopic from './MainTopic';
|
import MainTopic from './MainTopic';
|
||||||
import DragTopic from './DragTopic';
|
import DragTopic from './DragTopic';
|
||||||
|
|
||||||
|
export type ExportFormat = 'png' | 'svg' | 'jpg' | 'wxml';
|
||||||
|
|
||||||
class Designer extends Events {
|
class Designer extends Events {
|
||||||
private _mindmap: Mindmap;
|
private _mindmap: Mindmap;
|
||||||
|
|
||||||
@ -80,11 +82,11 @@ class Designer extends Events {
|
|||||||
private _cleanScreen: any;
|
private _cleanScreen: any;
|
||||||
|
|
||||||
constructor(options: DesignerOptions, divElement: JQuery) {
|
constructor(options: DesignerOptions, divElement: JQuery) {
|
||||||
|
super();
|
||||||
$assert(options, 'options must be defined');
|
$assert(options, 'options must be defined');
|
||||||
$assert(options.zoom, 'zoom must be defined');
|
$assert(options.zoom, 'zoom must be defined');
|
||||||
$assert(options.containerSize, 'size must be defined');
|
$assert(options.containerSize, 'size must be defined');
|
||||||
$assert(divElement, 'divElement must be defined');
|
$assert(divElement, 'divElement must be defined');
|
||||||
super();
|
|
||||||
|
|
||||||
// Set up i18n location ...
|
// Set up i18n location ...
|
||||||
Messages.init(options.locale);
|
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 workspace = this._workspace;
|
||||||
const svgElement = workspace.getSVGElement();
|
const svgElement = workspace.getSVGElement();
|
||||||
const size = workspace.getSize();
|
const size = workspace.getSize();
|
||||||
|
@ -57,8 +57,8 @@ class AccountSettingsPanel extends ListToolbarPanel {
|
|||||||
const content = $("<div class='toolbarPanel' id='accountSettingsPanel'></div>");
|
const content = $("<div class='toolbarPanel' id='accountSettingsPanel'></div>");
|
||||||
content[0].innerHTML = `
|
content[0].innerHTML = `
|
||||||
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
|
<p style='text-align:center;font-weight:bold;'>${global.accountName}</p>
|
||||||
<p>pveiga@wisemapping.com</p>
|
<p>${global.accountEmail}</p>
|
||||||
<div id="${global.accountMail}" model='logout' style='text-align:center'>
|
<div id="account-logout" model='logout' style='text-align:center'>
|
||||||
Logout
|
Logout
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -467,5 +467,11 @@
|
|||||||
},
|
},
|
||||||
"share.message": {
|
"share.message": {
|
||||||
"defaultMessage": "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."
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -467,5 +467,11 @@
|
|||||||
},
|
},
|
||||||
"share.message": {
|
"share.message": {
|
||||||
"defaultMessage": "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."
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -467,5 +467,11 @@
|
|||||||
},
|
},
|
||||||
"share.message": {
|
"share.message": {
|
||||||
"defaultMessage": "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."
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -467,5 +467,11 @@
|
|||||||
},
|
},
|
||||||
"share.message": {
|
"share.message": {
|
||||||
"defaultMessage": "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."
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -32,6 +32,12 @@ const queryClient = new QueryClient({
|
|||||||
const App = (): ReactElement => {
|
const App = (): ReactElement => {
|
||||||
const appi18n = new AppI18n();
|
const appi18n = new AppI18n();
|
||||||
const locale = appi18n.getBrowserLocale();
|
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 ? (
|
return locale.message ? (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
@ -66,7 +72,8 @@ const App = (): ReactElement => {
|
|||||||
<MapsPage />
|
<MapsPage />
|
||||||
</Route>
|
</Route>
|
||||||
<Route exact path="/c/maps/:id/edit">
|
<Route exact path="/c/maps/:id/edit">
|
||||||
<Editor />
|
<Editor memoryPersistence={memoryPersistence}
|
||||||
|
readOnlyMode={readOnlyMode} mapId={mapId} />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
|
@ -74,13 +74,30 @@ const ExportDialog = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (submit) {
|
if (submit) {
|
||||||
|
// TODO: Remove usage of global "designer"
|
||||||
|
const designer = global.designer;
|
||||||
// Depending on the type of export. It will require differt POST.
|
// Depending on the type of export. It will require differt POST.
|
||||||
if (
|
if (
|
||||||
exportFormat == 'pdf' ||
|
designer &&
|
||||||
exportFormat == 'svg' ||
|
designer.EXPORT_SUPPORTED_FORMATS.includes(exportFormat)
|
||||||
exportFormat == 'jpg' ||
|
|
||||||
exportFormat == 'png'
|
|
||||||
) {
|
) {
|
||||||
|
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();
|
formTransformtRef?.submit();
|
||||||
} else {
|
} else {
|
||||||
formExportRef?.submit();
|
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' })}
|
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' })}
|
submitButton={intl.formatMessage({ id: 'export.title', defaultMessage: 'Export' })}
|
||||||
>
|
>
|
||||||
<Alert severity="info">
|
{
|
||||||
<FormattedMessage
|
!enableImgExport &&
|
||||||
id="export.warning"
|
<Alert severity="info">
|
||||||
defaultMessage="Exporting to Image (SVG,PNG,JPEG,PDF) is only available in the editor toolbar."
|
<FormattedMessage
|
||||||
/>
|
id="export.warning"
|
||||||
</Alert>
|
defaultMessage="Exporting to Image (SVG,PNG,JPEG,PDF) is only available in the editor toolbar."
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
}
|
||||||
<FormControl component="fieldset">
|
<FormControl component="fieldset">
|
||||||
<RadioGroup name="export" value={exportGroup} onChange={handleOnGroupChange}>
|
<RadioGroup name="export" value={exportGroup} onChange={handleOnGroupChange}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@ -124,7 +143,7 @@ const ExportDialog = ({
|
|||||||
/>
|
/>
|
||||||
{exportGroup == 'image' && (
|
{exportGroup == 'image' && (
|
||||||
<Select
|
<Select
|
||||||
onSelect={handleOnExportFormatChange}
|
onChange={handleOnExportFormatChange}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={exportFormat}
|
value={exportFormat}
|
||||||
className={classes.label}
|
className={classes.label}
|
||||||
@ -132,7 +151,7 @@ const ExportDialog = ({
|
|||||||
<MenuItem value="svg" className={classes.menu}>
|
<MenuItem value="svg" className={classes.menu}>
|
||||||
Scalable Vector Graphics (SVG)
|
Scalable Vector Graphics (SVG)
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem value="pdf" className={classes.select}>
|
<MenuItem value="pdf" className={classes.menu}>
|
||||||
Portable Document Format (PDF)
|
Portable Document Format (PDF)
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem value="png" className={classes.menu}>
|
<MenuItem value="png" className={classes.menu}>
|
||||||
|
@ -22,9 +22,10 @@ type ActionDialogProps = {
|
|||||||
action?: ActionType;
|
action?: ActionType;
|
||||||
mapsId: number[];
|
mapsId: number[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
fromEditor: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ActionDispatcher = ({ mapsId, action, onClose }: ActionDialogProps): React.ReactElement => {
|
const ActionDispatcher = ({ mapsId, action, onClose, fromEditor }: ActionDialogProps): React.ReactElement => {
|
||||||
const handleOnClose = (): void => {
|
const handleOnClose = (): void => {
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
@ -57,12 +58,17 @@ const ActionDispatcher = ({ mapsId, action, onClose }: ActionDialogProps): React
|
|||||||
{action === 'info' && <InfoDialog onClose={handleOnClose} mapId={mapsId[0]} />}
|
{action === 'info' && <InfoDialog onClose={handleOnClose} mapId={mapsId[0]} />}
|
||||||
{action === 'create' && <CreateDialog onClose={handleOnClose} />}
|
{action === 'create' && <CreateDialog onClose={handleOnClose} />}
|
||||||
{action === 'export' && (
|
{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]} />}
|
{action === 'share' && <ShareDialog onClose={handleOnClose} mapId={mapsId[0]} />}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ActionDispatcher.defaultProps = {
|
||||||
|
fromEditor: false,
|
||||||
|
};
|
||||||
|
|
||||||
export const handleOnMutationSuccess = (onClose: () => void, queryClient: QueryClient): void => {
|
export const handleOnMutationSuccess = (onClose: () => void, queryClient: QueryClient): void => {
|
||||||
queryClient.invalidateQueries('maps');
|
queryClient.invalidateQueries('maps');
|
||||||
onClose();
|
onClose();
|
||||||
|
@ -2,14 +2,19 @@ import React from 'react';
|
|||||||
import Toolbar from './toolbar';
|
import Toolbar from './toolbar';
|
||||||
import ActionDispatcher from '../action-dispatcher';
|
import ActionDispatcher from '../action-dispatcher';
|
||||||
import { ActionType } from '../action-chooser';
|
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
|
// TODO: create the Editor component in the editor package, with its own build and include it instead
|
||||||
export default function Editor(): React.ReactElement {
|
export default function Editor({ mapId, memoryPersistence, readOnlyMode } : EditorPropsType): React.ReactElement {
|
||||||
const memoryPersistence = false;
|
const intl = useIntl();
|
||||||
const readOnlyMode = false;
|
|
||||||
const mapId = 1;
|
|
||||||
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
|
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
@ -42,20 +47,25 @@ export default function Editor(): React.ReactElement {
|
|||||||
<div id="headerNotifier"></div>
|
<div id="headerNotifier"></div>
|
||||||
{
|
{
|
||||||
memoryPersistence && <div id="tryInfoPanel">
|
memoryPersistence && <div id="tryInfoPanel">
|
||||||
<p>TRY_WELCOME</p>
|
<p>
|
||||||
<p>TRY_WELCOME_DESC</p>
|
{ intl.formatMessage({ id: 'editor.try-welcome' }) }
|
||||||
<a href="/c/registration"><div className="actionButton">
|
</p>
|
||||||
SIGN_UP</div>
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
activeDialog &&
|
activeDialog &&
|
||||||
<ActionDispatcher
|
<ActionDispatcher
|
||||||
action={activeDialog}
|
action={activeDialog}
|
||||||
onClose={() => setActiveDialog(null)}
|
onClose={() => setActiveDialog(null)}
|
||||||
mapsId={[mapId]}
|
mapsId={[mapId]}
|
||||||
/>
|
fromEditor
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
import { ActionType } from '../action-chooser';
|
import { ActionType } from '../action-chooser';
|
||||||
|
|
||||||
export type ToolbarPropsType = {
|
export type ToolbarPropsType = {
|
||||||
@ -12,6 +13,7 @@ export default function Toolbar({
|
|||||||
readOnlyMode,
|
readOnlyMode,
|
||||||
onAction,
|
onAction,
|
||||||
}: ToolbarPropsType): React.ReactElement {
|
}: ToolbarPropsType): React.ReactElement {
|
||||||
|
const intl = useIntl();
|
||||||
return (
|
return (
|
||||||
<div id="toolbar">
|
<div id="toolbar">
|
||||||
<div id="backToList">
|
<div id="backToList">
|
||||||
@ -22,6 +24,9 @@ export default function Toolbar({
|
|||||||
<div id="save" className="buttonOn">
|
<div id="save" className="buttonOn">
|
||||||
<img src="../../images/editor/save.svg" />
|
<img src="../../images/editor/save.svg" />
|
||||||
</div>
|
</div>
|
||||||
|
<div id="discard" className="buttonOn">
|
||||||
|
<img src="../../images/editor/discard.svg" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!readOnlyMode && (
|
{!readOnlyMode && (
|
||||||
@ -103,7 +108,7 @@ export default function Toolbar({
|
|||||||
<img src="../../images/editor/account.svg" />
|
<img src="../../images/editor/account.svg" />
|
||||||
</div>
|
</div>
|
||||||
<div id="share" className="actionButton" onClick={() => onAction('share')}>
|
<div id="share" className="actionButton" onClick={() => onAction('share')}>
|
||||||
SHARE
|
{ intl.formatMessage({ id: 'action.share', defaultMessage: 'Share' }) }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -197,6 +197,7 @@ const MapsPage = (): ReactElement => {
|
|||||||
action={activeDialog}
|
action={activeDialog}
|
||||||
onClose={() => setActiveDialog(undefined)}
|
onClose={() => setActiveDialog(undefined)}
|
||||||
mapsId={[]}
|
mapsId={[]}
|
||||||
|
fromEditor
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={classes.rightButtonGroup}>
|
<div className={classes.rightButtonGroup}>
|
||||||
|
Loading…
Reference in New Issue
Block a user