Integrate editor as part of webapp.
@ -3,7 +3,7 @@ services:
|
||||
e2e:
|
||||
image: cypress/included:8.4.1
|
||||
container_name: wisemapping-integration-tests
|
||||
entrypoint: '/bin/sh -c "yarn install && yarn bootstrap && yarn test:integration"'
|
||||
entrypoint: '/bin/sh -c "yarn bootstrap && yarn test:integration"'
|
||||
working_dir: /e2e
|
||||
environment:
|
||||
- CYPRESS_imageSnaphots=true
|
||||
|
@ -3,7 +3,7 @@ services:
|
||||
e2e:
|
||||
image: cypress/included:8.4.1
|
||||
container_name: wisemapping-integration-tests
|
||||
entrypoint: '/bin/sh -c "yarn install && yarn bootstrap && yarn test:integration"'
|
||||
entrypoint: '/bin/sh -c "yarn bootstrap && yarn test:integration"'
|
||||
working_dir: /e2e
|
||||
environment:
|
||||
- CYPRESS_imageSnaphots=true
|
||||
|
6
packages/editor/.babelrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
]
|
||||
}
|
4
packages/editor/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
cypress/downloads
|
||||
cypress/snapshots/*/__diff_output__
|
6
packages/editor/cypress.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"video": false,
|
||||
"videoUploadOnPasses": false,
|
||||
"baseUrl": "http://localhost:8081"
|
||||
}
|
||||
|
22
packages/editor/cypress/integration/playground.test.js
Normal file
@ -0,0 +1,22 @@
|
||||
context('Playground', () => {
|
||||
it('viewmode page should match its snapshot', () => {
|
||||
['welcome', 'sample1', 'sample2', 'sample3', 'sample4', 'sample5', 'sample6', 'complex', 'img-support', 'icon-sample'].forEach((mapId) => {
|
||||
cy.visit(`/viewmode.html?id=${mapId}`);
|
||||
cy.get('#mindplot.ready').should('exist');
|
||||
cy.matchImageSnapshot(`viewmode-${mapId}`);
|
||||
});
|
||||
});
|
||||
it('the playground container.html page should match its snapshot', () => {
|
||||
cy.visit('/container.html');
|
||||
cy.getIframeBody()
|
||||
.find('#mindplot.ready')
|
||||
.should('exist');
|
||||
cy.matchImageSnapshot('container');
|
||||
});
|
||||
it('the playground editor.html page should match its snapshot', () => {
|
||||
cy.visit('/editor.html');
|
||||
cy.get('#mindplot.ready').should('exist');
|
||||
// TODO: why is the editor appearing twice in the snapshot?
|
||||
cy.matchImageSnapshot('editor');
|
||||
});
|
||||
});
|
24
packages/editor/cypress/plugins/index.js
Normal file
@ -0,0 +1,24 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
const { addMatchImageSnapshotPlugin } = require('cypress-image-snapshot/plugin');
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
addMatchImageSnapshotPlugin(on, config);
|
||||
};
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 206 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 118 KiB |
After Width: | Height: | Size: 185 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 114 KiB |
24
packages/editor/cypress/support/commands.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command';
|
||||
|
||||
// make matchImageSnapshot() call the real implementation only if CYPRESS_imageSnaphots is set
|
||||
// otherwise it calls a noop
|
||||
if (Cypress.env('imageSnaphots')) {
|
||||
addMatchImageSnapshotCommand({
|
||||
failureThreshold: 0.001,
|
||||
failureThresholdType: 'percent',
|
||||
});
|
||||
} else {
|
||||
Cypress.Commands.add(
|
||||
'matchImageSnapshot',
|
||||
{
|
||||
prevSubject: ['optional', 'element', 'window', 'document'],
|
||||
},
|
||||
() => Promise.resolve(),
|
||||
);
|
||||
}
|
||||
|
||||
// https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/
|
||||
Cypress.Commands.add('getIframeBody', () => cy
|
||||
.get('iframe')
|
||||
.its('0.contentDocument.body').should('not.be.empty')
|
||||
.then(cy.wrap));
|
20
packages/editor/cypress/support/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands';
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 382 B |
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 173 B |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 189 B After Width: | Height: | Size: 189 B |
Before Width: | Height: | Size: 440 B After Width: | Height: | Size: 440 B |
Before Width: | Height: | Size: 258 B After Width: | Height: | Size: 258 B |
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 254 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 386 B |
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
Before Width: | Height: | Size: 205 B After Width: | Height: | Size: 205 B |
Before Width: | Height: | Size: 277 B After Width: | Height: | Size: 277 B |
Before Width: | Height: | Size: 343 B After Width: | Height: | Size: 343 B |
Before Width: | Height: | Size: 381 B After Width: | Height: | Size: 381 B |
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 391 B |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 155 B After Width: | Height: | Size: 155 B |
Before Width: | Height: | Size: 302 B After Width: | Height: | Size: 302 B |
1
packages/editor/images/public.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>
|
After Width: | Height: | Size: 457 B |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 300 B After Width: | Height: | Size: 300 B |
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
Before Width: | Height: | Size: 398 B After Width: | Height: | Size: 398 B |
Before Width: | Height: | Size: 758 B After Width: | Height: | Size: 758 B |
Before Width: | Height: | Size: 239 B After Width: | Height: | Size: 239 B |
Before Width: | Height: | Size: 510 B After Width: | Height: | Size: 510 B |
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 366 B |
Before Width: | Height: | Size: 280 B After Width: | Height: | Size: 280 B |
Before Width: | Height: | Size: 280 B After Width: | Height: | Size: 280 B |
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 341 B |
Before Width: | Height: | Size: 236 B After Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
@ -1,13 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Wisemapping</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- React app root element -->
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
@ -1,30 +1,53 @@
|
||||
{
|
||||
"name": "@wisemapping/editor",
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.tsx",
|
||||
"scripts": {},
|
||||
"version": "0.1.0",
|
||||
"main": "dist/editor.bundle.js",
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.prod.js",
|
||||
"playground": "webpack serve --config webpack.playground.js",
|
||||
"cy:run": "cypress run",
|
||||
"test:integration": "start-server-and-test 'yarn playground' http-get://localhost:8081 'yarn cy:run'",
|
||||
"test": "yarn test:integration"
|
||||
},
|
||||
"repository": "http://www.wisemapping.com",
|
||||
"author": "Paulo Veiga <pveiga@gmail.com>, Ezequiel Bergamaschi <ezequielbergamaschi@gmail.com>",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"@babel/preset-react": "^7.16.7",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.8.1",
|
||||
"@typescript-eslint/parser": "^4.8.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"compression-webpack-plugin": "^9.2.0",
|
||||
"copy-webpack-plugin": "^10.2.1",
|
||||
"cypress": "^8.4.1",
|
||||
"cypress-image-snapshot": "^4.0.1",
|
||||
"eslint": "^7.14.0",
|
||||
"eslint-config-prettier": "^8.0.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"prettier": "^2.2.1",
|
||||
"react": "^17.0.0",
|
||||
"ts-loader": "^8.0.11",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.1.2"
|
||||
"typescript": "^4.1.2",
|
||||
"webpack": "^5.67.0",
|
||||
"webpack-dev-server": "^4.7.3",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"@wisemapping/mindplot": "^0.4.15",
|
||||
"styled-components": "^5.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-intl": "^5.24.3"
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import React from 'react';
|
||||
import { StyledCanvas } from './styled';
|
||||
|
||||
const Canvas = (): React.ReactElement => <StyledCanvas>canvas</StyledCanvas>;
|
||||
|
||||
export default Canvas;
|
@ -1,8 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const StyledCanvas = styled.div`
|
||||
height: 100%
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
|
||||
`;
|
@ -1,6 +1,54 @@
|
||||
import React from 'react';
|
||||
import { StyledFooter } from './styled';
|
||||
import { StyledLogo } from './styled';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
const Footer = (): React.ReactElement => <StyledFooter>footer</StyledFooter>;
|
||||
import KeyboardSvg from '../../../images/keyboard.svg';
|
||||
import AddSvg from '../../../images/add.svg';
|
||||
import MinusSvg from '../../../images/minus.svg';
|
||||
import CenterFocusSvg from '../../../images/center_focus.svg';
|
||||
|
||||
export type FooterPropsType = {
|
||||
showTryPanel?: boolean;
|
||||
};
|
||||
|
||||
const Footer = ({ showTryPanel }: FooterPropsType): React.ReactElement => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id="floating-panel">
|
||||
<div id="keyboardShortcuts" className="buttonExtOn">
|
||||
<img src={KeyboardSvg} />
|
||||
</div>
|
||||
<div id="zoom-button">
|
||||
<button id="zoom-plus">
|
||||
<img src={AddSvg} />
|
||||
</button>
|
||||
<button id="zoom-minus">
|
||||
<img src={MinusSvg} />
|
||||
</button>
|
||||
</div>
|
||||
<div id="position">
|
||||
<button id="position-button">
|
||||
<img src={CenterFocusSvg} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<StyledLogo id="bottom-logo"></StyledLogo>
|
||||
<div id="headerNotifier"></div>
|
||||
{showTryPanel && (
|
||||
<div id="tryInfoPanel">
|
||||
<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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
|
@ -1,8 +1,18 @@
|
||||
import styled from 'styled-components';
|
||||
import { times } from '../../size';
|
||||
import LogoTextBlackSvg from '../../../images/logo-text-black.svg';
|
||||
|
||||
export const StyledFooter = styled.div`
|
||||
height: ${times(10)};
|
||||
width: 100%;
|
||||
border: 1px solid black;
|
||||
`;
|
||||
|
||||
export const StyledLogo = styled.div`
|
||||
position: fixed;
|
||||
left: 20px;
|
||||
bottom: 10px;
|
||||
background: url(${LogoTextBlackSvg}) no-repeat;
|
||||
width: 90px;
|
||||
height: 40px;
|
||||
`
|
@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import Footer from '../footer';
|
||||
import TopBar from '../top-bar';
|
||||
import Canvas from '../canvas';
|
||||
import { StyledFrame } from './styled';
|
||||
|
||||
const Frame = (): React.ReactElement => (
|
||||
<StyledFrame>
|
||||
<TopBar />
|
||||
<Canvas />
|
||||
<Footer />
|
||||
</StyledFrame>
|
||||
);
|
||||
|
||||
export default Frame;
|
@ -1,8 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const StyledFrame = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
143
packages/editor/src/components/toolbar/index.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import BackIconSvg from '../../../images/back-icon.svg';
|
||||
import SaveSvg from '../../../images/save.svg';
|
||||
import DiscardSvg from '../../../images/discard.svg';
|
||||
import UndoSvg from '../../../images/undo.svg';
|
||||
import RedoSvg from '../../../images/redo.svg';
|
||||
import TopicAddSvg from '../../../images/topic-add.svg';
|
||||
import TopicDeleteSvg from '../../../images/topic-delete.svg';
|
||||
import TopicBorderSvg from '../../../images/topic-border.svg';
|
||||
import TopicColorSvg from '../../../images/topic-color.svg';
|
||||
import TopicShapeSvg from '../../../images/topic-shape.svg';
|
||||
import FontTypeSvg from '../../../images/font-type.svg';
|
||||
import FontSizeSvg from '../../../images/font-size.svg';
|
||||
import FontBoldSvg from '../../../images/font-bold.svg';
|
||||
import FontItalicSvg from '../../../images/font-italic.svg';
|
||||
import FontColorSvg from '../../../images/font-color.svg';
|
||||
import TopicIconSvg from '../../../images/topic-icon.svg';
|
||||
import TopicNoteSvg from '../../../images/topic-note.svg';
|
||||
import TopicLinkSvg from '../../../images/topic-link.svg';
|
||||
import TopicRelationSvg from '../../../images/topic-relation.svg';
|
||||
import ExportSvg from '../../../images/export.svg';
|
||||
import PublicSvg from '../../../images/public.svg';
|
||||
import HistorySvg from '../../../images/history.svg';
|
||||
import PrintSvg from '../../../images/print.svg';
|
||||
import AccountSvg from '../../../images/account.svg';
|
||||
|
||||
export type ToolbarActionType = 'export' | 'publish' | 'history' | 'print' | 'share';
|
||||
|
||||
export type ToolbarPropsType = {
|
||||
memoryPersistence: boolean;
|
||||
readOnlyMode: boolean;
|
||||
onAction: (action: ToolbarActionType) => void;
|
||||
};
|
||||
|
||||
export default function Toolbar({
|
||||
memoryPersistence,
|
||||
readOnlyMode,
|
||||
onAction,
|
||||
}: ToolbarPropsType): React.ReactElement {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<div id="toolbar">
|
||||
<div id="backToList">
|
||||
<img src={BackIconSvg} />
|
||||
</div>
|
||||
{!memoryPersistence && (
|
||||
<div id="persist" className="buttonContainer">
|
||||
<div id="save" className="buttonOn">
|
||||
<img src={SaveSvg} />
|
||||
</div>
|
||||
<div id="discard" className="buttonOn">
|
||||
<img src={DiscardSvg}/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!readOnlyMode && (
|
||||
<>
|
||||
<div id="edit" className="buttonContainer">
|
||||
<div id="undoEdition" className="buttonOn">
|
||||
<img src={UndoSvg} />
|
||||
</div>
|
||||
<div id="redoEdition" className="buttonOn">
|
||||
<img src={RedoSvg} />
|
||||
</div>
|
||||
</div>
|
||||
<div id="nodeStyle" className="buttonContainer">
|
||||
<div id="addTopic" className="buttonOn">
|
||||
<img src={TopicAddSvg} />
|
||||
</div>
|
||||
<div id="deleteTopic" className="buttonOn">
|
||||
<img src={TopicDeleteSvg} />
|
||||
</div>
|
||||
<div id="topicBorder" className="buttonExtOn">
|
||||
<img src={TopicBorderSvg} />
|
||||
</div>
|
||||
<div id="topicColor" className="buttonExtOn">
|
||||
<img src={TopicColorSvg} />
|
||||
</div>
|
||||
<div id="topicShape" className="buttonExtOn">
|
||||
<img src={TopicShapeSvg} />
|
||||
</div>
|
||||
</div>
|
||||
<div id="font" className="buttonContainer">
|
||||
<div id="fontFamily" className="buttonOn">
|
||||
<img src={FontTypeSvg} />
|
||||
</div>
|
||||
<div id="fontSize" className="buttonExtOn">
|
||||
<img src={FontSizeSvg} />
|
||||
</div>
|
||||
<div id="fontBold" className="buttonOn">
|
||||
<img src={FontBoldSvg} />
|
||||
</div>
|
||||
<div id="fontItalic" className="buttonOn">
|
||||
<img src={FontItalicSvg} />
|
||||
</div>
|
||||
<div id="fontColor" className="buttonExtOn">
|
||||
<img src={FontColorSvg} />
|
||||
</div>
|
||||
</div>
|
||||
<div id="nodeContent" className="buttonContainer">
|
||||
<div id="topicIcon" className="buttonExtOn">
|
||||
<img src={TopicIconSvg} />
|
||||
</div>
|
||||
<div id="topicNote" className="buttonOn">
|
||||
<img src={TopicNoteSvg} />
|
||||
</div>
|
||||
<div id="topicLink" className="buttonOn">
|
||||
<img src={TopicLinkSvg} />
|
||||
</div>
|
||||
<div id="topicRelation" className="buttonOn">
|
||||
<img src={TopicRelationSvg} />
|
||||
</div>
|
||||
</div>
|
||||
<div id="separator" className="buttonContainer"></div>
|
||||
</>
|
||||
)}
|
||||
{!memoryPersistence && (
|
||||
<div id="toolbarRight">
|
||||
<div id="export" className="buttonOn" onClick={() => onAction('export')}>
|
||||
<img src={ExportSvg} />
|
||||
</div>
|
||||
<div id="publishIt" className="buttonOn" onClick={() => onAction('publish')}>
|
||||
<img src={PublicSvg} />
|
||||
</div>
|
||||
<div id="history" className="buttonOn" onClick={() => onAction('history')}>
|
||||
<img src={HistorySvg} />
|
||||
</div>
|
||||
<div id="print" className="buttonOn" onClick={() => onAction('print')}>
|
||||
<img src={PrintSvg} />
|
||||
</div>
|
||||
<div id="account">
|
||||
<img src={AccountSvg} />
|
||||
</div>
|
||||
<div id="share" className="actionButton" onClick={() => onAction('share')}>
|
||||
{ intl.formatMessage({ id: 'action.share', defaultMessage: 'Share' }) }
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import React from 'react';
|
||||
import { StyledTopBar } from './styled';
|
||||
|
||||
const TopBar = (): React.ReactElement => <StyledTopBar>top bar</StyledTopBar>;
|
||||
|
||||
export default TopBar;
|
@ -1,8 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
import { times } from '../../size';
|
||||
|
||||
export const StyledTopBar = styled.div`
|
||||
height: ${times(10)};
|
||||
width: 100%;
|
||||
border: 1px solid black;
|
||||
`;
|
30
packages/editor/src/custom.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
declare module "*.svg" {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module "@wisemapping/mindplot" {
|
||||
const mindplot: {
|
||||
Mindmap: any,
|
||||
PersistenceManager: any,
|
||||
Designer: any,
|
||||
LocalStorageManager: any,
|
||||
Menu: any,
|
||||
DesignerBuilder: any,
|
||||
RESTPersistenceManager: any,
|
||||
DesignerOptionsBuilder: any,
|
||||
buildDesigner: any,
|
||||
$notify: any
|
||||
};
|
||||
export var Mindmap: any;
|
||||
export var PersistenceManager: any;
|
||||
export var Designer: any;
|
||||
export var LocalStorageManager: any;
|
||||
export var Menu: any;
|
||||
export var DesignerBuilder: any;
|
||||
export var RESTPersistenceManager: any;
|
||||
export var DesignerOptionsBuilder: any;
|
||||
export var buildDesigner: any;
|
||||
export var $notify: any;
|
||||
export default mindplot;
|
||||
}
|
7
packages/editor/src/i18n.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const Locales = {
|
||||
EN: {},
|
||||
ES: {},
|
||||
DE: {},
|
||||
FR: {}
|
||||
};
|
||||
|
@ -1,3 +1,104 @@
|
||||
import Editor from './components/frame';
|
||||
import React from 'react';
|
||||
import Toolbar, { ToolbarActionType } from './components/toolbar';
|
||||
import Footer from './components/footer';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import * as mindplot from '@wisemapping/mindplot';
|
||||
declare global {
|
||||
var memoryPersistence: boolean;
|
||||
var readOnly: boolean;
|
||||
var lockTimestamp: string;
|
||||
var lockSession: string;
|
||||
var historyId: string;
|
||||
var isAuth: boolean;
|
||||
var mapId: number;
|
||||
var userOptions: { zoom: string | number } | null;
|
||||
var locale: string;
|
||||
var mindmapLocked: boolean;
|
||||
var mindmapLockedMsg: string;
|
||||
}
|
||||
|
||||
export default Editor;
|
||||
export type EditorPropsType = {
|
||||
initCallback?: (m: typeof mindplot) => () => void;
|
||||
mapId: number;
|
||||
memoryPersistence: boolean;
|
||||
readOnlyMode: boolean;
|
||||
locale?: string;
|
||||
onAction: (action: ToolbarActionType) => void;
|
||||
};
|
||||
|
||||
const initMindplot = ({
|
||||
PersistenceManager,
|
||||
RESTPersistenceManager,
|
||||
LocalStorageManager,
|
||||
DesignerOptionsBuilder,
|
||||
buildDesigner,
|
||||
$notify,
|
||||
}: typeof mindplot) => () => {
|
||||
let persistence: typeof PersistenceManager;
|
||||
if (!global.memoryPersistence && !global.readOnly) {
|
||||
persistence = new RESTPersistenceManager({
|
||||
documentUrl: '/c/restful/maps/{id}/document',
|
||||
revertUrl: '/c/restful/maps/{id}/history/latest',
|
||||
lockUrl: '/c/restful/maps/{id}/lock',
|
||||
timestamp: global.lockTimestamp,
|
||||
session: global.lockSession,
|
||||
});
|
||||
} else {
|
||||
persistence = new LocalStorageManager(
|
||||
`/c/restful/maps/{id}/${global.historyId ? `${global.historyId}/` : ''}document/xml${
|
||||
!global.isAuth ? '-pub' : ''
|
||||
}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(window.location.search.substring(1));
|
||||
|
||||
const zoomParam = Number.parseFloat(params.get('zoom'));
|
||||
const options = DesignerOptionsBuilder.buildOptions({
|
||||
persistenceManager: persistence,
|
||||
readOnly: Boolean(global.readOnly || false),
|
||||
mapId: global.mapId,
|
||||
container: 'mindplot',
|
||||
zoom: zoomParam || global.userOptions ? global.userOptions.zoom : 1,
|
||||
locale: global.locale,
|
||||
});
|
||||
|
||||
// Build designer ...
|
||||
const designer = buildDesigner(options);
|
||||
|
||||
// Load map from XML file persisted on disk...
|
||||
const instance = PersistenceManager.getInstance();
|
||||
const mindmap = instance.load(global.mapId);
|
||||
designer.loadMap(mindmap);
|
||||
|
||||
if (global.mindmapLocked) {
|
||||
$notify(global.mindmapLockedMsg);
|
||||
}
|
||||
};
|
||||
|
||||
export default function Editor({
|
||||
initCallback = initMindplot,
|
||||
mapId,
|
||||
memoryPersistence,
|
||||
readOnlyMode,
|
||||
locale = 'en',
|
||||
onAction,
|
||||
}: EditorPropsType): React.ReactElement {
|
||||
|
||||
React.useEffect(initCallback(mindplot), []);
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} defaultLocale="en" messages={{}}>
|
||||
<div id="header">
|
||||
<Toolbar
|
||||
memoryPersistence={memoryPersistence}
|
||||
readOnlyMode={readOnlyMode}
|
||||
onAction={onAction}
|
||||
/>
|
||||
</div>
|
||||
<div id="mindplot"></div>
|
||||
<Footer showTryPanel={memoryPersistence} />
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
33
packages/editor/test/playground/index.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@wisemapping/editor - Playground</title>
|
||||
<style>
|
||||
html * {
|
||||
font-family: Arial !important;
|
||||
}
|
||||
|
||||
tbody tr td:first-child {
|
||||
width: 20%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
<h1>@wisemapping/editor - Playground</h1>
|
||||
<p>You will find here a set of examples that shows how you can use integrate WiseMapping Editor</p>
|
||||
<div>
|
||||
<ul>
|
||||
<li><a href="/viewmode.html">View mode:</a> Simple integration to load and render mindaps in read
|
||||
only mode</li>
|
||||
<li><a href="/editor.html">Editor mode:</a> Example on how mindplot can be used for mindmap edition. Browser local storage is used for persistance.</li>
|
||||
<li><a href="/container.html">Embedded:</a> Example on how to embeded editor in a iframe.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -138,15 +138,6 @@ div.shareModalDialog {
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
div#bottom-logo {
|
||||
position: fixed;
|
||||
left: 20px;
|
||||
bottom: 10px;
|
||||
background: url(../images/logo-text-black.svg) no-repeat;
|
||||
width: 90px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
div#position {
|
||||
margin-top: 5px;
|
||||
}
|
17
packages/editor/test/playground/map-render/html/editor.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WiseMapping - Editor </title>
|
||||
<meta name="viewport" content="initial-scale=1">
|
||||
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
|
||||
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>WiseMapping - Editor </title>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
|
||||
<link rel="icon" href="images/favicon.ico" type="image/x-icon">
|
||||
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root" onselectstart="return false;"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -10,7 +10,7 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mindplot" onselectstart="return false;"></div>
|
||||
<div id="root" onselectstart="return false;"></div>
|
||||
<div id="footer">
|
||||
<div id="footer-logo"><img src="../images/logo-small.svg" /></div>
|
||||
<div id="footer-desc">
|
BIN
packages/editor/test/playground/map-render/images/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
packages/editor/test/playground/map-render/images/favicon.png
Normal file
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 14 KiB |
48
packages/editor/test/playground/map-render/js/editor.jsx
Normal file
@ -0,0 +1,48 @@
|
||||
import '../css/editor.css';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Editor from '../../../../src/index';
|
||||
|
||||
global.accountName = 'Test User';
|
||||
global.accountEmail = 'test@example.com';
|
||||
global.memoryPersistence = false;
|
||||
global.readOnly = false;
|
||||
global.mapId = 'welcome';
|
||||
global.locale = 'en';
|
||||
|
||||
|
||||
const initialization = ({
|
||||
LocalStorageManager,
|
||||
DesignerOptionsBuilder,
|
||||
buildDesigner,
|
||||
PersistenceManager
|
||||
}) => () => {
|
||||
const p = new LocalStorageManager('samples/{id}.wxml');
|
||||
const options = DesignerOptionsBuilder.buildOptions({
|
||||
persistenceManager: p
|
||||
});
|
||||
const designer = buildDesigner(options);
|
||||
|
||||
designer.addEvent('loadSuccess', () => {
|
||||
// Hack for automation testing ...
|
||||
document.getElementById('mindplot').classList.add('ready');
|
||||
});
|
||||
|
||||
// Load map from XML file persisted on disk...
|
||||
const mapId = 'welcome';
|
||||
const persistence = PersistenceManager.getInstance();
|
||||
const mindmap = persistence.load(mapId);
|
||||
designer.loadMap(mindmap);
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<Editor
|
||||
mapId={global.mapId}
|
||||
memoryPersistence={global.memoryPersistence}
|
||||
readOnlyMode={global.readOnly}
|
||||
locale={global.locale}
|
||||
onAction={(action) => console.log('action called:', action)}
|
||||
initCallback={initialization}
|
||||
/>,
|
||||
document.getElementById('root'),
|
||||
);
|
35
packages/editor/test/playground/map-render/js/embedded.jsx
Normal file
@ -0,0 +1,35 @@
|
||||
import '../css/embedded.css';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Editor from '../../../../src/index';
|
||||
|
||||
const initialization =
|
||||
({ LocalStorageManager, DesignerOptionsBuilder, buildDesigner, PersistenceManager }) =>
|
||||
() => {
|
||||
// Options has been defined in by a external file ?
|
||||
const p = new LocalStorageManager('samples/{id}.wxml');
|
||||
const options = DesignerOptionsBuilder.buildOptions({ persistenceManager: p });
|
||||
const designer = buildDesigner(options);
|
||||
|
||||
designer.addEvent('loadSuccess', () => {
|
||||
document.getElementById('mindplot').classList.add('ready');
|
||||
});
|
||||
|
||||
// Load map from XML file persisted on disk...
|
||||
const mapId = 'welcome';
|
||||
const persistence = PersistenceManager.getInstance();
|
||||
const mindmap = persistence.load(mapId);
|
||||
designer.loadMap(mindmap);
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<Editor
|
||||
mapId={global.mapId}
|
||||
memoryPersistence={global.memoryPersistence}
|
||||
readOnlyMode={global.readOnly}
|
||||
locale={global.locale}
|
||||
onAction={(action) => console.log('action called:', action)}
|
||||
initCallback={initialization}
|
||||
/>,
|
||||
document.getElementById('root')
|
||||
);
|
52
packages/editor/test/playground/map-render/js/viewmode.jsx
Normal file
@ -0,0 +1,52 @@
|
||||
import '../css/viewmode.css';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Editor from '../../../../src/index';
|
||||
|
||||
const initialization = ({
|
||||
LocalStorageManager,
|
||||
DesignerOptionsBuilder,
|
||||
buildDesigner,
|
||||
PersistenceManager
|
||||
}) => () => {
|
||||
const p = new LocalStorageManager('samples/{id}.wxml');
|
||||
const options = DesignerOptionsBuilder.buildOptions({ persistenceManager: p, readOnly: true, saveOnLoad: false });
|
||||
|
||||
// Obtain map id from query param
|
||||
const params = new URLSearchParams(window.location.search.substring(1));
|
||||
const mapId = params.get('id') || 'welcome';
|
||||
|
||||
const designer = buildDesigner(options);
|
||||
designer.addEvent('loadSuccess', () => {
|
||||
document.getElementById('mindplot').classList.add('ready');
|
||||
});
|
||||
|
||||
// Load map from XML file persisted on disk...
|
||||
const persistence = PersistenceManager.getInstance();
|
||||
const mindmap = persistence.load(mapId);
|
||||
designer.loadMap(mindmap);
|
||||
|
||||
// Code for selector of map.
|
||||
const mapSelectElem = document.getElementById('map-select');
|
||||
mapSelectElem.addEventListener('change', (e) => {
|
||||
const selectMap = e.target.value;
|
||||
window.location = `${window.location.pathname}?id=${selectMap}`;
|
||||
});
|
||||
|
||||
Array.from(mapSelectElem.options).forEach((option) => {
|
||||
option.selected = option.value === mapId;
|
||||
});
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<Editor
|
||||
mapId={global.mapId}
|
||||
memoryPersistence={global.memoryPersistence}
|
||||
readOnlyMode={global.readOnly}
|
||||
locale={global.locale}
|
||||
onAction={(action) => console.log('action called:', action)}
|
||||
initCallback={initialization}
|
||||
/>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
|