Add theme support.

This commit is contained in:
Paulo Gustavo Veiga 2023-02-13 20:17:27 -08:00
parent 9fad52a04d
commit f28e23490e
16 changed files with 145 additions and 34 deletions

View File

@ -48,7 +48,7 @@ describe('Topic Shape Suite', () => {
.invoke('attr', 'rx') .invoke('attr', 'rx')
.then(parseInt) .then(parseInt)
.should('be.a', 'number') .should('be.a', 'number')
.should('be.lte', 8); .should('be.eq', 9);
cy.focusTopicByText('Mind Mapping'); cy.focusTopicByText('Mind Mapping');
cy.matchImageSnapshot('changeToRoundedRectangle'); cy.matchImageSnapshot('changeToRoundedRectangle');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -9,6 +9,7 @@ import {
} from '../../../components/toolbar/ToolbarValueModelBuilder'; } from '../../../components/toolbar/ToolbarValueModelBuilder';
import { LineType } from '@wisemapping/mindplot/src/components/ConnectionLine'; import { LineType } from '@wisemapping/mindplot/src/components/ConnectionLine';
import { TopicShapeType } from '@wisemapping/mindplot/src/components/model/INodeModel'; import { TopicShapeType } from '@wisemapping/mindplot/src/components/model/INodeModel';
import ThemeType from '@wisemapping/mindplot/src/components/model/ThemeType';
class NodePropertyBuilder { class NodePropertyBuilder {
designer: Designer; designer: Designer;
@ -25,6 +26,7 @@ class NodePropertyBuilder {
private connectionColoreModel: NodeProperty<string>; private connectionColoreModel: NodeProperty<string>;
private noteModel: NodeProperty<string>; private noteModel: NodeProperty<string>;
private linkModel: NodeProperty<string>; private linkModel: NodeProperty<string>;
private _themeModel: NodeProperty<ThemeType>;
constructor(designer: Designer) { constructor(designer: Designer) {
this.designer = designer; this.designer = designer;
@ -92,10 +94,6 @@ class NodePropertyBuilder {
return this.selectedTopicColorModel; return this.selectedTopicColorModel;
} }
/**
*
* @returns model to get and set the node link
*/
getLinkModel(): NodeProperty<string> { getLinkModel(): NodeProperty<string> {
// const selected = this.selectedTopic(); // const selected = this.selectedTopic();
if (!this.linkModel) if (!this.linkModel)
@ -112,6 +110,18 @@ class NodePropertyBuilder {
return this.linkModel; return this.linkModel;
} }
getThemeModel(): NodeProperty<ThemeType> {
// const selected = this.selectedTopic();
if (!this._themeModel)
this._themeModel = {
getValue: (): ThemeType => this.designer.getMindmap().getTheme(),
setValue: (value: ThemeType) => {
this.designer.changeTheme(value);
},
};
return this._themeModel;
}
/** /**
* *
* @returns model to get and set topic border color * @returns model to get and set topic border color

View File

@ -0,0 +1,47 @@
/*
* 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 Box from '@mui/material/Box';
import FormControlLabel from '@mui/material/FormControlLabel';
import Radio from '@mui/material/Radio';
import RadioGroup from '@mui/material/RadioGroup';
import { SelectChangeEvent } from '@mui/material/Select';
import React, { ReactElement, useState } from 'react';
import NodeProperty from '../../../../classes/model/node-property';
const ThemeEditor = (props: {
closeModal: () => void;
themeModel: NodeProperty<string> | null;
}): ReactElement => {
const [theme, setTheme] = useState(props.themeModel.getValue());
const handleOnChange = (event: SelectChangeEvent) => {
setTheme(event.target.value);
props.themeModel.setValue(event.target.value);
props.closeModal();
};
return (
<Box sx={{ px: 2, pb: 2, width: '300px' }}>
<RadioGroup row value={theme} onChange={handleOnChange}>
<FormControlLabel value="classic" control={<Radio />} label="Classic" />
<FormControlLabel value="prism" control={<Radio />} label="Summer" />
</RadioGroup>
</Box>
);
};
export default ThemeEditor;

View File

@ -39,6 +39,7 @@ import ShareOutlined from '@mui/icons-material/ShareOutlined';
import SwapCallsOutlined from '@mui/icons-material/SwapCallsOutlined'; import SwapCallsOutlined from '@mui/icons-material/SwapCallsOutlined';
import NotInterestedOutlined from '@mui/icons-material/NotInterestedOutlined'; import NotInterestedOutlined from '@mui/icons-material/NotInterestedOutlined';
import ShortcutIconOutlined from '@mui/icons-material/ShortcutOutlined'; import ShortcutIconOutlined from '@mui/icons-material/ShortcutOutlined';
import ColorLensOutlined from '@mui/icons-material/ColorLensOutlined';
import Palette from '@mui/icons-material/Square'; import Palette from '@mui/icons-material/Square';
import SquareOutlined from '@mui/icons-material/SquareOutlined'; import SquareOutlined from '@mui/icons-material/SquareOutlined';
@ -53,6 +54,7 @@ import FontFamilySelector from '../action-widget/button/font-family-selector';
import Editor from '../../classes/model/editor'; import Editor from '../../classes/model/editor';
import { IntlShape } from 'react-intl'; import { IntlShape } from 'react-intl';
import { LineType } from '@wisemapping/mindplot/src/components/ConnectionLine'; import { LineType } from '@wisemapping/mindplot/src/components/ConnectionLine';
import ThemeEditor from '../action-widget/pane/theme-editor';
const keyTooltip = (msg: string, key: string): string => { const keyTooltip = (msg: string, key: string): string => {
const isMac = window.navigator.platform.toUpperCase().indexOf('MAC') >= 0; const isMac = window.navigator.platform.toUpperCase().indexOf('MAC') >= 0;
@ -381,6 +383,24 @@ export function buildEditorPanelConfig(model: Editor, intl: IntlShape): ActionCo
disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0, disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0,
}; };
/**
* tool for node link edition
*/
const editThemeConfiguration: ActionConfig = {
icon: <ColorLensOutlined />,
tooltip: intl.formatMessage({ id: 'editor-panel.tooltip-theme', defaultMessage: 'Theme' }),
useClickToClose: true,
title: intl.formatMessage({ id: 'editor-panel.theme-title', defaultMessage: 'Theme' }),
options: [
{
render: (closeModal) => (
<ThemeEditor closeModal={closeModal} themeModel={modelBuilder.getThemeModel()} />
),
},
],
disabled: () => model.getDesignerModel().filterSelectedTopics().length === 0,
};
/** /**
* tool for node note edition * tool for node note edition
*/ */
@ -461,5 +481,6 @@ export function buildEditorPanelConfig(model: Editor, intl: IntlShape): ActionCo
editNoteConfiguration, editNoteConfiguration,
editLinkUrlConfiguration, editLinkUrlConfiguration,
addRelationConfiguration, addRelationConfiguration,
editThemeConfiguration,
]; ];
} }

View File

@ -60,6 +60,7 @@ import { LineType } from './ConnectionLine';
import XMLSerializerFactory from './persistence/XMLSerializerFactory'; import XMLSerializerFactory from './persistence/XMLSerializerFactory';
import ImageExpoterFactory from './export/ImageExporterFactory'; import ImageExpoterFactory from './export/ImageExporterFactory';
import PositionType from './PositionType'; import PositionType from './PositionType';
import ThemeType from './model/ThemeType';
class Designer extends Events { class Designer extends Events {
private _mindmap: Mindmap | null; private _mindmap: Mindmap | null;
@ -70,7 +71,7 @@ class Designer extends Events {
private _model: DesignerModel; private _model: DesignerModel;
private _workspace: Canvas; private _canvas: Canvas;
_eventBussDispatcher: EventBusDispatcher; _eventBussDispatcher: EventBusDispatcher;
@ -108,7 +109,7 @@ class Designer extends Events {
// Init Screen manager.. // Init Screen manager..
const screenManager = new ScreenManager(divElem); const screenManager = new ScreenManager(divElem);
this._workspace = new Canvas(screenManager, this._model.getZoom(), this.isReadOnly()); this._canvas = new Canvas(screenManager, this._model.getZoom(), this.isReadOnly());
// Init layout manager ... // Init layout manager ...
this._eventBussDispatcher = new EventBusDispatcher(); this._eventBussDispatcher = new EventBusDispatcher();
@ -121,11 +122,11 @@ class Designer extends Events {
// Register keyboard events ... // Register keyboard events ...
DesignerKeyboard.register(this); DesignerKeyboard.register(this);
this._dragManager = this._buildDragManager(this._workspace); this._dragManager = this._buildDragManager(this._canvas);
} }
this._registerWheelEvents(); this._registerWheelEvents();
this._relPivot = new RelationshipPivot(this._workspace, this); this._relPivot = new RelationshipPivot(this._canvas, this);
TopicEventDispatcher.configure(this.isReadOnly()); TopicEventDispatcher.configure(this.isReadOnly());
@ -168,7 +169,7 @@ class Designer extends Events {
} }
private _registerMouseEvents() { private _registerMouseEvents() {
const workspace = this._workspace; const workspace = this._canvas;
const screenManager = workspace.getScreenManager(); const screenManager = workspace.getScreenManager();
const me = this; const me = this;
// Initialize workspace event listeners. // Initialize workspace event listeners.
@ -202,7 +203,7 @@ class Designer extends Events {
private _buildDragManager(workspace: Canvas): DragManager { private _buildDragManager(workspace: Canvas): DragManager {
const designerModel = this.getModel(); const designerModel = this.getModel();
const dragConnector = new DragConnector(designerModel, this._workspace); const dragConnector = new DragConnector(designerModel, this._canvas);
const dragManager = new DragManager(workspace, this._eventBussDispatcher); const dragManager = new DragManager(workspace, this._eventBussDispatcher);
const topics = designerModel.getTopics(); const topics = designerModel.getTopics();
@ -263,7 +264,7 @@ class Designer extends Events {
} }
if (targetTopic) { if (targetTopic) {
topic.connectTo(targetTopic, this._workspace); topic.connectTo(targetTopic, this._canvas);
} }
} }
@ -332,12 +333,12 @@ class Designer extends Events {
return; return;
} }
this.getModel().setZoom(zoom); this.getModel().setZoom(zoom);
this._workspace.setZoom(zoom); this._canvas.setZoom(zoom);
} }
zoomToFit(): void { zoomToFit(): void {
this.getModel().setZoom(1); this.getModel().setZoom(1);
this._workspace.setZoom(1, true); this._canvas.setZoom(1, true);
} }
zoomOut(factor = 1.2) { zoomOut(factor = 1.2) {
@ -345,7 +346,7 @@ class Designer extends Events {
const scale = model.getZoom() * factor; const scale = model.getZoom() * factor;
if (scale <= 7.0) { if (scale <= 7.0) {
model.setZoom(scale); model.setZoom(scale);
this._workspace.setZoom(scale); this._canvas.setZoom(scale);
} else { } else {
$notify($msg('ZOOM_ERROR')); $notify($msg('ZOOM_ERROR'));
} }
@ -357,7 +358,7 @@ class Designer extends Events {
if (scale >= 0.3) { if (scale >= 0.3) {
model.setZoom(scale); model.setZoom(scale);
this._workspace.setZoom(scale); this._canvas.setZoom(scale);
} else { } else {
$notify($msg('ZOOM_ERROR')); $notify($msg('ZOOM_ERROR'));
} }
@ -590,7 +591,7 @@ class Designer extends Events {
} }
// Current mouse position .... // Current mouse position ....
const screen = this._workspace.getScreenManager(); const screen = this._canvas.getScreenManager();
const pos = screen.getWorkspaceMousePosition(event); const pos = screen.getWorkspaceMousePosition(event);
// create a connection ... // create a connection ...
@ -605,7 +606,7 @@ class Designer extends Events {
loadMap(mindmap: Mindmap): Promise<void> { loadMap(mindmap: Mindmap): Promise<void> {
this._mindmap = mindmap; this._mindmap = mindmap;
this._workspace.enableQueueRender(true); this._canvas.enableQueueRender(true);
// Init layout manager ... // Init layout manager ...
const size = { width: 25, height: 25 }; const size = { width: 25, height: 25 };
@ -633,7 +634,7 @@ class Designer extends Events {
const centralTopic = this.getModel().getCentralTopic(); const centralTopic = this.getModel().getCentralTopic();
this.goToNode(centralTopic); this.goToNode(centralTopic);
return this._workspace.enableQueueRender(false).then(() => { return this._canvas.enableQueueRender(false).then(() => {
// Connect relationships ... // Connect relationships ...
const relationships = mindmap.getRelationships(); const relationships = mindmap.getRelationships();
relationships.forEach((relationship) => this._relationshipModelToRelationship(relationship)); relationships.forEach((relationship) => this._relationshipModelToRelationship(relationship));
@ -642,7 +643,7 @@ class Designer extends Events {
nodesGraph.forEach((topic) => topic.setVisibility(true)); nodesGraph.forEach((topic) => topic.setVisibility(true));
// Enable workspace drag events ... // Enable workspace drag events ...
this._workspace.registerEvents(); this._canvas.registerEvents();
// Finally, sort the map ... // Finally, sort the map ...
EventBus.instance.fireEvent('forceLayout'); EventBus.instance.fireEvent('forceLayout');
this.fireEvent('loadSuccess'); this.fireEvent('loadSuccess');
@ -666,14 +667,13 @@ class Designer extends Events {
} }
nodeModelToTopic(nodeModel: NodeModel): Topic { nodeModelToTopic(nodeModel: NodeModel): Topic {
$assert(nodeModel, 'Node model can not be null');
let children = nodeModel.getChildren().slice(); let children = nodeModel.getChildren().slice();
children = children.sort((a, b) => a.getOrder()! - b.getOrder()!); children = children.sort((a, b) => a.getOrder()! - b.getOrder()!);
const result = this._buildNodeGraph(nodeModel, this.isReadOnly()); const result = this._buildNodeGraph(nodeModel, this.isReadOnly());
result.setVisibility(false); result.setVisibility(false);
this._workspace.append(result); this._canvas.append(result);
children.forEach((child) => { children.forEach((child) => {
if (child) { if (child) {
this.nodeModelToTopic(child); this.nodeModelToTopic(child);
@ -682,6 +682,13 @@ class Designer extends Events {
return result; return result;
} }
changeTheme(theme: ThemeType): void {
console.log(`theme:${theme}`);
this.getMindmap().setTheme(theme);
const centralTopic = this.getModel().getCentralTopic();
centralTopic.redraw(true);
}
/** /**
* @private * @private
* @param {mindplot.model.RelationshipModel} model * @param {mindplot.model.RelationshipModel} model
@ -701,7 +708,7 @@ class Designer extends Events {
result.setVisibility(sourceTopic.isVisible() && targetTopic.isVisible()); result.setVisibility(sourceTopic.isVisible() && targetTopic.isVisible());
this._workspace.append(result); this._canvas.append(result);
return result; return result;
} }
@ -723,7 +730,7 @@ class Designer extends Events {
targetTopic.deleteRelationship(rel); targetTopic.deleteRelationship(rel);
this.getModel().removeRelationship(rel); this.getModel().removeRelationship(rel);
this._workspace.removeChild(rel); this._canvas.removeChild(rel);
const mindmap = this.getMindmap(); const mindmap = this.getMindmap();
mindmap.deleteRelationship(rel.getModel()); mindmap.deleteRelationship(rel.getModel());
@ -771,14 +778,14 @@ class Designer extends Events {
removeTopic(node: Topic): void { removeTopic(node: Topic): void {
if (!node.isCentralTopic()) { if (!node.isCentralTopic()) {
const parent = node.getParent(); const parent = node.getParent();
node.disconnect(this._workspace); node.disconnect(this._canvas);
// remove children // remove children
while (node.getChildren().length > 0) { while (node.getChildren().length > 0) {
this.removeTopic(node.getChildren()[0]); this.removeTopic(node.getChildren()[0]);
} }
this._workspace.removeChild(node); this._canvas.removeChild(node);
this.getModel().removeTopic(node); this.getModel().removeTopic(node);
// Delete this node from the model... // Delete this node from the model...
@ -792,7 +799,7 @@ class Designer extends Events {
} }
private _resetEdition() { private _resetEdition() {
const screenManager = this._workspace.getScreenManager(); const screenManager = this._canvas.getScreenManager();
screenManager.fireEvent('update'); screenManager.fireEvent('update');
screenManager.fireEvent('mouseup'); screenManager.fireEvent('mouseup');
this._relPivot.dispose(); this._relPivot.dispose();
@ -946,7 +953,7 @@ class Designer extends Events {
} }
getWorkSpace(): Canvas { getWorkSpace(): Canvas {
return this._workspace; return this._canvas;
} }
public get cleanScreen(): () => void { public get cleanScreen(): () => void {

View File

@ -64,12 +64,12 @@ class MainTopic extends Topic {
return group; return group;
} }
updateTopicShape(_targetTopic: Topic) { updateTopicShape() {
this.redrawShapeType(); this.redrawShapeType();
} }
disconnect(workspace: Canvas) { disconnect(canvas: Canvas) {
super.disconnect(workspace); super.disconnect(canvas);
const innerShape = this.getInnerShape(); const innerShape = this.getInnerShape();
innerShape.setVisibility(true); innerShape.setVisibility(true);

View File

@ -50,6 +50,8 @@ const ICON_SCALING_FACTOR = 1.3;
abstract class Topic extends NodeGraph { abstract class Topic extends NodeGraph {
private _innerShape: LineTopicShape | Rect | LineTopicShape | null; private _innerShape: LineTopicShape | Rect | LineTopicShape | null;
private _innerShapeType: TopicShapeType | undefined;
private _relationships: Relationship[]; private _relationships: Relationship[];
private _isInWorkspace: boolean; private _isInWorkspace: boolean;
@ -109,7 +111,6 @@ abstract class Topic extends NodeGraph {
const model = this.getModel(); const model = this.getModel();
model.setShapeType(type); model.setShapeType(type);
this.redrawShapeType();
this.redraw(); this.redraw();
} }
@ -212,6 +213,7 @@ abstract class Topic extends NodeGraph {
throw new Error(exhaustiveCheck); throw new Error(exhaustiveCheck);
} }
} }
this._innerShapeType = shapeType;
result.setPosition(0, 0); result.setPosition(0, 0);
return result; return result;
} }
@ -970,7 +972,6 @@ abstract class Topic extends NodeGraph {
// Remove from workspace. // Remove from workspace.
EventBus.instance.fireEvent('topicDisconect', this.getModel()); EventBus.instance.fireEvent('topicDisconect', this.getModel());
this.redrawShapeType();
this.redraw(true); this.redraw(true);
} }
} }
@ -1133,6 +1134,13 @@ abstract class Topic extends NodeGraph {
if (this._isInWorkspace) { if (this._isInWorkspace) {
const theme = ThemeFactory.create(this.getModel()); const theme = ThemeFactory.create(this.getModel());
const textShape = this.getOrBuildTextShape(); const textShape = this.getOrBuildTextShape();
// Needs to update inner shape ...
const shapeType = this.getShapeType();
if (shapeType !== this._innerShapeType) {
this.redrawShapeType();
}
// Update font ... // Update font ...
const fontColor = this.getFontColor(); const fontColor = this.getFontColor();
textShape.setColor(fontColor); textShape.setColor(fontColor);

View File

@ -114,7 +114,6 @@ class OriginalLayout {
const parentX = parentPosition.x; const parentX = parentPosition.x;
const parentY = parentPosition.y; const parentY = parentPosition.y;
console.log(`${parent?.getId()}:${offset.x}`);
const newPos = { const newPos = {
x: parentX + offset.x, x: parentX + offset.x,
y: parentY + offset.y + this.calculateAlignOffset(node, child, heightById), y: parentY + offset.y + this.calculateAlignOffset(node, child, heightById),

View File

@ -49,6 +49,10 @@ class Mindmap extends IMindmap {
return this._theme ? this._theme : 'classic'; return this._theme ? this._theme : 'classic';
} }
setTheme(value: ThemeType): void {
this._theme = value;
}
/** */ /** */
getDescription(): string { getDescription(): string {
return this._description; return this._description;

View File

@ -28,6 +28,7 @@ import { LineType } from '../ConnectionLine';
import { FontWeightType } from '../FontWeightType'; import { FontWeightType } from '../FontWeightType';
import { FontStyleType } from '../FontStyleType'; import { FontStyleType } from '../FontStyleType';
import { TopicShapeType } from '../model/INodeModel'; import { TopicShapeType } from '../model/INodeModel';
import ThemeType from '../model/ThemeType';
class XMLSerializerTango implements XMLMindmapSerializer { class XMLSerializerTango implements XMLMindmapSerializer {
private static MAP_ROOT_NODE = 'map'; private static MAP_ROOT_NODE = 'map';
@ -49,6 +50,13 @@ class XMLSerializerTango implements XMLMindmapSerializer {
if (name) { if (name) {
mapElem.setAttribute('name', this._rmXmlInv(name)); mapElem.setAttribute('name', this._rmXmlInv(name));
} }
// Add theme ...
const theme = mindmap.getTheme();
if (theme && theme !== 'classic') {
mapElem.setAttribute('theme', theme);
}
const version = mindmap.getVersion(); const version = mindmap.getVersion();
if ($defined(version)) { if ($defined(version)) {
mapElem.setAttribute('version', version); mapElem.setAttribute('version', version);
@ -257,6 +265,11 @@ class XMLSerializerTango implements XMLMindmapSerializer {
const version = rootElem.getAttribute('version') || 'pela'; const version = rootElem.getAttribute('version') || 'pela';
const mindmap = new Mindmap(mapId, version); const mindmap = new Mindmap(mapId, version);
const theme = rootElem.getAttribute('theme');
if (theme) {
mindmap.setTheme(theme as ThemeType);
}
// Add all the topics nodes ... // Add all the topics nodes ...
const childNodes = Array.from(rootElem.childNodes); const childNodes = Array.from(rootElem.childNodes);
const topicsNodes = childNodes const topicsNodes = childNodes

View File

@ -1,3 +1,4 @@
/* eslint-disable func-call-spacing */
/* /*
* Copyright [2021] [wisemapping] * Copyright [2021] [wisemapping]
* *
@ -44,6 +45,7 @@ export type TopicStyleType = {
type StyleType = string | string[] | number | undefined | LineType; type StyleType = string | string[] | number | undefined | LineType;
// eslint-disable-next-line no-spaced-func
const keyToModel = new Map<keyof TopicStyleType, (model: NodeModel) => StyleType>([ const keyToModel = new Map<keyof TopicStyleType, (model: NodeModel) => StyleType>([
['borderColor', (m: NodeModel) => m.getBorderColor()], ['borderColor', (m: NodeModel) => m.getBorderColor()],
['backgroundColor', (m: NodeModel) => m.getBackgroundColor()], ['backgroundColor', (m: NodeModel) => m.getBackgroundColor()],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 78 KiB