Merged in mindplot-webcomponent (pull request #57)

Mindplot webcomponent

* styles in js file

* editor now uses bootstrap widget manager

* fix console error: preventDefault on pasive events

* Mode as webcomponent parámeter. Rename of init method

* fix merge

* Mindplot webcomponent documentation

* fix

* remove comments

* delete comments

* Merged in Alejandro-Raiczyk/just-details-1661445571189 (pull request #56)

just details

Approved-by: Paulo Veiga
This commit is contained in:
Gonzalo Martinez 2022-08-26 01:35:59 +00:00 committed by Paulo Veiga
parent f6d04523b8
commit 2370faea62
38 changed files with 784 additions and 376 deletions

View File

@ -0,0 +1,90 @@
import {
WidgetManager,
Topic,
LinkModel,
LinkIcon,
NoteModel,
NoteIcon,
$msg,
} from '@wisemapping/mindplot';
import LinkIconTooltip from './LinkIconTooltip';
import LinkEditor from './LinkEditor';
import FloatingTip from './FloatingTip';
import NoteEditor from './NoteEditor';
import $ from 'jquery';
export default class BootstrapWidgetManager extends WidgetManager {
createTooltipForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) {
const htmlImage = linkIcon.getImage().peer;
const toolTip = new LinkIconTooltip(linkIcon);
linkIcon.addEvent('mouseleave', (event) => {
setTimeout(() => {
if (!$('#linkPopover:hover').length) {
toolTip.hide();
}
event.stopPropagation();
}, 100);
});
$(htmlImage._native).mouseenter(() => {
toolTip.show();
});
}
showEditorForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) {
const editorModel = {
getValue(): string {
return topic.getLinkValue();
},
setValue(value: string) {
topic.setLinkValue(value);
},
};
topic.closeEditors();
const editor = new LinkEditor(editorModel);
editor.show();
}
private _buildTooltipContentForNote(noteModel: NoteModel): JQuery {
if ($('body').find('#textPopoverNote').length === 1) {
const text = $('body').find('#textPopoverNote');
text.text(noteModel.getText());
return text;
}
const result = $('<div id="textPopoverNote"></div>').css({ padding: '5px' });
const text = $('<div></div>').text(noteModel.getText()).css({
'white-space': 'pre-wrap',
'word-wrap': 'break-word',
});
result.append(text);
return result;
}
createTooltipForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) {
const htmlImage = noteIcon.getImage().peer;
const me = this;
const toolTip = new FloatingTip($(htmlImage._native), {
title: $msg('NOTE'),
content() {
return me._buildTooltipContentForNote(noteModel);
},
html: true,
placement: 'bottom',
destroyOnExit: true,
});
}
showEditorForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) {
const editorModel = {
getValue(): string {
return topic.getNoteValue();
},
setValue(value: string) {
topic.setNoteValue(value);
},
};
topic.closeEditors();
const editor = new NoteEditor(editorModel);
editor.show();
}
}

View File

@ -16,7 +16,7 @@
* limitations under the License.
*/
import merge from 'lodash/merge';
import Events from '../Events';
import Events from '../menu/Events';
const defaultOptions = {
animation: true,

View File

@ -17,8 +17,8 @@
*/
import $ from 'jquery';
import { $assert } from '@wisemapping/core-js';
import { $msg } from '../Messages';
import BootstrapDialog from './bootstrap/BootstrapDialog';
import { $msg } from '@wisemapping/mindplot';
import BootstrapDialog from './BootstrapDialog';
interface LinkEditorModel {
getValue(): string;

View File

@ -17,9 +17,9 @@
*/
import { $assert } from '@wisemapping/core-js';
import $ from 'jquery';
import LinkIcon from '../LinkIcon';
import LinkModel from '../model/LinkModel';
import { $msg } from '../Messages';
import { LinkIcon } from '@wisemapping/mindplot';
import { LinkModel } from '@wisemapping/mindplot';
import { $msg } from '@wisemapping/mindplot';
import FloatingTip from './FloatingTip';
class LinkIconTooltip extends FloatingTip {
@ -56,7 +56,7 @@ class LinkIconTooltip extends FloatingTip {
target: '_blank',
});
link.append(linkText);
link.html(linkText);
result.append(link);
return result;
}

View File

@ -18,7 +18,7 @@
import { $assert } from '@wisemapping/core-js';
import $ from 'jquery';
import BootstrapDialog from '../../../../editor/src/classes/bootstrap/BootstrapDialog';
import { $msg } from '../Messages';
import { $msg } from '@wisemapping/mindplot';
interface NoteEditorModel {
getValue(): string;

View File

@ -17,7 +17,7 @@
*/
import $ from 'jquery';
import { $assert } from '@wisemapping/core-js';
import FloatingTip from '@wisemapping/mindplot/src/components/widget/FloatingTip';
import FloatingTip from '../bootstrap/FloatingTip';
class KeyboardShortcutTooltip extends FloatingTip {
constructor(buttonElem, text) {

View File

@ -17,7 +17,7 @@
*/
import { $assert } from '@wisemapping/core-js';
import ToolbarItem from './ToolbarItem';
import FloatingTip from '@wisemapping/mindplot/src/components/widget/FloatingTip';
import FloatingTip from '../bootstrap/FloatingTip';
class ToolbarPaneItem extends ToolbarItem {
constructor(buttonId, model, delayInit) {

View File

@ -22,21 +22,6 @@ body {
height: 100%;
}
div#mindplot {
position: relative;
top: 50px;
left: 0;
width: 100%;
height: 100%;
border: 0;
overflow: hidden;
opacity: 1;
background-color: #f2f2f2;
background-image: linear-gradient(#ebe9e7 1px, transparent 1px),
linear-gradient(to right, #ebe9e7 1px, #f2f2f2 1px);
background-size: 50px 50px;
}
.notesTip {
background-color: #dfcf3c;
padding: 5px 15px;

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import Toolbar, { ToolbarActionType } from './components/toolbar';
import Footer from './components/footer';
import { IntlProvider } from 'react-intl';
@ -10,10 +10,23 @@ import {
Designer,
DesignerKeyboard,
EditorRenderMode,
MindplotWebComponentInterface,
Mindmap,
MockPersistenceManager,
LocalStorageManager,
RESTPersistenceManager,
TextExporterFactory,
ImageExporterFactory,
Exporter,
Importer,
TextImporterFactory,
} from '@wisemapping/mindplot';
import './global-styled.css';
import I18nMsg from './classes/i18n-msg';
import Menu from './classes/menu/Menu';
import BootstrapWidgetManager from './classes/bootstrap/BootstrapWidgetManager';
require('../../../libraries/bootstrap/js/bootstrap.min');
declare global {
// used in mindplot
@ -21,6 +34,14 @@ declare global {
var accountEmail: string;
}
declare global {
namespace JSX {
interface IntrinsicElements {
['mindplot-component']: MindplotWebComponentInterface;
}
}
}
export type EditorOptions = {
mode: EditorRenderMode;
locale: string;
@ -31,6 +52,23 @@ export type EditorOptions = {
enableKeyboardEvents: boolean;
};
export {
PersistenceManager,
DesignerOptionsBuilder,
Designer,
DesignerKeyboard,
EditorRenderMode,
Mindmap,
MockPersistenceManager,
LocalStorageManager,
RESTPersistenceManager,
TextExporterFactory,
ImageExporterFactory,
Exporter,
Importer,
TextImporterFactory,
};
export type EditorProps = {
mapId: string;
options: EditorOptions;
@ -41,22 +79,21 @@ export type EditorProps = {
const Editor = ({ mapId, options, persistenceManager, onAction, onLoad }: EditorProps) => {
const [isMobile, setIsMobile] = useState(undefined);
const mindplotComponent: any = useRef();
useEffect(() => {
// Change page title ...
document.title = `${options.mapTitle} | WiseMapping `;
// Load mindmap ...
const designer = onLoadDesigner(mapId, options, persistenceManager);
// Has extended actions been customized ...
if (onLoad) {
onLoad(designer);
}
// Load mindmap ...
const instance = PersistenceManager.getInstance();
const mindmap = instance.load(mapId);
designer.loadMap(mindmap);
mindplotComponent.current.loadMap(mapId);
setIsMobile(checkMobile());
@ -89,17 +126,10 @@ const Editor = ({ mapId, options, persistenceManager, onAction, onLoad }: Editor
options: EditorOptions,
persistenceManager: PersistenceManager,
): Designer => {
const buildOptions = DesignerOptionsBuilder.buildOptions({
persistenceManager,
mode: options.mode,
mapId: mapId,
container: 'mindplot',
zoom: options.zoom,
locale: options.locale,
});
mindplotComponent.current.buildDesigner(persistenceManager, new BootstrapWidgetManager());
// Build designer ...
const result = buildDesigner(buildOptions);
const result = mindplotComponent.current && mindplotComponent.current.getDesigner();
// Register toolbar event ...
if (
@ -132,7 +162,11 @@ const Editor = ({ mapId, options, persistenceManager, onAction, onLoad }: Editor
<Toolbar editorMode={options.mode} onAction={onAction} />
)}
</div>
<div id="mindplot" style={mindplotStyle} className="wise-editor"></div>
<mindplot-component
ref={mindplotComponent}
id="mindmap-comp"
mode={options.mode}
></mindplot-component>
<div id="mindplot-tooltips" className="wise-editor"></div>
<Footer editorMode={options.mode} isMobile={isMobile} />
</IntlProvider>

View File

@ -22,7 +22,7 @@ import { LocalStorageManager, Designer } from '@wisemapping/mindplot';
const initialization = (designer: Designer) => {
designer.addEvent('loadSuccess', () => {
const elem = document.getElementById('mindplot');
const elem = document.getElementById('mindmap-comp');
if (elem) {
elem.classList.add('ready');
}

View File

@ -1,7 +1,78 @@
# WiseMapping Mindplot
WiseMapping Mindplot module is the core mind map rerendering of WiseMapping. This lighway library allows eithe edition and visualization of saves mindmaps.
WiseMapping Mindplot module is the core mind map rerendering of WiseMapping. This lighway library allows eithe edition and visualization of saved mindmaps.
## Usage
A WebComponent implementation for mindplot designer is available.
This component is registered as mindplot-component in customElements API. (see https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)
For use it you need to import minplot.js and put in your DOM a <mindplot-component id="mindplot-comp"/> tag. In order to create a Designer on it you need to call its buildDesigner method. Maps can be loaded through loadMap method.
#### Code example
```
<!DOCTYPE html>
<html>
<head>
<script src='mindplot.js'></script>
</head>
<body>
<mindmap-comp id='mindmap-comp' mode="viewonly"></mindmap-comp>
<script>
var webComponent = document.getElementById('mindmap-comp');
webComponent.buildDesigner(persistence, widget);
webComponent.loadMap("1");
</script>
</body>
</html>
```
Optionaly you can use your own presistence manager and widget manager.
If you don't have special requirements you can use the defaults.
```
var persistence = new LocalStorageManager(
'map.xml',
false, false
);
var widget = new MyAwesomeWidgetManager();
// then build the designer with these params
webComponent.buildDesigner(persistence, widget);
```
## Usage with React framework
To use the web component in your JSX code, first you need to register it in the IntrinsicElements interface using provided MindplotWebComponentInterface
#### TypeScript example
```
import { MindplotWebComponentInterface } from '@wisemapping/mindplot';
declare global {
namespace JSX {
interface IntrinsicElements {
['mindplot-component']: MindplotWebComponentInterface;
}
}
}
const App = ()=>{
const mindplotComponent: any = useRef();
useEffect(()=>{
mindplotComponent.current.buildDesigner();
mindplotComponent.current.loadMap("map_id");
}, [])
return (<div>
<mindplot-component
ref={mindplotComponent}
id="mindmap-comp"
mode={options.mode}
></mindplot-component>
</div>);
}
```
Check out the examples located in `test/playground/map-render/js` for some hints on high level usage. You can browse them by running `yarn playground`.

View File

@ -56,6 +56,7 @@ import { DesignerOptions } from './DesignerOptionsBuilder';
import DragTopic from './DragTopic';
import CentralTopic from './CentralTopic';
import FeatureType from './model/FeatureType';
import WidgetManager from './WidgetManager';
class Designer extends Events {
private _mindmap: Mindmap;
@ -882,7 +883,8 @@ class Designer extends Events {
const model = this.getModel();
const topic = model.selectedTopic();
if (topic) {
topic.showLinkEditor();
const manager = WidgetManager.getInstance();
manager.showEditorForLink(topic, null, null);
this.onObjectFocusEvent();
}
}
@ -891,7 +893,8 @@ class Designer extends Events {
const model = this.getModel();
const topic = model.selectedTopic();
if (topic) {
topic.showNoteEditor();
const manager = WidgetManager.getInstance();
manager.showEditorForNote(topic, null, null);
this.onObjectFocusEvent();
}
}

View File

@ -20,11 +20,12 @@ import $ from 'jquery';
import PersistenceManager from './PersistenceManager';
import Designer from './Designer';
import { DesignerOptions } from './DesignerOptionsBuilder';
import WidgetManager from './WidgetManager';
let designer: Designer;
export function buildDesigner(options: DesignerOptions): Designer {
const divContainer = $(`#${options.container}`);
const divContainer = options.divContainer ? $(options.divContainer) : $(`#${options.container}`);
$assert(divContainer, 'container could not be null');
// Register load events ...
@ -34,7 +35,8 @@ export function buildDesigner(options: DesignerOptions): Designer {
const persistence = options.persistenceManager;
$assert(persistence, 'persistence must be defined');
PersistenceManager.init(persistence);
const widgetManager = options.widgetManager ? options.widgetManager : new WidgetManager();
WidgetManager.init(widgetManager);
return designer;
}

View File

@ -17,14 +17,17 @@
*/
import { $assert } from '@wisemapping/core-js';
import EditorRenderMode from './EditorRenderMode';
import WidgetManager from './WidgetManager';
import PersistenceManager from './PersistenceManager';
export type DesignerOptions = {
zoom: number;
mode: EditorRenderMode;
mapId?: string;
divContainer?: HTMLElement;
container: string;
persistenceManager?: PersistenceManager;
widgetManager?: WidgetManager;
saveOnLoad?: boolean;
locale?: string;
};

View File

@ -16,13 +16,12 @@
* limitations under the License.
*/
import { $assert } from '@wisemapping/core-js';
import $ from 'jquery';
import Icon from './Icon';
import LinkIconTooltip from './widget/LinkIconTooltip';
import LinksImage from '../../assets/icons/links.svg';
import LinkModel from './model/LinkModel';
import Topic from './Topic';
import FeatureModel from './model/FeatureModel';
import WidgetManager from './WidgetManager';
class LinkIcon extends Icon {
private _linksModel: FeatureModel;
@ -31,8 +30,6 @@ class LinkIcon extends Icon {
private _readOnly: boolean;
private _tip: LinkIconTooltip;
constructor(topic: Topic, linkModel: LinkModel, readOnly: boolean) {
$assert(topic, 'topic can not be null');
$assert(linkModel, 'linkModel can not be null');
@ -47,30 +44,12 @@ class LinkIcon extends Icon {
private _registerEvents() {
this._image.setCursor('pointer');
this._tip = new LinkIconTooltip(this);
const me = this;
const manager = WidgetManager.getInstance();
manager.createTooltipForLink(this._topic, this._linksModel as LinkModel, this);
if (!this._readOnly) {
// Add on click event to open the editor ...
this.addEvent('click', (event) => {
me._tip.hide();
me._topic.showLinkEditor();
event.stopPropagation();
});
// FIXME: we shouldn't have timeout of that..
this.addEvent('mouseleave', (event) => {
setTimeout(() => {
if (!$('#linkPopover:hover').length) {
me._tip.hide();
}
event.stopPropagation();
}, 100);
});
manager.configureEditorForLink(this._topic, this._linksModel as LinkModel, this);
}
$(this.getImage().peer._native).mouseenter(() => {
me._tip.show();
});
}
getModel(): FeatureModel {

View File

@ -0,0 +1,81 @@
import Designer from './Designer';
import buildDesigner from './DesignerBuilder';
import DesignerOptionsBuilder from './DesignerOptionsBuilder';
import EditorRenderMode from './EditorRenderMode';
import LocalStorageManager from './LocalStorageManager';
import Mindmap from './model/Mindmap';
import PersistenceManager from './PersistenceManager';
import WidgetManager from './WidgetManager';
import mindplotStyles from './styles/mindplot-styles';
const defaultPersistenceManager = () => new LocalStorageManager('map.xml', false, false);
export type MindplotWebComponentInterface = {
id: string;
mode: string;
ref: any;
};
/**
* WebComponent implementation for minplot designer.
* This component is registered as mindplot-component in customElements api. (see https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)
* For use it you need to import minplot.js and put in your DOM a <mindplot-component/> tag. In order to create a Designer on it you need to call its buildDesigner method. Maps can be loaded throught loadMap method.
*/
class MindplotWebComponent extends HTMLElement {
private _shadowRoot: ShadowRoot;
private _mindmap: Mindmap;
private _designer: Designer;
constructor() {
super();
this._shadowRoot = this.attachShadow({ mode: 'open' });
const mindplotStylesElement = document.createElement('style');
mindplotStylesElement.innerHTML = mindplotStyles;
this._shadowRoot.appendChild(mindplotStylesElement);
const wrapper = document.createElement('div');
wrapper.setAttribute('class', 'wise-editor');
wrapper.setAttribute('id', 'mindplot');
this._shadowRoot.appendChild(wrapper);
}
/**
* @returns the designer
*/
getDesigner(): Designer {
return this._designer;
}
/**
* Build the designer of the component
* @param {PersistenceManager} persistence the persistence manager to be used. By default a LocalStorageManager is created
* @param {UIManager} widgetManager an UI Manager to override default Designer option.
*/
buildDesigner(persistence?: PersistenceManager, widgetManager?: WidgetManager) {
const editorRenderMode = this.getAttribute('mode') as EditorRenderMode;
const persistenceManager = persistence || defaultPersistenceManager();
const mode = editorRenderMode || 'viewonly';
const options = DesignerOptionsBuilder.buildOptions({
persistenceManager,
mode,
widgetManager,
divContainer: this._shadowRoot.getElementById('mindplot'),
container: 'mindplot',
zoom: 0.85,
locale: 'en',
});
this._designer = buildDesigner(options);
}
/**
* Load map in designer throught persistence manager instance
* @param id the map id to be loaded.
*/
loadMap(id: string) {
const instance = PersistenceManager.getInstance();
this._mindmap = instance.load(id);
this._designer.loadMap(this._mindmap);
}
}
export default MindplotWebComponent;

View File

@ -16,14 +16,12 @@
* limitations under the License.
*/
import { $assert } from '@wisemapping/core-js';
import $ from 'jquery';
import { $msg } from './Messages';
import Icon from './Icon';
import NotesImage from '../../assets/icons/notes.svg';
import Topic from './Topic';
import NoteModel from './model/NoteModel';
import FeatureModel from './model/FeatureModel';
import FloatingTip from './widget/FloatingTip';
import WidgetManager from './WidgetManager';
class NoteIcon extends Icon {
private _linksModel: NoteModel;
@ -32,8 +30,6 @@ class NoteIcon extends Icon {
private _readOnly: boolean;
private _tip: FloatingTip;
constructor(topic: Topic, noteModel: NoteModel, readOnly: boolean) {
$assert(topic, 'topic can not be null');
@ -47,42 +43,12 @@ class NoteIcon extends Icon {
private _registerEvents(): void {
this._image.setCursor('pointer');
const me = this;
const manager = WidgetManager.getInstance();
manager.createTooltipForNote(this._topic, this._linksModel as NoteModel, this);
if (!this._readOnly) {
// Add on click event to open the editor ...
this.addEvent('click', (event) => {
me._topic.showNoteEditor();
event.stopPropagation();
});
manager.configureEditorForNote(this._topic, this._linksModel as NoteModel, this);
}
this._tip = new FloatingTip($(me.getImage().peer._native), {
title: $msg('NOTE'),
// Content can also be a function of the target element!
content() {
return me._buildTooltipContent();
},
html: true,
placement: 'bottom',
destroyOnExit: true,
});
}
private _buildTooltipContent(): JQuery {
if ($('body').find('#textPopoverNote').length === 1) {
const text = $('body').find('#textPopoverNote');
text.text(this._linksModel.getText());
return text;
}
const result = $('<div id="textPopoverNote"></div>').css({ padding: '5px' });
const text = $('<div></div>').text(this._linksModel.getText()).css({
'white-space': 'pre-wrap',
'word-wrap': 'break-word',
});
result.append(text);
return result;
}
getModel(): FeatureModel {

View File

@ -28,9 +28,7 @@ import ConnectionLine from './ConnectionLine';
import IconGroup from './IconGroup';
import EventBus from './layout/EventBus';
import ShirinkConnector from './ShrinkConnector';
import NoteEditor from './widget/NoteEditor';
import ActionDispatcher from './ActionDispatcher';
import LinkEditor from './widget/LinkEditor';
import TopicEventDispatcher, { TopicEvent } from './TopicEventDispatcher';
import { TopicShape } from './model/INodeModel';
@ -732,79 +730,65 @@ abstract class Topic extends NodeGraph {
});
}
showNoteEditor(): void {
const topicId = this.getId();
getNoteValue(): string {
const model = this.getModel();
const editorModel = {
getValue(): string {
const notes = model.findFeatureByType(TopicFeatureFactory.Note.id);
let result;
if (notes.length > 0) {
result = (notes[0] as NoteModel).getText();
}
const notes = model.findFeatureByType(TopicFeatureFactory.Note.id);
let result;
if (notes.length > 0) {
result = (notes[0] as NoteModel).getText();
}
return result;
},
setValue(value: string) {
const dispatcher = ActionDispatcher.getInstance();
const notes = model.findFeatureByType(TopicFeatureFactory.Note.id);
if (!$defined(value)) {
const featureId = notes[0].getId();
dispatcher.removeFeatureFromTopic(topicId, featureId);
} else if (notes.length > 0) {
dispatcher.changeFeatureToTopic(topicId, notes[0].getId(), {
text: value,
});
} else {
dispatcher.addFeatureToTopic(topicId, TopicFeatureFactory.Note.id, {
text: value,
});
}
},
};
const editor = new NoteEditor(editorModel);
this.closeEditors();
editor.show();
return result;
}
/** opens a dialog where the user can enter or edit an existing link associated with this topic */
showLinkEditor() {
setNoteValue(value: string) {
const topicId = this.getId();
const model = this.getModel();
const editorModel = {
getValue(): string {
// @param {mindplot.model.LinkModel[]} links
const links = model.findFeatureByType(TopicFeatureFactory.Link.id);
let result;
if (links.length > 0) {
result = (links[0] as LinkModel).getUrl();
}
const dispatcher = ActionDispatcher.getInstance();
const notes = model.findFeatureByType(TopicFeatureFactory.Note.id);
if (!$defined(value)) {
const featureId = notes[0].getId();
dispatcher.removeFeatureFromTopic(topicId, featureId);
} else if (notes.length > 0) {
dispatcher.changeFeatureToTopic(topicId, notes[0].getId(), {
text: value,
});
} else {
dispatcher.addFeatureToTopic(topicId, TopicFeatureFactory.Note.id, {
text: value,
});
}
}
return result;
},
getLinkValue(): string {
const model = this.getModel();
// @param {mindplot.model.LinkModel[]} links
const links = model.findFeatureByType(TopicFeatureFactory.Link.id);
let result;
if (links.length > 0) {
result = (links[0] as LinkModel).getUrl();
}
setValue(value: string) {
const dispatcher = ActionDispatcher.getInstance();
const links = model.findFeatureByType(TopicFeatureFactory.Link.id);
if (!$defined(value)) {
const featureId = links[0].getId();
dispatcher.removeFeatureFromTopic(topicId, featureId);
} else if (links.length > 0) {
dispatcher.changeFeatureToTopic(topicId, links[0].getId(), {
url: value,
});
} else {
dispatcher.addFeatureToTopic(topicId, TopicFeatureFactory.Link.id, {
url: value,
});
}
},
};
return result;
}
this.closeEditors();
const editor = new LinkEditor(editorModel);
editor.show();
setLinkValue(value: string) {
const topicId = this.getId();
const model = this.getModel();
const dispatcher = ActionDispatcher.getInstance();
const links = model.findFeatureByType(TopicFeatureFactory.Link.id);
if (!$defined(value)) {
const featureId = links[0].getId();
dispatcher.removeFeatureFromTopic(topicId, featureId);
} else if (links.length > 0) {
dispatcher.changeFeatureToTopic(topicId, links[0].getId(), {
url: value,
});
} else {
dispatcher.addFeatureToTopic(topicId, TopicFeatureFactory.Link.id, {
url: value,
});
}
}
closeEditors() {

View File

@ -0,0 +1,107 @@
import $ from 'jquery';
import LinkIcon from './LinkIcon';
import LinkModel from './model/LinkModel';
import NoteModel from './model/NoteModel';
import NoteIcon from './NoteIcon';
import Topic from './Topic';
import { $msg } from './Messages';
class WidgetManager {
// eslint-disable-next-line no-use-before-define
static _instance: WidgetManager;
static init = (instance: WidgetManager) => {
this._instance = instance;
};
static getInstance(): WidgetManager {
return this._instance;
}
private createTooltip(mindmapElement, title, linkModel: LinkModel, noteModel: NoteModel) {
const webcomponentShadowRoot = $($('#mindmap-comp')[0].shadowRoot);
let tooltip = webcomponentShadowRoot.find('#mindplot-svg-tooltip');
if (!tooltip.length) {
webcomponentShadowRoot.append(
'<div id="mindplot-svg-tooltip" class="mindplot-svg-tooltip">' +
'<div id="mindplot-svg-tooltip-title" class="mindplot-svg-tooltip-title"></div>' +
'<div id="mindplot-svg-tooltip-content" class="mindplot-svg-tooltip-content">' +
'<a id="mindplot-svg-tooltip-content-link" alt="Open in new window ..." class="mindplot-svg-tooltip-content-link" target="_blank"></a>' +
'<p id="mindplot-svg-tooltip-content-note" class="mindplot-svg-tooltip-content-note"></p>' +
'</div>' +
'</div>',
);
tooltip = webcomponentShadowRoot.find('#mindplot-svg-tooltip');
tooltip.on('mouseover', (evt) => {
tooltip.css({ display: 'block' });
evt.stopPropagation();
});
tooltip.on('mouseleave', (evt) => {
tooltip.css({ display: 'none' });
evt.stopPropagation();
});
}
mindmapElement.addEvent('mouseenter', (evt) => {
webcomponentShadowRoot.find('#mindplot-svg-tooltip-title').html(title);
if (linkModel) {
webcomponentShadowRoot
.find('#mindplot-svg-tooltip-content-link')
.attr('href', linkModel.getUrl());
webcomponentShadowRoot.find('#mindplot-svg-tooltip-content-link').html(linkModel.getUrl());
webcomponentShadowRoot.find('#mindplot-svg-tooltip-content-link').css({ display: 'block' });
webcomponentShadowRoot.find('#mindplot-svg-tooltip-content-note').css({ display: 'none' });
}
if (noteModel) {
webcomponentShadowRoot.find('#mindplot-svg-tooltip-content-note').html(noteModel.getText());
webcomponentShadowRoot.find('#mindplot-svg-tooltip-content-note').css({ display: 'block' });
webcomponentShadowRoot.find('#mindplot-svg-tooltip-content-link').css({ display: 'none' });
}
const targetRect = evt.target.getBoundingClientRect();
const newX = Math.max(0, targetRect.left + targetRect.width / 2 - tooltip.width() / 2);
const newY = Math.max(0, targetRect.bottom + 10);
tooltip.css({ top: newY, left: newX, position: 'absolute' });
tooltip.css({ display: 'block' });
evt.stopPropagation();
});
mindmapElement.addEvent('mouseleave', (evt) => {
tooltip.css({ display: 'none' });
evt.stopPropagation();
});
}
createTooltipForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) {
this.createTooltip(linkIcon.getImage().peer, $msg('LINK'), linkModel, undefined);
}
createTooltipForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) {
this.createTooltip(noteIcon.getImage().peer, $msg('NOTE'), undefined, noteModel);
}
configureEditorForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) {
const htmlImage = linkIcon.getImage().peer;
htmlImage.addEvent('click', (evt) => {
this.showEditorForLink(topic, linkModel, linkIcon);
evt.stopPropagation();
});
}
configureEditorForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) {
const htmlImage = noteIcon.getImage().peer;
htmlImage.addEvent('click', (evt) => {
this.showEditorForNote(topic, noteModel, noteIcon);
evt.stopPropagation();
});
}
showEditorForLink(topic: Topic, linkModel: LinkModel, linkIcon: LinkIcon) {
console.log('Show link editor not yet implemented');
}
showEditorForNote(topic: Topic, noteModel: NoteModel, noteIcon: NoteIcon) {
console.log('Show note editor not yet implemented');
}
}
export default WidgetManager;

View File

@ -223,7 +223,9 @@ class Workspace {
// Change cursor.
window.document.body.style.cursor = 'move';
mouseMoveEvent.preventDefault();
// If I dont ignore touchmove events, browser console shows a lot of errors:
// Unable to preventDefault inside passive event listener invocation.
if (mouseMoveEvent.type !== 'touchmove') mouseMoveEvent.preventDefault();
// Fire drag event ...
screenManager.fireEvent('update');

View File

@ -0,0 +1,132 @@
const mindplotStyles = `
div#mindplot {
position: relative;
left: 0;
width: 100%;
height: 100%;
border: 0;
overflow: hidden;
opacity: 1;
background-color: #f2f2f2;
background-image: linear-gradient(#ebe9e7 1px, transparent 1px),
linear-gradient(to right, #ebe9e7 1px, #f2f2f2 1px);
background-size: 50px 50px;
}
.mindplot-svg-tooltip {
display: none;
color: rgb(51, 51, 51);
text-align: center;
padding: 1px;
border-radius: 6px;
position: absolute;
z-index: 999;
background-color: rgb(255, 255, 255);
animation: fadeIn 0.4s;
}
.fade-in {
animation: fadeIn ease 0.4s;
-webkit-animation: fadeIn ease 0.4s;
-moz-animation: fadeIn ease 0.4s;
-o-animation: fadeIn ease 0.4s;
-ms-animation: fadeIn ease 0.4s;
}
@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-moz-keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-webkit-keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-o-keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-ms-keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
.mindplot-svg-tooltip-title {
background-color: rgb(247, 247, 247);
border-bottom-color: rgb(235, 235, 235);
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
border-bottom-style: solid;
border-bottom-width: 1px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
box-sizing: border-box;
color: rgb(51, 51, 51);
cursor: default;
display: block;
padding: 8px 14px;
text-align: left;
font-family: Arial;
font-size: small;
}
.mindplot-svg-tooltip-content {
background-color: rgb(255, 255, 255);
padding: 6px 4px;
max-width: 250px;
}
.mindplot-svg-tooltip-content-link {
padding: 3px 5px;
overflow: hidden;
font-size: smaller;
text-decoration: none;
font-family: Arial;
font-size: small;
color: #428bca;
}
.mindplot-svg-tooltip-content-note {
text-align: left;
font-family: Arial;
font-size: small;
}
.mindplot-svg-tooltip:before {
content: "";
position: absolute;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid #fff;
bottom: 100%;
right: 50%;
transform: translateX(50%);
z-index: 5;
}
.mindplot-svg-tooltip:after {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid rgb(247, 247, 247);
bottom: calc(1px + 100%);
right: 50%;
transform: translateX(50%);
}
`;
export default mindplotStyles;

View File

@ -1,154 +0,0 @@
/*
* Copyright [2021] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import $ from 'jquery';
import Options from '../../Options';
import { $msg } from '../../Messages';
class BootstrapDialog extends Options {
constructor(title, options) {
super();
this.options = {
cancelButton: false,
closeButton: false,
acceptButton: true,
removeButton: false,
errorMessage: false,
onEventData: {},
};
this.setOptions(options);
this.options.onEventData.dialog = this;
this._native = $('<div class="modal fade" tabindex="-1"></div>').append(
'<div class="modal-dialog"></div>',
);
const content = $('<div class="modal-content"></div>');
const header = this._buildHeader(title);
if (header) {
content.append(header);
}
const body = $('<div class="modal-body"></div>');
if (this.options.errorMessage) {
const error = $('<div class="alert alert-danger"></div>');
error.hide();
body.append(error);
}
content.append(body);
const footer = this._buildFooter();
if (footer) {
content.append(footer);
}
this._native.find('.modal-dialog').append(content);
this._native.on('hidden.bs.modal', function remove() {
$(this).remove();
});
this._native.on('shown.bs.modal', this.onDialogShown);
this._native.appendTo('#mindplot-tooltips');
}
_buildFooter() {
let footer = null;
if (this.options.acceptButton || this.options.removeButton || this.options.cancelButton) {
footer = $('<div class="modal-footer" style="paddingTop:5;textAlign:center">');
}
if (this.options.acceptButton) {
this.acceptButton = $(
`<button type="button" class="btn btn-primary" id="acceptBtn" data-dismiss="modal">${$msg(
'ACCEPT',
)}</button>`,
);
footer.append(this.acceptButton);
this.acceptButton.unbind('click').on('click', this.options.onEventData, this.onAcceptClick);
}
if (this.options.removeButton) {
this.removeButton = $(
`<button type="button" class="btn btn-secondary" id="removeBtn" data-dismiss="modal">${$msg(
'REMOVE',
)}</button>`,
);
footer.append(this.removeButton);
this.removeButton.on('click', this.options.onEventData, this.onRemoveClick);
}
if (this.options.cancelButton) {
footer.append(
`<button type="button" class="btn btn-secondary" data-dismiss="modal">${$msg(
'CANCEL',
)}</button>`,
);
}
return footer;
}
_buildHeader(title) {
let header = null;
if (this.options.closeButton || title) {
header = $('<div class="modal-header"></div>');
}
if (this.options.closeButton) {
header.append(
'<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>',
);
}
if (title) {
header.append(`<h2 class="modal-title">${title}</h2>`);
}
return header;
}
onAcceptClick(event) {
throw new Error('Unsupported operation');
}
onDialogShown(event) {
// Overwrite default behaviour ...
}
onRemoveClick(event) {
throw new Error('Unsupported operation');
}
show() {
this._native.modal();
}
setContent(content) {
const modalBody = this._native.find('.modal-body');
modalBody.append(content);
}
css(options) {
this._native.find('.modal-dialog').css(options);
}
close() {
this._native.modal('hide');
}
alertError(message) {
this._native.find('.alert-danger').text(message);
this._native.find('.alert-danger').show();
}
cleanError() {
this._native.find('.alert-danger').hide();
}
}
export default BootstrapDialog;

View File

@ -31,7 +31,19 @@ import Exporter from './components/export/Exporter';
import Importer from './components/import/Importer';
import DesignerKeyboard from './components/DesignerKeyboard';
import EditorRenderMode from './components/EditorRenderMode';
import ImageIcon from './components/ImageIcon';
import MindplotWebComponent, {
MindplotWebComponentInterface,
} from './components/MindplotWebComponent';
import LinkIcon from './components/LinkIcon';
import NoteIcon from './components/NoteIcon';
import Topic from './components/Topic';
import LinkModel from './components/model/LinkModel';
import NoteModel from './components/model/NoteModel';
import WidgetManager from './components/WidgetManager';
import { buildDesigner } from './components/DesignerBuilder';
@ -39,10 +51,15 @@ import { $notify } from './components/widget/ToolbarNotifier';
import { $msg } from './components/Messages';
// This hack is required to initialize Bootstrap. In future, this should be removed.
import './mindplot-styles.css';
const globalAny: any = global;
globalAny.jQuery = jquery;
require('../../../libraries/bootstrap/js/bootstrap.min');
// WebComponent registration
// The if statement is the fix for doble registration problem. Can be deleted wen webapp-mindplot dependency be dead.
if (!customElements.get('mindplot-component')) {
customElements.define('mindplot-component', MindplotWebComponent);
}
export {
Mindmap,
@ -64,4 +81,12 @@ export {
$notify,
$msg,
DesignerKeyboard,
MindplotWebComponent,
MindplotWebComponentInterface,
LinkIcon,
LinkModel,
NoteIcon,
NoteModel,
WidgetManager,
Topic,
};

View File

@ -17,42 +17,30 @@
*/
import jquery from 'jquery';
import {} from './components/widget/ToolbarNotifier';
import { buildDesigner } from './components/DesignerBuilder';
import PersistenceManager from './components/PersistenceManager';
import LocalStorageManager from './components/LocalStorageManager';
import DesignerOptionsBuilder from './components/DesignerOptionsBuilder';
import MindplotWebComponent from './components/MindplotWebComponent';
console.log('loading static mindmap in read-only');
// This hack is required to initialize Bootstrap. In future, this should be removed.
const globalAny: any = global;
globalAny.jQuery = jquery;
require('../../../libraries/bootstrap/js/bootstrap.min');
// WebComponent registration
customElements.define('mindplot-component', MindplotWebComponent);
// Configure designer options ...
const historyId = global.historyId ? `${global.historyId}/` : '';
const persistence: PersistenceManager = new LocalStorageManager(
`/c/restful/maps/{id}/${historyId}document/xml${!global.isAuth ? '-pub' : ''}`,
true,
);
// Obtain map zoom from query param if it was specified...
const params = new URLSearchParams(window.location.search.substring(1));
const zoomParam = Number.parseFloat(params.get('zoom'));
const options = DesignerOptionsBuilder.buildOptions({
persistenceManager: persistence,
mode: 'viewonly',
mapId: global.mapId,
container: 'mindplot',
zoom: zoomParam || global.userOptions.zoom,
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);
const webComponent: MindplotWebComponent = document.getElementById(
'mindmap-comp',
) as MindplotWebComponent;
webComponent.buildDesigner(persistence);
webComponent.loadMap(global.mapId);

View File

@ -0,0 +1,110 @@
/*
DO NOT USE THIS FILE until we resolve how to import .css files into shadow dom
As a workaround, use the file mindplot-styles.js instead
*/
.mindplot-svg-tooltip {
display: none;
color: rgb(51, 51, 51);
text-align: center;
padding: 1px;
border-radius: 6px;
position: absolute;
z-index: 999;
background-color: rgb(255, 255, 255);
animation: fadeIn 0.4s;
}
.fade-in {
animation: fadeIn ease 0.4s;
-webkit-animation: fadeIn ease 0.4s;
-moz-animation: fadeIn ease 0.4s;
-o-animation: fadeIn ease 0.4s;
-ms-animation: fadeIn ease 0.4s;
}
@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-moz-keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-webkit-keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-o-keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-ms-keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
}
.mindplot-svg-tooltip-title {
background-color: rgb(247, 247, 247);
border-bottom-color: rgb(235, 235, 235);
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
border-bottom-style: solid;
border-bottom-width: 1px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
box-sizing: border-box;
color: rgb(51, 51, 51);
cursor: default;
display: block;
font-family: Arial;
padding: 8px 14px;
text-align: left;
}
.mindplot-svg-tooltip-content {
background-color: rgb(255, 255, 255);
padding: 6px 4px;
max-width: 250px;
}
.mindplot-svg-tooltip-content-link {
padding: 3px 5px;
overflow: hidden;
}
.mindplot-svg-tooltip-content-note {
text-align: left;
}
.mindplot-svg-tooltip:before {
content: "";
position: absolute;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid #fff;
bottom: 100%;
right: 50%;
transform: translateX(50%);
z-index: 5;
}
.mindplot-svg-tooltip:after {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid rgb(247, 247, 247);
bottom: calc(1px + 100%);
right: 50%;
transform: translateX(50%);
}

View File

@ -14,8 +14,10 @@
]
},
"include": [
"src/**/*"
, "../editor/src/classes/menu/AccountSettingsPanel.js", "../editor/src/classes/menu/IMenu.ts", "../editor/src/classes/bootstrap" ],
"src/**/*",
"../editor/src/classes/menu/AccountSettingsPanel.js",
"../editor/src/classes/menu/IMenu.ts"
],
"exclude": [
"node_modules"
]

View File

@ -35,6 +35,10 @@ module.exports = {
test: /\.(png|svg)$/i,
type: 'asset/inline',
},
{
test: /\.css$/,
loader: 'css-loader',
},
],
},
resolve: {

View File

@ -20,21 +20,12 @@ const playgroundConfig = {
devServer: {
historyApiFallback: true,
port: 8083,
open: false,
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader?url=false'],
},
],
open: false
},
plugins: [
new CleanWebpackPlugin(),
new CopyPlugin({
patterns: [
{ from: '../../libraries/bootstrap', to: 'bootstrap' },
{ from: 'test/playground/index.html', to: 'index.html' },
],
}),

View File

@ -64,7 +64,6 @@
"@mui/styles": "^5.3.0",
"@reduxjs/toolkit": "^1.5.0",
"@wisemapping/editor": "^0.4.0",
"@wisemapping/mindplot": "^5.0.2",
"axios": "^0.21.0",
"dayjs": "^1.10.7",
"react": "^17.0.2",

View File

@ -1,4 +1,4 @@
import { EditorRenderMode, Mindmap, PersistenceManager } from '@wisemapping/mindplot';
import { EditorRenderMode, Mindmap, PersistenceManager } from '@wisemapping/editor';
import Client, {
AccountInfo,
BasicMapInfo,

View File

@ -1,4 +1,4 @@
import { EditorRenderMode, Mindmap, PersistenceManager } from '@wisemapping/mindplot';
import { EditorRenderMode, Mindmap, PersistenceManager } from '@wisemapping/editor';
import { Locale, LocaleCode } from '../app-i18n';
export type NewUser = {

View File

@ -1,5 +1,5 @@
import { Mindmap, MockPersistenceManager, PersistenceManager } from '@wisemapping/mindplot';
import XMLSerializerTango from '@wisemapping/mindplot/src/components/persistence/XMLSerializerTango';
import { Mindmap, MockPersistenceManager, PersistenceManager } from '@wisemapping/editor';
import XMLSerializerTango from '@wisemapping/editor';
import Client, {
AccountInfo,
BasicMapInfo,

View File

@ -4,8 +4,8 @@ import {
Mindmap,
PersistenceManager,
RESTPersistenceManager,
} from '@wisemapping/mindplot';
import { PersistenceError } from '@wisemapping/mindplot/src/components/PersistenceManager';
} from '@wisemapping/editor';
import { PersistenceError } from '@wisemapping/editor';
import axios, { AxiosInstance, AxiosResponse } from 'axios';
import Client, {
ErrorInfo,

View File

@ -1,5 +1,5 @@
import { EditorOptions } from '@wisemapping/editor';
import { EditorRenderMode } from '@wisemapping/mindplot';
import { EditorRenderMode } from '@wisemapping/editor';
import AppConfig from '../../classes/app-config';
class EditorOptionsBuilder {

View File

@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
import ActionDispatcher from '../maps-page/action-dispatcher';
import { ActionType } from '../maps-page/action-chooser';
import Editor from '@wisemapping/editor';
import { EditorRenderMode, PersistenceManager } from '@wisemapping/mindplot';
import { EditorRenderMode, PersistenceManager } from '@wisemapping/editor';
import { IntlProvider } from 'react-intl';
import AppI18n, { Locales } from '../../classes/app-i18n';
import { useSelector } from 'react-redux';

View File

@ -16,12 +16,12 @@ import {
ImageExporterFactory,
Exporter,
Mindmap,
} from '@wisemapping/mindplot';
} from '@wisemapping/editor';
import Client from '../../../../classes/client';
import { activeInstance } from '../../../../redux/clientSlice';
import { useSelector } from 'react-redux';
import SizeType from '@wisemapping/mindplot/src/components/SizeType';
import SizeType from '@wisemapping/editor';
import Checkbox from '@mui/material/Checkbox';
type ExportFormat = 'svg' | 'jpg' | 'png' | 'txt' | 'mm' | 'wxml' | 'xls' | 'md';

View File

@ -1,7 +1,7 @@
import { Alert } from '@mui/material';
import Button from '@mui/material/Button';
import FormControl from '@mui/material/FormControl';
import { Importer, TextImporterFactory } from '@wisemapping/mindplot';
import { Importer, TextImporterFactory } from '@wisemapping/editor';
import React from 'react';
import { FormattedMessage, useIntl } from 'react-intl';

View File

@ -34,6 +34,10 @@ module.exports = {
test: /\.wxml$/i,
type: 'asset/source',
},
{
test: /\.css$/,
loader: 'css-loader',
},
],
},
optimization: {