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", "cy:open": "cypress open",
"test:unit": "jest ./test/unit/export/*.ts ./test/unit/import/*.ts ./test/unit/layout/*.js --verbose --silent --detectOpenHandles", "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: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": { "dependencies": {
"@types/jquery": "^3.5.11", "@types/jquery": "^3.5.11",
@ -41,6 +43,18 @@
"xml-formatter": "^2.6.1" "xml-formatter": "^2.6.1"
}, },
"devDependencies": { "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", "blob-polyfill": "^6.0.20211015",
"cypress": "^12.3.0", "cypress": "^12.3.0",
"cypress-image-snapshot": "^4.0.1", "cypress-image-snapshot": "^4.0.1",

View File

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

View File

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

View File

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

View File

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

View File

@ -28,13 +28,6 @@ import { $msg } from './Messages';
import DesignerKeyboard from './DesignerKeyboard'; import DesignerKeyboard from './DesignerKeyboard';
import LocalStorageManager from './LocalStorageManager'; import LocalStorageManager from './LocalStorageManager';
export type MindplotWebComponentInterface = {
id: string;
mode: string;
ref: object;
locale?: string;
zoom?: number;
};
/** /**
* WebComponent implementation for minplot designer. * 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) * 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'); const wrapper = document.createElement('div');
wrapper.setAttribute('class', 'wise-editor'); wrapper.setAttribute('class', 'wise-editor');
wrapper.setAttribute('id', 'mindplot'); wrapper.setAttribute('id', 'mindplot-div-container');
this._shadowRoot.appendChild(wrapper); this._shadowRoot.appendChild(wrapper);
this._isLoaded = false; this._isLoaded = false;
@ -87,7 +80,7 @@ class MindplotWebComponent extends HTMLElement {
const persistenceManager = persistence || new LocalStorageManager('map.xml', false, false); const persistenceManager = persistence || new LocalStorageManager('map.xml', false, false);
const mode = editorRenderMode || 'viewonly'; 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'); $assert(mindplodElem, 'Root mindplot element could not be loaded');
const options = DesignerOptionsBuilder.buildOptions({ const options = DesignerOptionsBuilder.buildOptions({
@ -95,7 +88,6 @@ class MindplotWebComponent extends HTMLElement {
mode, mode,
widgetManager, widgetManager,
divContainer: mindplodElem!, divContainer: mindplodElem!,
container: 'mindplot',
zoom: zoom ? Number.parseFloat(zoom) : 1, zoom: zoom ? Number.parseFloat(zoom) : 1,
locale: locale || 'en', 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 { private _registerEvents(): void {
this.getElement().setCursor('pointer'); this.getElement().setCursor('pointer');
if (WidgetManager.isInitialized()) {
const manager = WidgetManager.getInstance(); const manager = WidgetManager.getInstance();
manager.createTooltipForNote(this._topic, this._linksModel as NoteModel, this); manager.createTooltipForNote(this._topic, this._linksModel as NoteModel, this);
if (!this._readOnly) { if (!this._readOnly) {
manager.configureEditorForNote(this._topic, this._linksModel as NoteModel, this); manager.configureEditorForNote(this._topic, this._linksModel as NoteModel, this);
} }
} }
}
getModel(): FeatureModel { getModel(): FeatureModel {
return this._linksModel; return this._linksModel;

View File

@ -26,7 +26,7 @@ import registerTouchHandler from '../../../../libraries/jquery.touchevent';
registerTouchHandler($); registerTouchHandler($);
class ScreenManager { class ScreenManager {
private _divContainer: JQuery; private _divContainer: JQuery<HTMLDivElement>;
private _padding: { x: number; y: number }; private _padding: { x: number; y: number };
@ -34,9 +34,9 @@ class ScreenManager {
private _scale: number; private _scale: number;
constructor(divElement: JQuery) { constructor(divElement: HTMLElement) {
$assert(divElement, 'can not be null'); $assert(divElement, 'can not be null');
this._divContainer = divElement; this._divContainer = $(divElement) as JQuery<HTMLDivElement>;
this._padding = { x: 0, y: 0 }; this._padding = { x: 0, y: 0 };
// Ignore default click event propagation. Prevent 'click' event on drag. // 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 } { getVisibleBrowserSize(): { width: number; height: number } {
return { return {
width: window.innerWidth, 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); textShape.setFontName(fontFamily);
const text = this.getText(); const text = this.getText();
textShape.setText(text); textShape.setText(text.trim());
// Calculate topic size and adjust elements ... // Calculate topic size and adjust elements ...
const textWidth = textShape.getWidth(); const textWidth = textShape.getWidth();

View File

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

View File

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

View File

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

View File

@ -27,6 +27,7 @@ import emojiToIconMap from './iconToEmoji.json';
import { LineType } from '../ConnectionLine'; 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';
class XMLSerializerTango implements XMLMindmapSerializer { class XMLSerializerTango implements XMLMindmapSerializer {
private static MAP_ROOT_NODE = 'map'; private static MAP_ROOT_NODE = 'map';
@ -335,7 +336,7 @@ class XMLSerializerTango implements XMLMindmapSerializer {
if (shape) { if (shape) {
// Fix typo on serialization.... // Fix typo on serialization....
shape = shape.replace('rectagle', 'rectangle'); shape = shape.replace('rectagle', 'rectangle');
topic.setShapeType(shape); topic.setShapeType(shape as TopicShapeType);
// Is an image ? // Is an image ?
const image = domElem.getAttribute('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 Exporter from './components/export/Exporter';
import Importer from './components/import/Importer'; import Importer from './components/import/Importer';
import DesignerKeyboard from './components/DesignerKeyboard'; import DesignerKeyboard from './components/DesignerKeyboard';
import EditorRenderMode from './components/EditorRenderMode'; import type EditorRenderMode from './components/EditorRenderMode';
import DesignerModel from './components/DesignerModel'; import DesignerModel from './components/DesignerModel';
import SvgImageIcon from './components/SvgImageIcon'; import SvgImageIcon from './components/SvgImageIcon';
import MindplotWebComponent, { import MindplotWebComponent from './components/MindplotWebComponent';
MindplotWebComponentInterface, import type MindplotWebComponentInterface from './components/MindplotWebComponentInterface';
} from './components/MindplotWebComponent';
import LinkIcon from './components/LinkIcon'; import LinkIcon from './components/LinkIcon';
import NoteIcon from './components/NoteIcon'; import NoteIcon from './components/NoteIcon';
import Topic from './components/Topic'; import Topic from './components/Topic';

View File

@ -20,8 +20,6 @@ import PersistenceManager from './components/PersistenceManager';
import LocalStorageManager from './components/LocalStorageManager'; import LocalStorageManager from './components/LocalStorageManager';
import MindplotWebComponent from './components/MindplotWebComponent'; import MindplotWebComponent from './components/MindplotWebComponent';
console.log('loading static mindmap in read-only');
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const globalAny: any = global; const globalAny: any = global;
globalAny.jQuery = jquery; 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": [ "include": [
"src/**/*", "src/**/*", "storybook/src/stories",
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"

2090
yarn.lock

File diff suppressed because it is too large Load Diff