Storybook.

This commit is contained in:
Paulo Gustavo Veiga 2023-01-30 18:38:19 -08:00
parent b806e58e55
commit 51c442d122
22 changed files with 1384 additions and 1115 deletions

View File

@ -0,0 +1,21 @@
module.exports = {
"stories": [
"../storybook/src/**/*.stories.mdx",
"../storybook/src/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions"
],
"framework": "@storybook/html",
typescript: {
check: false,
checkOptions: {},
reactDocgen: 'react-docgen-typescript',
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
},
},
};

View File

@ -0,0 +1,9 @@
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}

View File

@ -29,7 +29,9 @@
"cy:open": "cypress open",
"test:unit": "jest ./test/unit/export/*.ts ./test/unit/import/*.ts ./test/unit/layout/*.js --verbose --silent --detectOpenHandles",
"test:integration": "start-server-and-test playground http-get://localhost:8083 cy:run",
"test": "yarn test:unit && yarn test:integration"
"test": "yarn test:unit && yarn test:integration",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"dependencies": {
"@types/jquery": "^3.5.11",
@ -41,6 +43,18 @@
"xml-formatter": "^2.6.1"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@mdx-js/react": "^1.6.22",
"@storybook/addon-actions": "^6.5.16",
"@storybook/addon-docs": "^6.5.16",
"@storybook/addon-essentials": "^6.5.16",
"@storybook/addon-interactions": "^6.5.16",
"@storybook/addon-links": "^6.5.16",
"@storybook/builder-webpack4": "^6.5.16",
"@storybook/html": "^6.5.16",
"@storybook/manager-webpack4": "^6.5.16",
"@storybook/testing-library": "^0.0.13",
"babel-loader": "^8.3.0",
"blob-polyfill": "^6.0.20211015",
"cypress": "^12.3.0",
"cypress-image-snapshot": "^4.0.1",

View File

@ -15,6 +15,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import $ from 'jquery';
import { $assert, $defined } from '@wisemapping/core-js';
import Point from '@wisemapping/web2d';
import Messages, { $msg } from './Messages';
@ -78,22 +80,19 @@ class Designer extends Events {
private _cleanScreen!: () => void;
constructor(options: DesignerOptions, divElement: JQuery) {
constructor(options: DesignerOptions) {
super();
$assert(options, 'options must be defined');
$assert(options.zoom, 'zoom must be defined');
$assert(divElement, 'divElement must be defined');
// Set up i18n location ...
console.log(`Editor location: ${options.locale}`);
Messages.init(options.locale ? options.locale : 'en');
const divElem = options.divContainer;
this._options = options;
// Set full div elem render area.The component must fill container size
// container is responsible for location and size
divElement.css('width', '100%');
divElement.css('height', '100%');
$(divElem).css('width', '100%');
$(divElem).css('height', '100%');
// Dispatcher manager ...
const commandContext = new CommandContext(this);
@ -108,7 +107,7 @@ class Designer extends Events {
this._model = new DesignerModel(options);
// Init Screen manager..
const screenManager = new ScreenManager(divElement);
const screenManager = new ScreenManager(divElem);
this._workspace = new Workspace(screenManager, this._model.getZoom(), this.isReadOnly());
// Init layout manager ...

View File

@ -16,7 +16,6 @@
* limitations under the License.
*/
import { $assert } from '@wisemapping/core-js';
import $ from 'jquery';
import PersistenceManager from './PersistenceManager';
import Designer from './Designer';
import { DesignerOptions } from './DesignerOptionsBuilder';
@ -26,14 +25,14 @@ import ReadOnlyWidgetManager from './ReadOnlyWidgetManager';
let designer: Designer;
export function buildDesigner(options: DesignerOptions): Designer {
const divContainer = options.divContainer ? $(options.divContainer) : $(`#${options.container}`);
$assert(divContainer, 'container could not be null');
const containerElem = options.divContainer;
$assert(containerElem, 'container could not be null');
if (designer) {
throw new Error('Designer can does not support multiple initializations');
}
// Register load events ...
designer = new Designer(options, divContainer);
designer = new Designer(options);
// Configure default persistence manager ...
const persistence = options.persistenceManager;

View File

@ -25,7 +25,6 @@ export type DesignerOptions = {
mode: EditorRenderMode;
mapId?: string;
divContainer: HTMLElement;
container: string;
persistenceManager?: PersistenceManager;
widgetManager?: WidgetManager;
saveOnLoad?: boolean;
@ -40,7 +39,6 @@ class OptionsBuilder {
mode: 'edition-owner',
zoom: 0.85,
saveOnLoad: true,
container: 'mindplot',
locale: 'en',
};

View File

@ -45,12 +45,14 @@ class LinkIcon extends ImageIcon {
private _registerEvents() {
this.getElement().setCursor('pointer');
if (WidgetManager.isInitialized()) {
const manager = WidgetManager.getInstance();
manager.createTooltipForLink(this._topic, this._linksModel as LinkModel, this);
if (!this._readOnly) {
manager.configureEditorForLink(this._topic, this._linksModel as LinkModel, this);
}
}
}
getModel(): FeatureModel {
return this._linksModel;

View File

@ -28,13 +28,6 @@ import { $msg } from './Messages';
import DesignerKeyboard from './DesignerKeyboard';
import LocalStorageManager from './LocalStorageManager';
export type MindplotWebComponentInterface = {
id: string;
mode: string;
ref: object;
locale?: string;
zoom?: number;
};
/**
* 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)
@ -59,7 +52,7 @@ class MindplotWebComponent extends HTMLElement {
const wrapper = document.createElement('div');
wrapper.setAttribute('class', 'wise-editor');
wrapper.setAttribute('id', 'mindplot');
wrapper.setAttribute('id', 'mindplot-div-container');
this._shadowRoot.appendChild(wrapper);
this._isLoaded = false;
@ -87,7 +80,7 @@ class MindplotWebComponent extends HTMLElement {
const persistenceManager = persistence || new LocalStorageManager('map.xml', false, false);
const mode = editorRenderMode || 'viewonly';
const mindplodElem = this._shadowRoot.getElementById('mindplot');
const mindplodElem = this._shadowRoot.getElementById('mindplot-div-container');
$assert(mindplodElem, 'Root mindplot element could not be loaded');
const options = DesignerOptionsBuilder.buildOptions({
@ -95,7 +88,6 @@ class MindplotWebComponent extends HTMLElement {
mode,
widgetManager,
divContainer: mindplodElem!,
container: 'mindplot',
zoom: zoom ? Number.parseFloat(zoom) : 1,
locale: locale || 'en',
});

View File

@ -0,0 +1,9 @@
type MindplotWebComponentInterface = {
id: string;
mode: string;
ref: object;
locale?: string;
zoom?: number;
};
export default MindplotWebComponentInterface;

View File

@ -44,12 +44,14 @@ class NoteIcon extends ImageIcon {
private _registerEvents(): void {
this.getElement().setCursor('pointer');
if (WidgetManager.isInitialized()) {
const manager = WidgetManager.getInstance();
manager.createTooltipForNote(this._topic, this._linksModel as NoteModel, this);
if (!this._readOnly) {
manager.configureEditorForNote(this._topic, this._linksModel as NoteModel, this);
}
}
}
getModel(): FeatureModel {
return this._linksModel;

View File

@ -26,7 +26,7 @@ import registerTouchHandler from '../../../../libraries/jquery.touchevent';
registerTouchHandler($);
class ScreenManager {
private _divContainer: JQuery;
private _divContainer: JQuery<HTMLDivElement>;
private _padding: { x: number; y: number };
@ -34,9 +34,9 @@ class ScreenManager {
private _scale: number;
constructor(divElement: JQuery) {
constructor(divElement: HTMLElement) {
$assert(divElement, 'can not be null');
this._divContainer = divElement;
this._divContainer = $(divElement) as JQuery<HTMLDivElement>;
this._padding = { x: 0, y: 0 };
// Ignore default click event propagation. Prevent 'click' event on drag.
@ -56,12 +56,12 @@ class ScreenManager {
}
/**
* Return the current visibile area in the browser.
* Return the current visible area in the browser.
*/
getVisibleBrowserSize(): { width: number; height: number } {
return {
width: window.innerWidth,
height: window.innerHeight - Number.parseInt(this._divContainer.css('top'), 10),
height: window.innerHeight,
};
}

View File

@ -1206,7 +1206,7 @@ abstract class Topic extends NodeGraph {
textShape.setFontName(fontFamily);
const text = this.getText();
textShape.setText(text);
textShape.setText(text.trim());
// Calculate topic size and adjust elements ...
const textWidth = textShape.getWidth();

View File

@ -20,7 +20,7 @@ const CONNECTOR_WIDTH = 6;
const OUTER_SHAPE_ATTRIBUTES = {
fillColor: 'rgb(252,235,192)',
stroke: '1 dot rgb(241,163,39)',
stroke: '1 solid rgb(241,163,39)',
x: 0,
y: 0,
};

View File

@ -15,9 +15,16 @@ abstract class WidgetManager {
};
static getInstance(): WidgetManager {
if (!this._instance) {
throw new Error('WidgetManager has not been initialized');
}
return this._instance;
}
static isInitialized() {
return this._instance !== undefined;
}
private createTooltip(
mindmapElement,
title: string,

View File

@ -140,7 +140,7 @@ abstract class INodeModel {
return this.getProperty('shapeType') as TopicShapeType;
}
setShapeType(type: string) {
setShapeType(type: TopicShapeType | undefined) {
this.putProperty('shapeType', type);
}
@ -156,7 +156,7 @@ abstract class INodeModel {
return this.getProperty('order') as number;
}
setFontFamily(fontFamily: string): void {
setFontFamily(fontFamily: string | undefined): void {
this.putProperty('fontFamily', fontFamily);
}
@ -188,7 +188,7 @@ abstract class INodeModel {
return this.getProperty('fontColor') as string;
}
setFontSize(size: number): void {
setFontSize(size: number | undefined): void {
this.putProperty('fontSize', size);
}

View File

@ -27,6 +27,7 @@ import emojiToIconMap from './iconToEmoji.json';
import { LineType } from '../ConnectionLine';
import { FontWeightType } from '../FontWeightType';
import { FontStyleType } from '../FontStyleType';
import { TopicShapeType } from '../model/INodeModel';
class XMLSerializerTango implements XMLMindmapSerializer {
private static MAP_ROOT_NODE = 'map';
@ -335,7 +336,7 @@ class XMLSerializerTango implements XMLMindmapSerializer {
if (shape) {
// Fix typo on serialization....
shape = shape.replace('rectagle', 'rectangle');
topic.setShapeType(shape);
topic.setShapeType(shape as TopicShapeType);
// Is an image ?
const image = domElem.getAttribute('image');

View File

@ -30,14 +30,13 @@ import TextImporterFactory from './components/import/TextImporterFactory';
import Exporter from './components/export/Exporter';
import Importer from './components/import/Importer';
import DesignerKeyboard from './components/DesignerKeyboard';
import EditorRenderMode from './components/EditorRenderMode';
import type EditorRenderMode from './components/EditorRenderMode';
import DesignerModel from './components/DesignerModel';
import SvgImageIcon from './components/SvgImageIcon';
import MindplotWebComponent, {
MindplotWebComponentInterface,
} from './components/MindplotWebComponent';
import MindplotWebComponent from './components/MindplotWebComponent';
import type MindplotWebComponentInterface from './components/MindplotWebComponentInterface';
import LinkIcon from './components/LinkIcon';
import NoteIcon from './components/NoteIcon';
import Topic from './components/Topic';

View File

@ -20,8 +20,6 @@ import PersistenceManager from './components/PersistenceManager';
import LocalStorageManager from './components/LocalStorageManager';
import MindplotWebComponent from './components/MindplotWebComponent';
console.log('loading static mindmap in read-only');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const globalAny: any = global;
globalAny.jQuery = jquery;

View File

@ -0,0 +1,78 @@
import createTopic from './Topic';
// More on default export: https://storybook.js.org/docs/html/writing-stories/introduction#default-export
export default {
title: 'Mindplot/Topic',
// More on argTypes: https://storybook.js.org/docs/html/api/argtypes
argTypes: {
backgroundColor: { control: 'color' },
borderColor: { control: 'color' },
fontFamily: {
options: ['Arial', 'Verdana'],
control: { type: 'select' },
},
fontSize: { control: { type: 'number', min: 0, max: 20, step: 2 } },
fontColor: { control: 'color' },
shapeType: {
options: ['rectangle', 'rounded rectangle', 'elipse', 'line'],
control: { type: 'select' },
},
text: { control: 'text' },
noteText: { control: 'text' },
linkText: { control: 'text' },
eicon: { control: 'multi-select', options: ['❤️', '🌈', '🖇️'] },
},
};
// More on component templates: https://storybook.js.org/docs/html/writing-stories/introduction#using-args
const Template = ({ ...args }) => createTopic({ ...args });
export const BoderderStyle = Template.bind({});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
BoderderStyle.args = {
text: 'Border Style',
borderColor: 'red',
};
export const FontStyle = Template.bind({});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
FontStyle.args = {
text: 'Font Style',
fontColor: 'red',
fontSize: 10,
fontFamily: 'Fantasy',
};
export const BackgroundColor = Template.bind({});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
BackgroundColor.args = {
text: 'Background Color Style',
backgroundColor: 'red',
};
export const NoteFeature = Template.bind({});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
NoteFeature.args = {
text: 'Note Feature',
noteText: 'This is great note\nwith two lines',
};
export const LinkFeature = Template.bind({});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
LinkFeature.args = {
text: 'Link Feature',
linkText: 'https://www.google.com/',
};
export const IconFeature = Template.bind({});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
IconFeature.args = {
text: 'EIcon Feature\n with multi-line',
eicon: ['❤️', '🌈', '🖇️'],
};

View File

@ -0,0 +1,87 @@
import $ from 'jquery';
import { LinkModel, Mindmap, NoteModel, Topic } from '../../../src';
import NodeModel from '../../../src/components/model/NodeModel';
import CentralTopic from '../../../src/components/CentralTopic';
import Workspace from '../../../src/components/Workspace';
import ScreenManager from '../../../src/components/ScreenManager';
import EmojiIconModel from '../../../src/components/model/EmojiIconModel';
const registerRefreshHook = (topic: Topic) => {
// Trigger a redraw after the node is added ...
if (globalThis.observer) {
globalThis.observer.disconnect();
}
globalThis.observer = new MutationObserver(() => {
topic.redraw();
console.log('Refresh triggered...');
});
globalThis.observer.observe(document.getElementById('root')!, { childList: true });
};
const createTopic = ({
backgroundColor = undefined,
text = undefined,
borderColor = undefined,
shapeType = undefined,
fontFamily = undefined,
fontSize = undefined,
fontColor = undefined,
noteText = undefined,
linkText = undefined,
eicon = undefined,
}) => {
// Build basic container ...
const divElem = document.createElement('div');
const jqueryDiv = $(divElem);
jqueryDiv.css({
height: '600px',
width: '800px',
backgroundColor: 'gray',
});
// Initialize designer helpers ...
const screenManager = new ScreenManager(divElem);
const workspace = new Workspace(screenManager, 0.3, true);
// Update model ...
const mindmap = new Mindmap();
const model = new NodeModel('CentralTopic', mindmap);
model.setText(text);
model.setBackgroundColor(backgroundColor);
model.setBorderColor(borderColor);
model.setShapeType(shapeType);
model.setFontColor(fontColor);
model.setFontFamily(fontFamily);
model.setFontSize(fontSize);
if (noteText) {
const note = new NoteModel({ text: noteText });
model.addFeature(note);
}
if (linkText) {
const note = new LinkModel({ url: linkText });
model.addFeature(note);
}
if (eicon) {
(eicon as string[]).forEach((icon) => {
const emodel = new EmojiIconModel({ id: icon });
model.addFeature(emodel);
});
}
// Create topic UI element ...
mindmap.addBranch(model);
const centralTopic = new CentralTopic(model, { readOnly: true });
workspace.append(centralTopic);
// Register refresh hook ..
registerRefreshHook(centralTopic);
return divElem;
};
export default createTopic;

View File

@ -23,7 +23,7 @@
]
},
"include": [
"src/**/*",
"src/**/*", "storybook/src/stories",
],
"exclude": [
"node_modules"

2090
yarn.lock

File diff suppressed because it is too large Load Diff