mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-12-25 12:53:48 +01:00
Merge branch 'develop'
This commit is contained in:
commit
e1f71612ba
@ -37,7 +37,7 @@ declare global {
|
||||
}
|
||||
|
||||
export type EditorPropsType = {
|
||||
initCallback?: () => void;
|
||||
initCallback?: (locale: string) => void;
|
||||
mapId?: number;
|
||||
isTryMode: boolean;
|
||||
readOnlyMode: boolean;
|
||||
@ -60,7 +60,7 @@ const loadLocaleData = (locale: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const initMindplot = () => {
|
||||
const initMindplot = (locale: string) => {
|
||||
// Change page title ...
|
||||
document.title = `${global.mapTitle} | WiseMapping `;
|
||||
|
||||
@ -95,7 +95,7 @@ const initMindplot = () => {
|
||||
(global.userOptions?.zoom != undefined
|
||||
? Number.parseFloat(global.userOptions.zoom as string)
|
||||
: 0.8),
|
||||
locale: global.locale,
|
||||
locale: locale,
|
||||
});
|
||||
|
||||
// Build designer ...
|
||||
@ -119,11 +119,11 @@ const Editor = ({
|
||||
onAction,
|
||||
}: EditorPropsType): React.ReactElement => {
|
||||
React.useEffect(() => {
|
||||
initCallback();
|
||||
initCallback(locale);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} defaultLocale="en" messages={loadLocaleData(locale)}>
|
||||
<IntlProvider locale={locale} messages={loadLocaleData(locale)}>
|
||||
<Toolbar
|
||||
isTryMode={isTryMode}
|
||||
onAction={onAction}
|
||||
|
@ -21,6 +21,7 @@ import { $assert } from '@wisemapping/core-js';
|
||||
import Point from '@wisemapping/web2d';
|
||||
import { Mindmap } from '..';
|
||||
import CommandContext from './CommandContext';
|
||||
import ControlPoint from './ControlPoint';
|
||||
import Events from './Events';
|
||||
import NodeModel from './model/NodeModel';
|
||||
import RelationshipModel from './model/RelationshipModel';
|
||||
@ -44,7 +45,7 @@ abstract class ActionDispatcher extends Events {
|
||||
|
||||
abstract moveTopic(topicId: number, position: Point): void;
|
||||
|
||||
abstract moveControlPoint(ctrlPoint: this, point: Point): void;
|
||||
abstract moveControlPoint(ctrlPoint: ControlPoint, point: Point): void;
|
||||
|
||||
abstract changeFontFamilyToTopic(topicIds: number[], fontFamily: string): void;
|
||||
|
||||
|
@ -35,7 +35,6 @@ class CentralTopic extends Topic {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
workoutIncomingConnectionPoint(): Point {
|
||||
return this.getPosition();
|
||||
}
|
||||
|
@ -23,15 +23,18 @@ abstract class Command {
|
||||
|
||||
static _uuid: number;
|
||||
|
||||
private _discardDuplicated: string;
|
||||
|
||||
constructor() {
|
||||
this._id = Command._nextUUID();
|
||||
this._discardDuplicated = undefined;
|
||||
}
|
||||
|
||||
abstract execute(commandContext:CommandContext):void;
|
||||
abstract execute(commandContext: CommandContext): void;
|
||||
|
||||
abstract undoExecute(commandContext:CommandContext):void;
|
||||
abstract undoExecute(commandContext: CommandContext): void;
|
||||
|
||||
getId():number {
|
||||
getId(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
@ -42,6 +45,14 @@ abstract class Command {
|
||||
this._uuid += 1;
|
||||
return this._uuid;
|
||||
}
|
||||
|
||||
get discardDuplicated(): string {
|
||||
return this._discardDuplicated;
|
||||
}
|
||||
|
||||
set discardDuplicated(value: string) {
|
||||
this._discardDuplicated = value;
|
||||
}
|
||||
}
|
||||
|
||||
export default Command;
|
||||
|
@ -20,8 +20,33 @@ import { $defined } from '@wisemapping/core-js';
|
||||
|
||||
import Shape from './util/Shape';
|
||||
import ActionDispatcher from './ActionDispatcher';
|
||||
import Workspace from './Workspace';
|
||||
|
||||
class ControlPoint {
|
||||
private control1: Elipse;
|
||||
|
||||
private control2: Elipse;
|
||||
|
||||
private _controlPointsController: Elipse[];
|
||||
|
||||
private _controlLines: Line[];
|
||||
|
||||
private _isBinded: boolean;
|
||||
|
||||
_line: Line;
|
||||
|
||||
private _workspace: Workspace;
|
||||
|
||||
private _endPoint: any[];
|
||||
|
||||
private _orignalCtrlPoint: any;
|
||||
|
||||
private _controls: any;
|
||||
|
||||
private _mouseMoveFunction: (e: Event) => void;
|
||||
|
||||
private _mouseUpFunction: (e: Event) => void;
|
||||
|
||||
constructor() {
|
||||
this.control1 = new Elipse({
|
||||
width: 6,
|
||||
@ -70,7 +95,7 @@ class ControlPoint {
|
||||
});
|
||||
}
|
||||
|
||||
setLine(line) {
|
||||
setLine(line: Line) {
|
||||
if ($defined(this._line)) {
|
||||
this._removeLine();
|
||||
}
|
||||
@ -93,7 +118,7 @@ class ControlPoint {
|
||||
if ($defined(this._line)) this._createControlPoint();
|
||||
}
|
||||
|
||||
_createControlPoint() {
|
||||
private _createControlPoint() {
|
||||
this._controls = this._line.getLine().getControlPoints();
|
||||
let pos = this._line.getLine().getFrom();
|
||||
this._controlPointsController[0].setPosition(
|
||||
@ -117,11 +142,11 @@ class ControlPoint {
|
||||
);
|
||||
}
|
||||
|
||||
_removeLine() {
|
||||
private _removeLine() {
|
||||
// Overwrite default behaviour ...
|
||||
}
|
||||
|
||||
_mouseDown(event, point, me) {
|
||||
private _mouseDown(event: Event, point, me) {
|
||||
if (!this._isBinded) {
|
||||
this._isBinded = true;
|
||||
this._mouseMoveFunction = (e) => {
|
||||
@ -129,7 +154,7 @@ class ControlPoint {
|
||||
};
|
||||
|
||||
this._workspace.getScreenManager().addEvent('mousemove', this._mouseMoveFunction);
|
||||
this._mouseUpFunction = (e) => {
|
||||
this._mouseUpFunction = (e: Event) => {
|
||||
me._mouseUp(e, point, me);
|
||||
};
|
||||
this._workspace.getScreenManager().addEvent('mouseup', this._mouseUpFunction);
|
||||
@ -139,7 +164,7 @@ class ControlPoint {
|
||||
return false;
|
||||
}
|
||||
|
||||
_mouseMoveEvent(event, point) {
|
||||
private _mouseMoveEvent(event: MouseEvent, point: Point) {
|
||||
const screen = this._workspace.getScreenManager();
|
||||
const pos = screen.getWorkspaceMousePosition(event);
|
||||
|
||||
@ -162,7 +187,7 @@ class ControlPoint {
|
||||
this._line.getLine().updateLine(point);
|
||||
}
|
||||
|
||||
_mouseUp(event, point) {
|
||||
private _mouseUp(event: MouseEvent, point: Point) {
|
||||
this._workspace.getScreenManager().removeEvent('mousemove', this._mouseMoveFunction);
|
||||
this._workspace.getScreenManager().removeEvent('mouseup', this._mouseUpFunction);
|
||||
|
||||
@ -171,13 +196,13 @@ class ControlPoint {
|
||||
this._isBinded = false;
|
||||
}
|
||||
|
||||
_mouseClick(event) {
|
||||
_mouseClick(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
setVisibility(visible) {
|
||||
setVisibility(visible: boolean) {
|
||||
if (visible) {
|
||||
this._controlLines[0].moveToFront();
|
||||
this._controlLines[1].moveToFront();
|
||||
@ -190,7 +215,7 @@ class ControlPoint {
|
||||
this._controlLines[1].setVisibility(visible);
|
||||
}
|
||||
|
||||
addToWorkspace(workspace) {
|
||||
addToWorkspace(workspace: Workspace): void {
|
||||
this._workspace = workspace;
|
||||
workspace.append(this._controlPointsController[0]);
|
||||
workspace.append(this._controlPointsController[1]);
|
||||
@ -198,7 +223,7 @@ class ControlPoint {
|
||||
workspace.append(this._controlLines[1]);
|
||||
}
|
||||
|
||||
removeFromWorkspace(workspace) {
|
||||
removeFromWorkspace(workspace: Workspace) {
|
||||
this._workspace = null;
|
||||
workspace.removeChild(this._controlPointsController[0]);
|
||||
workspace.removeChild(this._controlPointsController[1]);
|
||||
@ -206,20 +231,21 @@ class ControlPoint {
|
||||
workspace.removeChild(this._controlLines[1]);
|
||||
}
|
||||
|
||||
getControlPoint(index) {
|
||||
getControlPoint(index: number): ControlPoint {
|
||||
return this._controls[index];
|
||||
}
|
||||
|
||||
getOriginalEndPoint(index) {
|
||||
getOriginalEndPoint(index: number) {
|
||||
return this._endPoint[index];
|
||||
}
|
||||
|
||||
getOriginalCtrlPoint(index) {
|
||||
getOriginalCtrlPoint(index: number): ControlPoint {
|
||||
return this._orignalCtrlPoint[index];
|
||||
}
|
||||
|
||||
static FROM = 0;
|
||||
|
||||
static TO = 1;
|
||||
}
|
||||
|
||||
ControlPoint.FROM = 0;
|
||||
ControlPoint.TO = 1;
|
||||
|
||||
export default ControlPoint;
|
@ -137,8 +137,8 @@ class Designer extends Events {
|
||||
}
|
||||
|
||||
private _registerWheelEvents(): void {
|
||||
const zoomFactor = 1.006;
|
||||
document.addEventListener('wheel', (event) => {
|
||||
const zoomFactor = 1.02;
|
||||
document.addEventListener('wheel', (event: WheelEvent) => {
|
||||
if (event.deltaX > 0 || event.deltaY > 0) {
|
||||
this.zoomOut(zoomFactor);
|
||||
} else {
|
||||
|
@ -22,7 +22,8 @@ import { Designer } from '..';
|
||||
import Topic from './Topic';
|
||||
|
||||
class DesignerKeyboard extends Keyboard {
|
||||
static _instance: any;
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
static _instance: DesignerKeyboard;
|
||||
|
||||
constructor(designer: Designer) {
|
||||
super();
|
||||
@ -79,14 +80,14 @@ class DesignerKeyboard extends Keyboard {
|
||||
this.addShortcut(
|
||||
['tab'], (eventevent: Event) => {
|
||||
designer.createChildForSelectedNode();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
eventevent.preventDefault();
|
||||
eventevent.stopPropagation();
|
||||
},
|
||||
);
|
||||
this.addShortcut(
|
||||
['meta+enter'], (eventevent: Event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
eventevent.preventDefault();
|
||||
eventevent.stopPropagation();
|
||||
designer.createChildForSelectedNode();
|
||||
},
|
||||
);
|
||||
@ -244,7 +245,7 @@ class DesignerKeyboard extends Keyboard {
|
||||
const excludes = ['esc', 'escape', 'f1', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'];
|
||||
|
||||
$(document).on('keypress', (event) => {
|
||||
let keyCode;
|
||||
let keyCode: number;
|
||||
// Firefox doesn't skip special keys for keypress event...
|
||||
if (event.key && excludes.includes(event.key.toLowerCase())) {
|
||||
return;
|
||||
@ -256,6 +257,7 @@ class DesignerKeyboard extends Keyboard {
|
||||
keyCode = event.keyCode;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const jq: any = $;
|
||||
const specialKey = jq.hotkeys.specialKeys[keyCode];
|
||||
if (['enter', 'capslock'].indexOf(specialKey) === -1 && !jq.hotkeys.shiftNums[keyCode]) {
|
||||
|
@ -16,15 +16,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import Command from './Command';
|
||||
import CommandContext from './CommandContext';
|
||||
|
||||
class DesignerUndoManager {
|
||||
private _undoQueue: Command[];
|
||||
|
||||
private _redoQueue: Command[];
|
||||
|
||||
private _baseId: number;
|
||||
|
||||
constructor() {
|
||||
this._undoQueue = [];
|
||||
this._redoQueue = [];
|
||||
this._baseId = 0;
|
||||
}
|
||||
|
||||
enqueue(command) {
|
||||
enqueue(command: Command) {
|
||||
$assert(command, 'Command can not be null');
|
||||
const { length } = this._undoQueue;
|
||||
if (command.discardDuplicated && length > 0) {
|
||||
@ -39,7 +47,7 @@ class DesignerUndoManager {
|
||||
this._redoQueue = [];
|
||||
}
|
||||
|
||||
execUndo(commandContext) {
|
||||
execUndo(commandContext: CommandContext) {
|
||||
if (this._undoQueue.length > 0) {
|
||||
const command = this._undoQueue.pop();
|
||||
this._redoQueue.push(command);
|
@ -17,9 +17,25 @@
|
||||
*/
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import DragTopic from './DragTopic';
|
||||
import EventBusDispatcher from './layout/EventBusDispatcher';
|
||||
import Workspace from './Workspace';
|
||||
|
||||
class DragManager {
|
||||
constructor(workspace, eventDispatcher) {
|
||||
private _workspace: Workspace;
|
||||
|
||||
private _designerModel: Workspace;
|
||||
|
||||
private _isDragInProcess: boolean;
|
||||
|
||||
private _eventDispatcher: EventBusDispatcher;
|
||||
|
||||
private _listeners;
|
||||
|
||||
private _mouseMoveListener;
|
||||
|
||||
private _mouseUpListener;
|
||||
|
||||
constructor(workspace: Workspace, eventDispatcher: EventBusDispatcher) {
|
||||
this._workspace = workspace;
|
||||
this._designerModel = workspace;
|
||||
this._listeners = {};
|
||||
@ -34,7 +50,7 @@ class DragManager {
|
||||
const screen = workspace.getScreenManager();
|
||||
const dragManager = this;
|
||||
const me = this;
|
||||
const mouseDownListener = function mouseDownListener(event) {
|
||||
const mouseDownListener = function mouseDownListener() {
|
||||
if (workspace.isWorkspaceEventsEnabled()) {
|
||||
// Disable double drag...
|
||||
workspace.enableWorkspaceEvents(false);
|
||||
@ -62,11 +78,11 @@ class DragManager {
|
||||
node.addEvent('mousedown', mouseDownListener);
|
||||
}
|
||||
|
||||
remove(node) {
|
||||
remove() {
|
||||
throw new Error('Not implemented: DragManager.prototype.remove');
|
||||
}
|
||||
|
||||
_buildMouseMoveListener(workspace, dragNode, dragManager) {
|
||||
protected _buildMouseMoveListener(workspace: Workspace, dragNode, dragManager: DragManager) {
|
||||
const screen = workspace.getScreenManager();
|
||||
const me = this;
|
||||
const result = (event) => {
|
||||
@ -98,7 +114,7 @@ class DragManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
_buildMouseUpListener(workspace, node, dragNode, dragManager) {
|
||||
protected _buildMouseUpListener(workspace: Workspace, node, dragNode, dragManager: DragManager) {
|
||||
const screen = workspace.getScreenManager();
|
||||
const me = this;
|
||||
const result = (event) => {
|
@ -27,6 +27,7 @@ import SizeType from './SizeType';
|
||||
|
||||
class MainTopic extends Topic {
|
||||
private INNER_RECT_ATTRIBUTES: { stroke: string; };
|
||||
|
||||
/**
|
||||
* @extends mindplot.Topic
|
||||
* @constructs
|
||||
@ -74,7 +75,6 @@ class MainTopic extends Topic {
|
||||
return group;
|
||||
}
|
||||
|
||||
|
||||
updateTopicShape(targetTopic: Topic) {
|
||||
// Change figure based on the connected topic ...
|
||||
const model = this.getModel();
|
||||
|
@ -258,7 +258,7 @@ class Relationship extends ConnectionLine {
|
||||
}
|
||||
|
||||
// @typescript-eslint/ban-types
|
||||
addEvent(eventType: string, listener: any) {
|
||||
addEvent(eventType: string, listener) {
|
||||
let type = eventType;
|
||||
// Translate to web 2d events ...
|
||||
if (type === 'onfocus') {
|
||||
|
@ -28,7 +28,7 @@ class RelationshipPivot {
|
||||
|
||||
private _designer: Designer;
|
||||
|
||||
private _mouseMoveEvent: MouseEvent;
|
||||
private _mouseMoveEvent;
|
||||
|
||||
private _onClickEvent: (event: MouseEvent) => void;
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
import { $assert } from '@wisemapping/core-js';
|
||||
import { Point } from '@wisemapping/web2d';
|
||||
import Icon from './Icon';
|
||||
import Topic from './Topic';
|
||||
|
||||
class ScreenManager {
|
||||
@ -75,7 +76,7 @@ class ScreenManager {
|
||||
|
||||
fireEvent(type: string, event: UIEvent = null) {
|
||||
if (type === 'click') {
|
||||
this._clickEvents.forEach((listener: (arg0: any, arg1: any) => void) => {
|
||||
this._clickEvents.forEach((listener) => {
|
||||
listener(type, event);
|
||||
});
|
||||
} else {
|
||||
@ -101,7 +102,7 @@ class ScreenManager {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
getWorkspaceIconPosition(e: { getImage: () => any; getSize: () => any; getGroup: () => any; }) {
|
||||
getWorkspaceIconPosition(e: Icon) {
|
||||
// Retrieve current icon position.
|
||||
const image = e.getImage();
|
||||
const elementPosition = image.getPosition();
|
||||
|
@ -20,11 +20,12 @@ import { Elipse } from '@wisemapping/web2d';
|
||||
import TopicConfig from './TopicConfig';
|
||||
import ActionDispatcher from './ActionDispatcher';
|
||||
import Topic from './Topic';
|
||||
import IconGroup from './IconGroup';
|
||||
|
||||
class ShirinkConnector {
|
||||
private _isShrink: boolean;
|
||||
private _ellipse: any;
|
||||
|
||||
private _ellipse: Elipse;
|
||||
|
||||
constructor(topic: Topic) {
|
||||
this._isShrink = false;
|
||||
const ellipse = new Elipse(TopicConfig.INNER_RECT_ATTRIBUTES);
|
||||
@ -33,7 +34,7 @@ class ShirinkConnector {
|
||||
ellipse.setFill('rgb(62,118,179)');
|
||||
|
||||
ellipse.setSize(TopicConfig.CONNECTOR_WIDTH, TopicConfig.CONNECTOR_WIDTH);
|
||||
ellipse.addEvent('click', (event) => {
|
||||
ellipse.addEvent('click', (event: Event) => {
|
||||
const model = topic.getModel();
|
||||
const collapse = !model.areChildrenShrunken();
|
||||
|
||||
|
@ -19,7 +19,7 @@ import $ from 'jquery';
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
|
||||
import {
|
||||
Rect, Image, Line, Text, Group, ElementClass, Point
|
||||
Rect, Image, Line, Text, Group, ElementClass, Point,
|
||||
} from '@wisemapping/web2d';
|
||||
|
||||
import NodeGraph from './NodeGraph';
|
||||
@ -35,7 +35,6 @@ import NoteEditor from './widget/NoteEditor';
|
||||
import ActionDispatcher from './ActionDispatcher';
|
||||
import LinkEditor from './widget/LinkEditor';
|
||||
|
||||
|
||||
import TopicEventDispatcher, { TopicEvent } from './TopicEventDispatcher';
|
||||
import { TopicShape } from './model/INodeModel';
|
||||
import NodeModel from './model/NodeModel';
|
||||
@ -50,14 +49,25 @@ const ICON_SCALING_FACTOR = 1.3;
|
||||
|
||||
abstract class Topic extends NodeGraph {
|
||||
private _innerShape: ElementClass;
|
||||
|
||||
private _relationships: Relationship[];
|
||||
|
||||
private _isInWorkspace: boolean;
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
private _children: Topic[];
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
private _parent: Topic | null;
|
||||
|
||||
private _outerShape: ElementClass;
|
||||
|
||||
private _text: Text | null;
|
||||
|
||||
private _iconsGroup: IconGroup;
|
||||
private _connector: any;
|
||||
|
||||
private _connector: ShirinkConnector;
|
||||
|
||||
private _outgoingLine: Line;
|
||||
|
||||
constructor(model: NodeModel, options) {
|
||||
@ -241,7 +251,7 @@ abstract class Topic extends NodeGraph {
|
||||
result.setStroke(1, 'solid', stokeColor);
|
||||
};
|
||||
|
||||
result.getSize = function getSize() { this.size };
|
||||
result.getSize = function getSize() { return this.size; };
|
||||
|
||||
result.setPosition = () => {
|
||||
// Overwrite behaviour ...
|
||||
@ -1324,7 +1334,6 @@ abstract class Topic extends NodeGraph {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
isChildTopic(childTopic: Topic): boolean {
|
||||
let result = this.getId() === childTopic.getId();
|
||||
if (!result) {
|
||||
|
@ -113,11 +113,11 @@ class Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
addEvent(type: string, listener): void {
|
||||
addEvent(type: string, listener: (event: Event) => void): void {
|
||||
this._workspace.addEvent(type, listener);
|
||||
}
|
||||
|
||||
removeEvent(type: string, listener): void {
|
||||
removeEvent(type: string, listener: (event: Event) => void): void {
|
||||
$assert(type, 'type can not be null');
|
||||
$assert(listener, 'listener can not be null');
|
||||
this._workspace.removeEvent(type, listener);
|
||||
@ -193,7 +193,7 @@ class Workspace {
|
||||
const workspace = this._workspace;
|
||||
const screenManager = this._screenManager;
|
||||
const mWorkspace = this;
|
||||
const mouseDownListener = function mouseDownListener(event) {
|
||||
const mouseDownListener = function mouseDownListener(event: MouseEvent) {
|
||||
if (!$defined(workspace._mouseMoveListener)) {
|
||||
if (mWorkspace.isWorkspaceEventsEnabled()) {
|
||||
mWorkspace.enableWorkspaceEvents(false);
|
||||
@ -202,7 +202,7 @@ class Workspace {
|
||||
const originalCoordOrigin = workspace.getCoordOrigin();
|
||||
|
||||
let wasDragged = false;
|
||||
workspace._mouseMoveListener = (mouseMoveEvent) => {
|
||||
workspace._mouseMoveListener = (mouseMoveEvent: MouseEvent) => {
|
||||
const currentMousePosition = screenManager.getWorkspaceMousePosition(mouseMoveEvent);
|
||||
|
||||
const offsetX = currentMousePosition.x - mouseDownPosition.x;
|
||||
|
@ -18,6 +18,7 @@
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import Command from '../Command';
|
||||
import CommandContext from '../CommandContext';
|
||||
import FeatureModel from '../model/FeatureModel';
|
||||
import FeatureType from '../model/FeatureType';
|
||||
|
||||
class AddFeatureToTopicCommand extends Command {
|
||||
@ -27,7 +28,7 @@ class AddFeatureToTopicCommand extends Command {
|
||||
|
||||
private _attributes: object;
|
||||
|
||||
private _featureModel: any;
|
||||
private _featureModel: FeatureModel;
|
||||
|
||||
/*
|
||||
* @classdesc This command class handles do/undo of adding features to topics, e.g. an
|
||||
|
@ -24,9 +24,9 @@ class ChangeFeatureToTopicCommand extends Command {
|
||||
|
||||
private _topicId: number;
|
||||
|
||||
private _attributes: any;
|
||||
private _attributes;
|
||||
|
||||
constructor(topicId: number, featureId: number, attributes: any) {
|
||||
constructor(topicId: number, featureId: number, attributes) {
|
||||
$assert($defined(topicId), 'topicId can not be null');
|
||||
$assert($defined(featureId), 'featureId can not be null');
|
||||
$assert($defined(attributes), 'attributes can not be null');
|
||||
@ -53,7 +53,7 @@ class ChangeFeatureToTopicCommand extends Command {
|
||||
* Overrides abstract parent method
|
||||
* @see {@link mindplot.Command.undoExecute}
|
||||
*/
|
||||
undoExecute(commandContext: any) {
|
||||
undoExecute(commandContext: CommandContext) {
|
||||
this.execute(commandContext);
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import Topic from '../Topic';
|
||||
class DragTopicCommand extends Command {
|
||||
private _topicsId: number;
|
||||
|
||||
private _parentId: any;
|
||||
private _parentId: number;
|
||||
|
||||
private _position: Point;
|
||||
|
||||
@ -62,7 +62,7 @@ class DragTopicCommand extends Command {
|
||||
const origPosition = topic.getPosition();
|
||||
|
||||
// Disconnect topic ..
|
||||
if ($defined(origParentTopic) && origParentTopic !== this._parentId) {
|
||||
if ($defined(origParentTopic) && origParentTopic.getId() !== this._parentId) {
|
||||
commandContext.disconnect(topic);
|
||||
}
|
||||
|
||||
@ -76,9 +76,9 @@ class DragTopicCommand extends Command {
|
||||
}
|
||||
|
||||
// Finally, connect topic ...
|
||||
if (origParentTopic !== this._parentId) {
|
||||
if (!$defined(origParentTopic) || origParentTopic.getId() !== this._parentId) {
|
||||
if ($defined(this._parentId)) {
|
||||
const parentTopic = commandContext.findTopics(this._parentId)[0];
|
||||
const parentTopic = commandContext.findTopics([this._parentId])[0];
|
||||
commandContext.connect(topic, parentTopic);
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,6 @@ import CommandContext from '../CommandContext';
|
||||
import Topic from '../Topic';
|
||||
|
||||
class GenericFunctionCommand extends Command {
|
||||
private _discardDuplicated: string;
|
||||
|
||||
private _value: string | object | boolean | number;
|
||||
|
||||
private _topicsId: number[];
|
||||
@ -42,7 +40,6 @@ class GenericFunctionCommand extends Command {
|
||||
this._topicsId = topicsIds;
|
||||
this._commandFunc = commandFunc;
|
||||
this._oldValues = [];
|
||||
this.discardDuplicated = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,14 +76,6 @@ class GenericFunctionCommand extends Command {
|
||||
throw new Error('undo can not be applied.');
|
||||
}
|
||||
}
|
||||
|
||||
public get disardDuplicated(): string {
|
||||
return this._discardDuplicated;
|
||||
}
|
||||
|
||||
public set discardDuplicated(value: string) {
|
||||
this._discardDuplicated = value;
|
||||
}
|
||||
}
|
||||
|
||||
export default GenericFunctionCommand;
|
||||
|
@ -16,13 +16,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import { Line } from '@wisemapping/web2d';
|
||||
import Command from '../Command';
|
||||
import ControlPoint from '../ControlPoint';
|
||||
|
||||
class MoveControlPointCommand extends Command {
|
||||
private _ctrlPointControler: ControlPoint;
|
||||
|
||||
private _line: any;
|
||||
private _line: Line;
|
||||
|
||||
private _controlPoint: any;
|
||||
|
||||
|
@ -18,13 +18,14 @@
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import Command from '../Command';
|
||||
import CommandContext from '../CommandContext';
|
||||
import FeatureModel from '../model/FeatureModel';
|
||||
|
||||
class RemoveFeatureFromTopicCommand extends Command {
|
||||
private _topicId: number;
|
||||
|
||||
private _featureId: number;
|
||||
|
||||
private _oldFeature: any;
|
||||
private _oldFeature: FeatureModel;
|
||||
|
||||
/**
|
||||
* @classdesc This command handles do/undo of removing a feature from a topic, e.g. an icon or
|
||||
@ -43,7 +44,7 @@ class RemoveFeatureFromTopicCommand extends Command {
|
||||
/**
|
||||
* Overrides abstract parent method
|
||||
*/
|
||||
execute(commandContext:CommandContext):void {
|
||||
execute(commandContext: CommandContext): void {
|
||||
const topic = commandContext.findTopics([this._topicId])[0];
|
||||
const feature = topic.findFeatureById(this._featureId);
|
||||
topic.removeFeature(feature);
|
||||
@ -54,7 +55,7 @@ class RemoveFeatureFromTopicCommand extends Command {
|
||||
* Overrides abstract parent method
|
||||
* @see {@link mindplot.Command.undoExecute}
|
||||
*/
|
||||
undoExecute(commandContext:CommandContext) {
|
||||
undoExecute(commandContext: CommandContext) {
|
||||
const topic = commandContext.findTopics([this._topicId])[0];
|
||||
topic.addFeature(this._oldFeature);
|
||||
this._oldFeature = null;
|
||||
|
@ -16,9 +16,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import PositionType from '../PositionType';
|
||||
import SizeType from '../SizeType';
|
||||
|
||||
class Node {
|
||||
constructor(id, size, position, sorter) {
|
||||
private _id: number;
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
_parent: Node;
|
||||
|
||||
private _sorter: any;
|
||||
|
||||
private _properties;
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
_children: Node[];
|
||||
|
||||
constructor(id: number, size: SizeType, position, sorter) {
|
||||
$assert(typeof id === 'number' && Number.isFinite(id), 'id can not be null');
|
||||
$assert(size, 'size can not be null');
|
||||
$assert(position, 'position can not be null');
|
||||
@ -69,7 +83,7 @@ class Node {
|
||||
}
|
||||
|
||||
/** */
|
||||
setOrder(order) {
|
||||
setOrder(order: number) {
|
||||
$assert(
|
||||
typeof order === 'number' && Number.isFinite(order),
|
||||
`Order can not be null. Value:${order}`,
|
||||
@ -148,7 +162,7 @@ class Node {
|
||||
y: oldDisplacement.y + displacement.y,
|
||||
};
|
||||
|
||||
this._setProperty('freeDisplacement', Object.clone(newDisplacement));
|
||||
this._setProperty('freeDisplacement', { ...newDisplacement });
|
||||
}
|
||||
|
||||
/** */
|
||||
@ -163,7 +177,7 @@ class Node {
|
||||
}
|
||||
|
||||
/** */
|
||||
setPosition(position) {
|
||||
setPosition(position: PositionType) {
|
||||
$assert($defined(position), 'Position can not be null');
|
||||
$assert($defined(position.x), 'x can not be null');
|
||||
$assert($defined(position.y), 'y can not be null');
|
||||
@ -172,12 +186,12 @@ class Node {
|
||||
const currentPos = this.getPosition();
|
||||
if (
|
||||
currentPos == null
|
||||
|| Math.abs(currentPos.x - position.x) > 2
|
||||
|| Math.abs(currentPos.y - position.y) > 2
|
||||
|| Math.abs(currentPos.x - position.x) > 2
|
||||
|| Math.abs(currentPos.y - position.y) > 2
|
||||
) this._setProperty('position', position);
|
||||
}
|
||||
|
||||
_setProperty(key, value) {
|
||||
_setProperty(key: string, value) {
|
||||
let prop = this._properties[key];
|
||||
if (!prop) {
|
||||
prop = {
|
||||
@ -214,20 +228,13 @@ class Node {
|
||||
/** @return {String} returns id, order, position, size and shrink information */
|
||||
toString() {
|
||||
return (
|
||||
`[id:${
|
||||
this.getId()
|
||||
}, order:${
|
||||
this.getOrder()
|
||||
}, position: {${
|
||||
this.getPosition().x
|
||||
},${
|
||||
this.getPosition().y
|
||||
}}, size: {${
|
||||
this.getSize().width
|
||||
},${
|
||||
this.getSize().height
|
||||
}}, shrink:${
|
||||
this.areChildrenShrunken()
|
||||
`[id:${this.getId()
|
||||
}, order:${this.getOrder()
|
||||
}, position: {${this.getPosition().x
|
||||
},${this.getPosition().y
|
||||
}}, size: {${this.getSize().width
|
||||
},${this.getSize().height
|
||||
}}, shrink:${this.areChildrenShrunken()
|
||||
}]`
|
||||
);
|
||||
}
|
@ -16,8 +16,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { $assert, $defined } from '@wisemapping/core-js';
|
||||
import PositionType from '../PositionType';
|
||||
import Node from './Node';
|
||||
|
||||
class RootedTreeSet {
|
||||
private _rootNodes: Node[];
|
||||
|
||||
protected _children: Node[];
|
||||
|
||||
|
||||
constructor() {
|
||||
this._rootNodes = [];
|
||||
}
|
||||
@ -26,7 +33,7 @@ class RootedTreeSet {
|
||||
* @param root
|
||||
* @throws will throw an error if root is null or undefined
|
||||
*/
|
||||
setRoot(root) {
|
||||
setRoot(root: Node) {
|
||||
$assert(root, 'root can not be null');
|
||||
this._rootNodes.push(this._decodate(root));
|
||||
}
|
||||
@ -36,8 +43,7 @@ class RootedTreeSet {
|
||||
return this._rootNodes;
|
||||
}
|
||||
|
||||
_decodate(node) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
_decodate(node: Node) {
|
||||
node._children = [];
|
||||
return node;
|
||||
}
|
||||
@ -48,7 +54,7 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if node with id already exists
|
||||
* @throws will throw an error if node has been added already
|
||||
*/
|
||||
add(node) {
|
||||
add(node: Node) {
|
||||
$assert(node, 'node can not be null');
|
||||
$assert(
|
||||
!this.find(node.getId(), false),
|
||||
@ -62,7 +68,7 @@ class RootedTreeSet {
|
||||
* @param nodeId
|
||||
* @throws will throw an error if nodeId is null or undefined
|
||||
*/
|
||||
remove(nodeId) {
|
||||
remove(nodeId: number) {
|
||||
$assert($defined(nodeId), 'nodeId can not be null');
|
||||
const node = this.find(nodeId);
|
||||
this._rootNodes = this._rootNodes.filter((n) => n !== node);
|
||||
@ -75,7 +81,7 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if childId is null or undefined
|
||||
* @throws will throw an error if node with id childId is already a child of parent
|
||||
*/
|
||||
connect(parentId, childId) {
|
||||
connect(parentId: number, childId: number) {
|
||||
$assert($defined(parentId), 'parent can not be null');
|
||||
$assert($defined(childId), 'child can not be null');
|
||||
|
||||
@ -96,7 +102,7 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if nodeId is null or undefined
|
||||
* @throws will throw an error if node is not connected
|
||||
*/
|
||||
disconnect(nodeId) {
|
||||
disconnect(nodeId: number) {
|
||||
$assert($defined(nodeId), 'nodeId can not be null');
|
||||
const node = this.find(nodeId);
|
||||
$assert(node._parent, 'Node is not connected');
|
||||
@ -113,7 +119,7 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if node cannot be found
|
||||
* @return node
|
||||
*/
|
||||
find(id, validate = true) {
|
||||
find(id: number, validate = true): Node {
|
||||
$assert($defined(id), 'id can not be null');
|
||||
|
||||
const graphs = this._rootNodes;
|
||||
@ -132,7 +138,7 @@ class RootedTreeSet {
|
||||
return result;
|
||||
}
|
||||
|
||||
_find(id, parent) {
|
||||
private _find(id: number, parent: Node): Node {
|
||||
if (parent.getId() === id) {
|
||||
return parent;
|
||||
}
|
||||
@ -153,7 +159,7 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if nodeId is null or undefined
|
||||
* @return children
|
||||
*/
|
||||
getChildren(node) {
|
||||
getChildren(node: Node): Node[] {
|
||||
$assert(node, 'node cannot be null');
|
||||
return node._children;
|
||||
}
|
||||
@ -163,7 +169,7 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if node is null or undefined
|
||||
* @return root node or the provided node, if it has no parent
|
||||
*/
|
||||
getRootNode(node) {
|
||||
getRootNode(node: Node) {
|
||||
$assert(node, 'node cannot be null');
|
||||
const parent = this.getParent(node);
|
||||
if ($defined(parent)) {
|
||||
@ -177,12 +183,12 @@ class RootedTreeSet {
|
||||
* @param node
|
||||
* @throws will throw an error if node is null or undefined
|
||||
* @return {Array} ancestors */
|
||||
getAncestors(node) {
|
||||
getAncestors(node: Node): Node[] {
|
||||
$assert(node, 'node cannot be null');
|
||||
return this._getAncestors(this.getParent(node), []);
|
||||
}
|
||||
|
||||
_getAncestors(node, ancestors) {
|
||||
_getAncestors(node: Node, ancestors: Node[]) {
|
||||
const result = ancestors;
|
||||
if (node) {
|
||||
result.push(node);
|
||||
@ -196,7 +202,7 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if node is null or undefined
|
||||
* @return {Array} siblings
|
||||
*/
|
||||
getSiblings(node) {
|
||||
getSiblings(node: Node): Node[] {
|
||||
$assert(node, 'node cannot be null');
|
||||
if (!$defined(node._parent)) {
|
||||
return [];
|
||||
@ -210,12 +216,12 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if node is null or undefined
|
||||
* @return {Boolean} whether the node has a single path to a single leaf (no branching)
|
||||
*/
|
||||
hasSinglePathToSingleLeaf(node) {
|
||||
hasSinglePathToSingleLeaf(node: Node): boolean {
|
||||
$assert(node, 'node cannot be null');
|
||||
return this._hasSinglePathToSingleLeaf(node);
|
||||
}
|
||||
|
||||
_hasSinglePathToSingleLeaf(node) {
|
||||
private _hasSinglePathToSingleLeaf(node: Node): boolean {
|
||||
const children = this.getChildren(node);
|
||||
|
||||
if (children.length === 1) {
|
||||
@ -228,7 +234,7 @@ class RootedTreeSet {
|
||||
/**
|
||||
* @param node
|
||||
* @return {Boolean} whether the node is the start of a subbranch */
|
||||
isStartOfSubBranch(node) {
|
||||
isStartOfSubBranch(node: Node): boolean {
|
||||
return this.getSiblings(node).length > 0 && this.getChildren(node).length === 1;
|
||||
}
|
||||
|
||||
@ -237,7 +243,7 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if node is null or undefined
|
||||
* @return {Boolean} whether the node is a leaf
|
||||
*/
|
||||
isLeaf(node) {
|
||||
isLeaf(node: Node): boolean {
|
||||
$assert(node, 'node cannot be null');
|
||||
return this.getChildren(node).length === 0;
|
||||
}
|
||||
@ -247,7 +253,7 @@ class RootedTreeSet {
|
||||
* @throws will throw an error if node is null or undefined
|
||||
* @return parent
|
||||
*/
|
||||
getParent(node) {
|
||||
getParent(node: Node): Node {
|
||||
$assert(node, 'node cannot be null');
|
||||
return node._parent;
|
||||
}
|
||||
@ -265,7 +271,7 @@ class RootedTreeSet {
|
||||
return result;
|
||||
}
|
||||
|
||||
_dump(node, indent) {
|
||||
_dump(node: Node, indent: string) {
|
||||
let result = `${indent + node}\n`;
|
||||
const children = this.getChildren(node);
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
@ -287,7 +293,7 @@ class RootedTreeSet {
|
||||
}
|
||||
}
|
||||
|
||||
_plot(canvas, node, root) {
|
||||
_plot(canvas, node: Node, root?) {
|
||||
const children = this.getChildren(node);
|
||||
const cx = node.getPosition().x + canvas.width / 2 - node.getSize().width / 2;
|
||||
const cy = node.getPosition().y + canvas.height / 2 - node.getSize().height / 2;
|
||||
@ -316,43 +322,27 @@ class RootedTreeSet {
|
||||
const rectSize = { width: rect.attr('width'), height: rect.attr('height') };
|
||||
rect.click(() => {
|
||||
console.log(
|
||||
`[id:${
|
||||
node.getId()
|
||||
}, order:${
|
||||
node.getOrder()
|
||||
}, position:(${
|
||||
rectPosition.x
|
||||
}, ${
|
||||
rectPosition.y
|
||||
}), size:${
|
||||
rectSize.width
|
||||
},${
|
||||
rectSize.height
|
||||
}, freeDisplacement:(${
|
||||
node.getFreeDisplacement().x
|
||||
},${
|
||||
node.getFreeDisplacement().y
|
||||
`[id:${node.getId()
|
||||
}, order:${node.getOrder()
|
||||
}, position:(${rectPosition.x
|
||||
}, ${rectPosition.y
|
||||
}), size:${rectSize.width
|
||||
},${rectSize.height
|
||||
}, freeDisplacement:(${node.getFreeDisplacement().x
|
||||
},${node.getFreeDisplacement().y
|
||||
})]`,
|
||||
);
|
||||
});
|
||||
text.click(() => {
|
||||
console.log(
|
||||
`[id:${
|
||||
node.getId()
|
||||
}, order:${
|
||||
node.getOrder()
|
||||
}, position:(${
|
||||
rectPosition.x
|
||||
},${
|
||||
rectPosition.y
|
||||
}), size:${
|
||||
rectSize.width
|
||||
}x${
|
||||
rectSize.height
|
||||
}, freeDisplacement:(${
|
||||
node.getFreeDisplacement().x
|
||||
},${
|
||||
node.getFreeDisplacement().y
|
||||
`[id:${node.getId()
|
||||
}, order:${node.getOrder()
|
||||
}, position:(${rectPosition.x
|
||||
},${rectPosition.y
|
||||
}), size:${rectSize.width
|
||||
}x${rectSize.height
|
||||
}, freeDisplacement:(${node.getFreeDisplacement().x
|
||||
},${node.getFreeDisplacement().y
|
||||
})]`,
|
||||
);
|
||||
});
|
||||
@ -367,7 +357,7 @@ class RootedTreeSet {
|
||||
* @param node
|
||||
* @param position
|
||||
*/
|
||||
updateBranchPosition(node, position) {
|
||||
updateBranchPosition(node: Node, position: PositionType): void {
|
||||
const oldPos = node.getPosition();
|
||||
node.setPosition(position);
|
||||
|
||||
@ -386,7 +376,7 @@ class RootedTreeSet {
|
||||
* @param xOffset
|
||||
* @param yOffset
|
||||
*/
|
||||
shiftBranchPosition(node, xOffset, yOffset) {
|
||||
shiftBranchPosition(node: Node, xOffset: number, yOffset: number): void {
|
||||
const position = node.getPosition();
|
||||
node.setPosition({ x: position.x + xOffset, y: position.y + yOffset });
|
||||
|
||||
@ -402,7 +392,7 @@ class RootedTreeSet {
|
||||
* @param yOffset
|
||||
* @return siblings in the offset (vertical) direction, i.e. with lower or higher order
|
||||
*/
|
||||
getSiblingsInVerticalDirection(node, yOffset) {
|
||||
getSiblingsInVerticalDirection(node: Node, yOffset: number): Node[] {
|
||||
// siblings with lower or higher order
|
||||
// (depending on the direction of the offset and on the same side as their parent)
|
||||
const parent = this.getParent(node);
|
||||
@ -429,7 +419,7 @@ class RootedTreeSet {
|
||||
* @return branches of the root node on the same side as the given node's, in the given
|
||||
* vertical direction
|
||||
*/
|
||||
getBranchesInVerticalDirection(node, yOffset) {
|
||||
getBranchesInVerticalDirection(node: Node, yOffset: number): Node[] {
|
||||
// direct descendants of the root that do not contain the node and are on the same side
|
||||
// and on the direction of the offset
|
||||
const rootNode = this.getRootNode(node);
|
||||
@ -437,7 +427,7 @@ class RootedTreeSet {
|
||||
.filter(((child) => this._find(node.getId(), child)));
|
||||
|
||||
const branch = branches[0];
|
||||
const rootDescendants = this.getSiblings(branch).filter((sibling) => {
|
||||
const result = this.getSiblings(branch).filter((sibling) => {
|
||||
const sameSide = node.getPosition().x > rootNode.getPosition().x
|
||||
? sibling.getPosition().x > rootNode.getPosition().x
|
||||
: sibling.getPosition().x < rootNode.getPosition().x;
|
||||
@ -447,7 +437,7 @@ class RootedTreeSet {
|
||||
return sameSide && sameDirection;
|
||||
}, this);
|
||||
|
||||
return rootDescendants;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ class BootstrapDialog extends Options {
|
||||
return header;
|
||||
}
|
||||
|
||||
onAcceptClick(event) {
|
||||
onAcceptClick() {
|
||||
throw new Error('Unsupported operation');
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ class BootstrapDialog extends Options {
|
||||
// Overwrite default behaviour ...
|
||||
}
|
||||
|
||||
onRemoveClick(event) {
|
||||
onRemoveClick() {
|
||||
throw new Error('Unsupported operation');
|
||||
}
|
||||
|
||||
|
@ -37,8 +37,7 @@ const queryClient = new QueryClient({
|
||||
});
|
||||
|
||||
const App = (): ReactElement => {
|
||||
const appi18n = new AppI18n();
|
||||
const locale = appi18n.getBrowserLocale();
|
||||
const locale = AppI18n.getBrowserLocale();
|
||||
|
||||
// global variables set server-side
|
||||
const istTryMode = global.memoryPersistence;
|
||||
|
@ -15,13 +15,13 @@ export class Locale {
|
||||
}
|
||||
}
|
||||
|
||||
export default class AppI18n {
|
||||
public getUserLocale(): Locale {
|
||||
export default abstract class AppI18n {
|
||||
public static getUserLocale(): Locale {
|
||||
const account = fetchAccount();
|
||||
return account ? account.locale : this.getBrowserLocale();
|
||||
return account?.locale ? account.locale : this.getBrowserLocale();
|
||||
}
|
||||
|
||||
public getBrowserLocale(): Locale {
|
||||
public static getBrowserLocale(): Locale {
|
||||
let localeCode = (navigator.languages && navigator.languages[0]) || navigator.language;
|
||||
|
||||
// Just remove the variant ...
|
||||
|
@ -90,10 +90,22 @@ class CacheDecoratorClient implements Client {
|
||||
return this.client.fetchLabels();
|
||||
}
|
||||
|
||||
createLabel(title: string, color: string): Promise<number> {
|
||||
return this.client.createLabel(title, color);
|
||||
}
|
||||
|
||||
deleteLabel(id: number): Promise<void> {
|
||||
return this.client.deleteLabel(id);
|
||||
}
|
||||
|
||||
addLabelToMap(labelId: number, mapId: number): Promise<void> {
|
||||
return this.client.addLabelToMap(labelId, mapId);
|
||||
}
|
||||
|
||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
|
||||
return this.client.deleteLabelFromMap(labelId, mapId);
|
||||
}
|
||||
|
||||
fetchAccountInfo(): Promise<AccountInfo> {
|
||||
return this.client.fetchAccountInfo();
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ export type AccountInfo = {
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
email: string;
|
||||
locale: Locale;
|
||||
locale?: Locale;
|
||||
};
|
||||
|
||||
export type Permission = {
|
||||
@ -94,8 +94,11 @@ interface Client {
|
||||
updateStarred(id: number, starred: boolean): Promise<void>;
|
||||
updateMapToPublic(id: number, isPublic: boolean): Promise<void>;
|
||||
|
||||
createLabel(title: string, color: string): Promise<number>;
|
||||
fetchLabels(): Promise<Label[]>;
|
||||
deleteLabel(id: number): Promise<void>;
|
||||
addLabelToMap(labelId: number, mapId: number): Promise<void>;
|
||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void>;
|
||||
fetchAccountInfo(): Promise<AccountInfo>;
|
||||
|
||||
registerNewUser(user: NewUser): Promise<void>;
|
||||
|
@ -327,9 +327,46 @@ class MockClient implements Client {
|
||||
}
|
||||
}
|
||||
|
||||
createLabel(title: string, color: string): Promise<number> {
|
||||
const newId = Math.max.apply(Number, this.labels.map(l => l.id)) + 1;
|
||||
this.labels.push({
|
||||
id: newId,
|
||||
title,
|
||||
color,
|
||||
});
|
||||
return newId;
|
||||
}
|
||||
|
||||
deleteLabel(id: number): Promise<void> {
|
||||
this.labels = this.labels.filter((l) => l.id != id);
|
||||
console.log('Label delete:' + this.labels);
|
||||
this.maps = this.maps.map(m => {
|
||||
return {
|
||||
...m,
|
||||
labels: m.labels.filter((l) => l.id != id)
|
||||
};
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
addLabelToMap(labelId: number, mapId: number): Promise<void> {
|
||||
const labelToAdd = this.labels.find((l) => l.id === labelId);
|
||||
if (!labelToAdd) {
|
||||
return Promise.reject({ msg: `unable to find label with id ${labelId}`});
|
||||
}
|
||||
const map = this.maps.find((m) => m.id === mapId);
|
||||
if (!map) {
|
||||
return Promise.reject({ msg: `unable to find map with id ${mapId}` });
|
||||
}
|
||||
map.labels.push(labelToAdd);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
|
||||
const map = this.maps.find((m) => m.id === mapId);
|
||||
if (!map) {
|
||||
return Promise.reject({ msg: `unable to find map with id ${mapId}` });
|
||||
}
|
||||
map.labels = map.labels.filter((l) => l.id !== labelId);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import Client, {
|
||||
ImportMapInfo,
|
||||
Permission,
|
||||
} from '..';
|
||||
import { LocaleCode, localeFromStr, Locales } from '../../app-i18n';
|
||||
import { LocaleCode, localeFromStr } from '../../app-i18n';
|
||||
|
||||
export default class RestClient implements Client {
|
||||
private baseUrl: string;
|
||||
@ -184,7 +184,7 @@ export default class RestClient implements Client {
|
||||
`${this.baseUrl}/c/restful/maps?title=${model.title}&description=${model.description ? model.description : ''
|
||||
}`,
|
||||
model.content,
|
||||
{ headers: { 'Content-Type': model.contentType } }
|
||||
{ headers: { 'Content-Type': 'application/xml' } }
|
||||
)
|
||||
.then((response) => {
|
||||
const mapId = response.headers.resourceid;
|
||||
@ -214,7 +214,7 @@ export default class RestClient implements Client {
|
||||
lastname: account.lastname ? account.lastname : '',
|
||||
firstname: account.firstname ? account.firstname : '',
|
||||
email: account.email,
|
||||
locale: locale ? localeFromStr(locale) : Locales.EN,
|
||||
locale: locale ? localeFromStr(locale) : undefined,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -510,10 +510,59 @@ export default class RestClient implements Client {
|
||||
return new Promise(handler);
|
||||
}
|
||||
|
||||
createLabel(title: string, color: string): Promise<number> {
|
||||
const handler = (success: (labelId: number) => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
.post(`${this.baseUrl}/c/restful/labels`, JSON.stringify({ title, color, iconName: 'smile' }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.then((response) => {
|
||||
success(response.headers.resourceid);
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorInfo = this.parseResponseOnError(error.response);
|
||||
reject(errorInfo);
|
||||
});
|
||||
};
|
||||
return new Promise(handler);
|
||||
}
|
||||
|
||||
deleteLabel(id: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
.delete(`${this.baseUrl}/c/restful/label/${id}`)
|
||||
.delete(`${this.baseUrl}/c/restful/labels/${id}`)
|
||||
.then(() => {
|
||||
success();
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorInfo = this.parseResponseOnError(error.response);
|
||||
reject(errorInfo);
|
||||
});
|
||||
};
|
||||
return new Promise(handler);
|
||||
}
|
||||
|
||||
addLabelToMap(labelId: number, mapId: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
.post(`${this.baseUrl}/c/restful/maps/${mapId}/labels`, JSON.stringify(labelId), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.then(() => {
|
||||
success();
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorInfo = this.parseResponseOnError(error.response);
|
||||
reject(errorInfo);
|
||||
});
|
||||
};
|
||||
return new Promise(handler);
|
||||
}
|
||||
|
||||
deleteLabelFromMap(labelId: number, mapId: number): Promise<void> {
|
||||
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
|
||||
axios
|
||||
.delete(`${this.baseUrl}/c/restful/maps/${mapId}/labels/${labelId}`)
|
||||
.then(() => {
|
||||
success();
|
||||
})
|
||||
|
@ -13,8 +13,8 @@ const EditorPage = ({ mapId, ...props }: EditorPropsType): React.ReactElement =>
|
||||
const [activeDialog, setActiveDialog] = React.useState<ActionType | null>(null);
|
||||
|
||||
// Load user locale ...
|
||||
const appi18n = new AppI18n();
|
||||
const userLocale = appi18n.getUserLocale();
|
||||
const userLocale = AppI18n.getUserLocale();
|
||||
console.log("Locale:" + userLocale.code);
|
||||
|
||||
return <>
|
||||
<Editor {...props} onAction={setActiveDialog} locale={userLocale.code} />
|
||||
|
@ -13,6 +13,8 @@ type InputProps = {
|
||||
autoComplete?: string;
|
||||
fullWidth?: boolean;
|
||||
disabled?: boolean;
|
||||
maxLength?: number,
|
||||
rows?: number
|
||||
};
|
||||
|
||||
const Input = ({
|
||||
@ -26,6 +28,7 @@ const Input = ({
|
||||
autoComplete,
|
||||
fullWidth = true,
|
||||
disabled = false,
|
||||
maxLength = 254,
|
||||
}: InputProps): React.ReactElement => {
|
||||
const fieldError = error?.fields?.[name];
|
||||
return (
|
||||
@ -43,6 +46,7 @@ const Input = ({
|
||||
margin="dense"
|
||||
disabled={disabled}
|
||||
autoComplete={autoComplete}
|
||||
inputProps={{ maxLength: maxLength }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -86,6 +86,7 @@ const CreateDialog = ({ onClose }: CreateProps): React.ReactElement => {
|
||||
onChange={handleOnChange}
|
||||
error={error}
|
||||
fullWidth={true}
|
||||
maxLength={60}
|
||||
/>
|
||||
|
||||
<Input
|
||||
@ -99,6 +100,7 @@ const CreateDialog = ({ onClose }: CreateProps): React.ReactElement => {
|
||||
onChange={handleOnChange}
|
||||
required={false}
|
||||
fullWidth={true}
|
||||
rows={3}
|
||||
/>
|
||||
</FormControl>
|
||||
</BaseDialog>
|
||||
|
@ -4,20 +4,15 @@ import { useMutation, useQueryClient } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Client from '../../../../classes/client';
|
||||
import { activeInstance } from '../../../../redux/clientSlice';
|
||||
import { handleOnMutationSuccess } from '..';
|
||||
import { handleOnMutationSuccess, MultiDialogProps } from '..';
|
||||
import BaseDialog from '../base-dialog';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import AlertTitle from '@mui/material/AlertTitle';
|
||||
|
||||
export type DeleteMultiselectDialogProps = {
|
||||
mapsId: number[];
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const DeleteMultiselectDialog = ({
|
||||
onClose,
|
||||
mapsId,
|
||||
}: DeleteMultiselectDialogProps): React.ReactElement => {
|
||||
}: MultiDialogProps): React.ReactElement => {
|
||||
const intl = useIntl();
|
||||
const client: Client = useSelector(activeInstance);
|
||||
const queryClient = useQueryClient();
|
||||
|
@ -210,15 +210,15 @@ const ExportDialog = ({
|
||||
value={exportFormat}
|
||||
className={classes.select}
|
||||
>
|
||||
<MenuItem className={classes.select} value="xls">
|
||||
Microsoft Excel (XLS)
|
||||
</MenuItem>
|
||||
<MenuItem className={classes.select} value="txt">
|
||||
Plain Text File (TXT)
|
||||
</MenuItem>
|
||||
<MenuItem className={classes.select} value="md">
|
||||
Markdown (MD)
|
||||
</MenuItem>
|
||||
{/* <MenuItem className={classes.select} value="xls">
|
||||
Microsoft Excel (XLS)
|
||||
</MenuItem> */}
|
||||
</Select>
|
||||
)}
|
||||
</FormControl>
|
||||
@ -248,9 +248,9 @@ const ExportDialog = ({
|
||||
<MenuItem className={classes.select} value="mm">
|
||||
Freemind 1.0.1 (MM)
|
||||
</MenuItem>
|
||||
<MenuItem className={classes.select} value="mmap">
|
||||
{/* <MenuItem className={classes.select} value="mmap">
|
||||
MindManager (MMAP)
|
||||
</MenuItem>
|
||||
</MenuItem> */}
|
||||
</Select>
|
||||
)}
|
||||
</FormControl>
|
||||
|
@ -105,7 +105,7 @@ const ImportDialog = ({ onClose }: CreateProps): React.ReactElement => {
|
||||
description={intl.formatMessage({
|
||||
id: 'import.description',
|
||||
defaultMessage:
|
||||
'You can import FreeMind 1.0.1 and WiseMapping maps to your list of maps. Select the file you want to import.',
|
||||
'You can import WiseMapping maps to your list of maps. Select the file you want to import.',
|
||||
})}
|
||||
submitButton={intl.formatMessage({ id: 'import.button', defaultMessage: 'Create' })}
|
||||
>
|
||||
|
@ -12,6 +12,7 @@ import InfoDialog from './info-dialog';
|
||||
import DeleteMultiselectDialog from './delete-multiselect-dialog';
|
||||
import ExportDialog from './export-dialog';
|
||||
import ShareDialog from './share-dialog';
|
||||
import LabelDialog from './label-dialog';
|
||||
|
||||
export type BasicMapInfo = {
|
||||
name: string;
|
||||
@ -61,6 +62,7 @@ const ActionDispatcher = ({ mapsId, action, onClose, fromEditor }: ActionDialogP
|
||||
<ExportDialog onClose={handleOnClose} mapId={mapsId[0]} enableImgExport={fromEditor} />
|
||||
)}
|
||||
{action === 'share' && <ShareDialog onClose={handleOnClose} mapId={mapsId[0]} />}
|
||||
{action === 'label' && <LabelDialog onClose={handleOnClose} mapsId={mapsId} />}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@ -79,4 +81,9 @@ export type SimpleDialogProps = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export type MultiDialogProps = {
|
||||
mapsId: number[];
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export default ActionDispatcher;
|
||||
|
@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import { useStyles } from './style';
|
||||
import { MultiDialogProps } from '..';
|
||||
import BaseDialog from '../base-dialog';
|
||||
import Client, { ErrorInfo, Label, MapInfo } from '../../../../classes/client';
|
||||
import { LabelSelector } from '../../maps-list/label-selector';
|
||||
import { activeInstance } from '../../../../redux/clientSlice';
|
||||
import { ChangeLabelMutationFunctionParam, getChangeLabelMutationFunction } from '../../maps-list';
|
||||
|
||||
|
||||
const LabelDialog = ({ mapsId, onClose }: MultiDialogProps): React.ReactElement => {
|
||||
const intl = useIntl();
|
||||
const classes = useStyles();
|
||||
const client: Client = useSelector(activeInstance);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// TODO: pass down map data instead of using query?
|
||||
const { data } = useQuery<unknown, ErrorInfo, MapInfo[]>('maps', () => {
|
||||
return client.fetchAllMaps();
|
||||
});
|
||||
|
||||
const maps = data.filter(m => mapsId.includes(m.id));
|
||||
|
||||
const changeLabelMutation = useMutation<void, ErrorInfo, ChangeLabelMutationFunctionParam, number>(
|
||||
getChangeLabelMutationFunction(client),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries('maps');
|
||||
queryClient.invalidateQueries('labels');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const handleChangesInLabels = (label: Label, checked: boolean) => {
|
||||
changeLabelMutation.mutate({
|
||||
maps,
|
||||
label,
|
||||
checked
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BaseDialog
|
||||
onClose={onClose}
|
||||
title={intl.formatMessage({
|
||||
id: 'label.title',
|
||||
defaultMessage: 'Add a label',
|
||||
})}
|
||||
description={intl.formatMessage({
|
||||
id: 'label.description',
|
||||
defaultMessage:
|
||||
'Use labels to organize your maps.',
|
||||
})}
|
||||
PaperProps={{ classes: { root: classes.paper } }}
|
||||
>
|
||||
<>
|
||||
<Typography variant="body2" marginTop="10px">
|
||||
<FormattedMessage id="label.add-for" defaultMessage="Editing labels for maps: " />
|
||||
{ maps.map(m => m.title).join(', ') }
|
||||
</Typography>
|
||||
<LabelSelector onChange={handleChangesInLabels} maps={maps} />
|
||||
</>
|
||||
</BaseDialog>
|
||||
</div>);
|
||||
};
|
||||
|
||||
export default LabelDialog;
|
@ -0,0 +1,10 @@
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
|
||||
export const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
paper: {
|
||||
maxWidth: '420px',
|
||||
},
|
||||
})
|
||||
);
|
@ -7,7 +7,7 @@ import List from '@mui/material/List';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import { useStyles } from './style';
|
||||
import { MapsList } from './maps-list';
|
||||
import { createIntl, createIntlCache, FormattedMessage, IntlProvider, IntlShape, useIntl } from 'react-intl';
|
||||
import { createIntl, createIntlCache, FormattedMessage, IntlProvider } from 'react-intl';
|
||||
import { useQuery, useMutation, useQueryClient } from 'react-query';
|
||||
import { activeInstance } from '../../redux/clientSlice';
|
||||
import { useSelector } from 'react-redux';
|
||||
@ -40,6 +40,7 @@ import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction';
|
||||
|
||||
import logoIcon from './logo-small.svg';
|
||||
import poweredByIcon from './pwrdby-white.svg';
|
||||
import LabelDeleteConfirm from './maps-list/label-delete-confirm';
|
||||
|
||||
export type Filter = GenericFilter | LabelFilter;
|
||||
|
||||
@ -64,10 +65,9 @@ const MapsPage = (): ReactElement => {
|
||||
const client: Client = useSelector(activeInstance);
|
||||
const queryClient = useQueryClient();
|
||||
const [activeDialog, setActiveDialog] = React.useState<ActionType | undefined>(undefined);
|
||||
|
||||
const [labelToDelete, setLabelToDelete] = React.useState<number | null>(null);
|
||||
// Reload based on user preference ...
|
||||
const appi18n = new AppI18n();
|
||||
const userLocale = appi18n.getUserLocale();
|
||||
const userLocale = AppI18n.getUserLocale();
|
||||
|
||||
const cache = createIntlCache();
|
||||
const intl = createIntl({
|
||||
@ -77,7 +77,6 @@ const MapsPage = (): ReactElement => {
|
||||
}, cache)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
document.title = intl.formatMessage({
|
||||
id: 'maps.page-title',
|
||||
defaultMessage: 'My Maps | WiseMapping',
|
||||
@ -85,7 +84,10 @@ const MapsPage = (): ReactElement => {
|
||||
}, []);
|
||||
|
||||
const mutation = useMutation((id: number) => client.deleteLabel(id), {
|
||||
onSuccess: () => queryClient.invalidateQueries('labels'),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries('labels');
|
||||
queryClient.invalidateQueries('maps');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(`Unexpected error ${error}`);
|
||||
},
|
||||
@ -238,7 +240,7 @@ const MapsPage = (): ReactElement => {
|
||||
filter={buttonInfo.filter}
|
||||
active={filter}
|
||||
onClick={handleMenuClick}
|
||||
onDelete={handleLabelDelete}
|
||||
onDelete={setLabelToDelete}
|
||||
key={`${buttonInfo.filter.type}:${buttonInfo.label}`}
|
||||
/>
|
||||
);
|
||||
@ -259,6 +261,14 @@ const MapsPage = (): ReactElement => {
|
||||
<MapsList filter={filter} />
|
||||
</main>
|
||||
</div>
|
||||
{ labelToDelete && <LabelDeleteConfirm
|
||||
onClose={() => setLabelToDelete(null)}
|
||||
onConfirm={() => {
|
||||
handleLabelDelete(labelToDelete);
|
||||
setLabelToDelete(null);
|
||||
}}
|
||||
label={labels.find(l => l.id === labelToDelete)}
|
||||
/> }
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
@ -3,9 +3,9 @@ import React from 'react';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import Client from '../../../classes/client';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { activeInstance, fetchAccount } from '../../../redux/clientSlice';
|
||||
import { activeInstance } from '../../../redux/clientSlice';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { LocaleCode, Locales } from '../../../classes/app-i18n';
|
||||
import AppI18n, { LocaleCode, Locales } from '../../../classes/app-i18n';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import Button from '@mui/material/Button';
|
||||
import Menu from '@mui/material/Menu';
|
||||
@ -49,7 +49,7 @@ const LanguageMenu = (): React.ReactElement => {
|
||||
mutation.mutate(localeCode);
|
||||
};
|
||||
|
||||
const accountInfo = fetchAccount();
|
||||
const userLocale = AppI18n.getUserLocale();
|
||||
return (
|
||||
<span>
|
||||
<Tooltip
|
||||
@ -68,7 +68,7 @@ const LanguageMenu = (): React.ReactElement => {
|
||||
onClick={handleMenu}
|
||||
startIcon={<TranslateTwoTone style={{ color: 'inherit' }} />}
|
||||
>
|
||||
{accountInfo?.locale?.label}
|
||||
{userLocale.label}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
|
@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import Popover from '@mui/material/Popover';
|
||||
import Button from '@mui/material/Button';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import LabelTwoTone from '@mui/icons-material/LabelTwoTone';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { Label } from '../../../../classes/client';
|
||||
import { LabelSelector } from '../label-selector';
|
||||
|
||||
type AddLabelButtonTypes = {
|
||||
onChange?: (label: Label) => void;
|
||||
};
|
||||
|
||||
export function AddLabelButton({ onChange }: AddLabelButtonTypes): React.ReactElement {
|
||||
console.log(onChange);
|
||||
const intl = useIntl();
|
||||
|
||||
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null);
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const id = open ? 'add-label-popover' : undefined;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
id: 'map.tooltip-add',
|
||||
defaultMessage: 'Add label to selected',
|
||||
})}
|
||||
>
|
||||
<>
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="outlined"
|
||||
type="button"
|
||||
style={{ marginLeft: '10px' }}
|
||||
disableElevation={true}
|
||||
startIcon={<LabelTwoTone />}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<FormattedMessage id="action.label" defaultMessage="Add Label" />
|
||||
</Button>
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
}}
|
||||
>
|
||||
<LabelSelector />
|
||||
</Popover>
|
||||
</>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import TextField from '@mui/material/TextField';
|
||||
|
||||
import { Label } from '../../../../classes/client';
|
||||
import { StyledButton, NewLabelContainer, NewLabelColor, CreateLabel } from './styled';
|
||||
import { Tooltip } from '@mui/material';
|
||||
|
||||
const labelColors = [
|
||||
'#00b327',
|
||||
'#0565ff',
|
||||
'#2d2dd6',
|
||||
'#6a00ba',
|
||||
'#ad1599',
|
||||
'#ff1e35',
|
||||
'#ff6600',
|
||||
'#ffff47',
|
||||
];
|
||||
|
||||
type AddLabelFormProps = {
|
||||
onAdd: (newLabel: Label) => void;
|
||||
};
|
||||
|
||||
export default function AddLabelForm({ onAdd }: AddLabelFormProps): React.ReactElement {
|
||||
const intl = useIntl();
|
||||
const [createLabelColorIndex, setCreateLabelColorIndex] = React.useState(
|
||||
Math.floor(Math.random() * labelColors.length)
|
||||
);
|
||||
const [newLabelTitle, setNewLabelTitle] = React.useState('');
|
||||
|
||||
const newLabelColor = labelColors[createLabelColorIndex];
|
||||
|
||||
const setNextLabelColorIndex = () => {
|
||||
const nextIndex = labelColors[createLabelColorIndex + 1] ? createLabelColorIndex + 1 : 0;
|
||||
setCreateLabelColorIndex(nextIndex);
|
||||
};
|
||||
|
||||
const handleSubmitNew = () => {
|
||||
onAdd({
|
||||
title: newLabelTitle,
|
||||
color: newLabelColor,
|
||||
id: 0,
|
||||
});
|
||||
setNewLabelTitle('');
|
||||
setNextLabelColorIndex();
|
||||
};
|
||||
|
||||
return (
|
||||
<CreateLabel>
|
||||
<NewLabelContainer>
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
id: 'label.change-color',
|
||||
defaultMessage: 'Change label color',
|
||||
})}
|
||||
>
|
||||
<NewLabelColor
|
||||
htmlColor={newLabelColor}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setNextLabelColorIndex();
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<TextField
|
||||
variant="standard"
|
||||
label={intl.formatMessage({
|
||||
id: 'label.add-placeholder',
|
||||
defaultMessage: 'Label title',
|
||||
})}
|
||||
onChange={(e) => setNewLabelTitle(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSubmitNew();
|
||||
}
|
||||
}}
|
||||
value={newLabelTitle}
|
||||
/>
|
||||
<StyledButton
|
||||
onClick={() => handleSubmitNew()}
|
||||
disabled={!newLabelTitle.length}
|
||||
aria-label={intl.formatMessage({
|
||||
id: 'label.add-button',
|
||||
defaultMessage: 'Add label',
|
||||
})}
|
||||
>
|
||||
<AddIcon />
|
||||
</StyledButton>
|
||||
</NewLabelContainer>
|
||||
</CreateLabel>
|
||||
);
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import styled from 'styled-components';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import LabelTwoTone from '@mui/icons-material/LabelTwoTone';
|
||||
|
||||
export const StyledButton = styled(IconButton)`
|
||||
margin: 4px;
|
||||
`;
|
||||
|
||||
export const NewLabelContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const NewLabelColor = styled(LabelTwoTone)`
|
||||
margin-right: 12px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const CreateLabel = styled.div`
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
`;
|
@ -2,7 +2,7 @@ import React, { useEffect, CSSProperties } from 'react';
|
||||
|
||||
import { useStyles } from './styled';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { activeInstance, fetchAccount } from '../../../redux/clientSlice';
|
||||
import { activeInstance } from '../../../redux/clientSlice';
|
||||
import { useMutation, useQuery, useQueryClient } from 'react-query';
|
||||
import Client, { ErrorInfo, Label, MapInfo } from '../../../classes/client';
|
||||
import ActionChooser, { ActionType } from '../action-chooser';
|
||||
@ -33,12 +33,13 @@ import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
import StarRateRoundedIcon from '@mui/icons-material/StarRateRounded';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
|
||||
import { AddLabelButton } from './add-label-button';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { LabelsCell } from './labels-cell';
|
||||
import LocalizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import AppI18n from '../../../classes/app-i18n';
|
||||
import LabelTwoTone from '@mui/icons-material/LabelTwoTone';
|
||||
|
||||
dayjs.extend(LocalizedFormat)
|
||||
dayjs.extend(LocalizedFormat);
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||
@ -235,6 +236,24 @@ const mapsFilter = (filter: Filter, search: string): ((mapInfo: MapInfo) => bool
|
||||
};
|
||||
};
|
||||
|
||||
export type ChangeLabelMutationFunctionParam = { maps: MapInfo[]; label: Label; checked: boolean };
|
||||
|
||||
export const getChangeLabelMutationFunction =
|
||||
(client: Client) =>
|
||||
async ({ maps, label, checked }: ChangeLabelMutationFunctionParam): Promise<void> => {
|
||||
if (!label.id) {
|
||||
label.id = await client.createLabel(label.title, label.color);
|
||||
}
|
||||
if (checked) {
|
||||
const toAdd = maps.filter((m) => !m.labels.find((l) => l.id === label.id));
|
||||
await Promise.all(toAdd.map((m) => client.addLabelToMap(label.id, m.id)));
|
||||
} else {
|
||||
const toRemove = maps.filter((m) => m.labels.find((l) => l.id === label.id));
|
||||
await Promise.all(toRemove.map((m) => client.deleteLabelFromMap(label.id, m.id)));
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
const classes = useStyles();
|
||||
const [order, setOrder] = React.useState<Order>('desc');
|
||||
@ -251,10 +270,8 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Configure locale ...
|
||||
const account = fetchAccount();
|
||||
if (account) {
|
||||
dayjs.locale(account.locale.code);
|
||||
}
|
||||
const userLocale = AppI18n.getUserLocale();
|
||||
dayjs.locale(userLocale.code);
|
||||
|
||||
useEffect(() => {
|
||||
setSelected([]);
|
||||
@ -331,7 +348,7 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
};
|
||||
9;
|
||||
|
||||
const starredMultation = useMutation<void, ErrorInfo, number>(
|
||||
(id: number) => {
|
||||
const map = mapsInfo.find((m) => m.id == id);
|
||||
@ -385,6 +402,36 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddLabelClick = () => {
|
||||
setActiveDialog({
|
||||
actionType: 'label',
|
||||
mapsId: selected,
|
||||
});
|
||||
};
|
||||
|
||||
const removeLabelMultation = useMutation<
|
||||
void,
|
||||
ErrorInfo,
|
||||
{ mapId: number; labelId: number },
|
||||
number
|
||||
>(
|
||||
({ mapId, labelId }) => {
|
||||
return client.deleteLabelFromMap(labelId, mapId);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries('maps');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const handleRemoveLabel = (mapId: number, labelId: number) => {
|
||||
removeLabelMultation.mutate({ mapId, labelId });
|
||||
};
|
||||
|
||||
const isSelected = (id: number) => selected.indexOf(id) !== -1;
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
@ -419,7 +466,31 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{selected.length > 0 && <AddLabelButton />}
|
||||
{selected.length > 0 && (
|
||||
<Tooltip
|
||||
arrow={true}
|
||||
title={intl.formatMessage({
|
||||
id: 'map.tooltip-add',
|
||||
defaultMessage: 'Add label to selected',
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
color="primary"
|
||||
size="medium"
|
||||
variant="outlined"
|
||||
type="button"
|
||||
style={{ marginLeft: '10px' }}
|
||||
disableElevation={true}
|
||||
startIcon={<LabelTwoTone />}
|
||||
onClick={handleAddLabelClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="action.label"
|
||||
defaultMessage="Add Label"
|
||||
/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={classes.toolbarListActions}>
|
||||
@ -560,8 +631,13 @@ export const MapsList = (props: MapsListProps): React.ReactElement => {
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}>
|
||||
<LabelsCell labels={row.labels} />
|
||||
<TableCell className={[classes.bodyCell, classes.labelsCell].join(' ')}>
|
||||
<LabelsCell
|
||||
labels={row.labels}
|
||||
onDelete={(lbl) => {
|
||||
handleRemoveLabel(row.id, lbl.id);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell className={classes.bodyCell}>
|
||||
|
@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import AlertTitle from '@mui/material/AlertTitle';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import BaseDialog from '../../action-dispatcher/base-dialog';
|
||||
import { Label } from '../../../../classes/client';
|
||||
|
||||
export type LabelDeleteConfirmType = {
|
||||
label: Label;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
const LabelDeleteConfirm = ({ label, onClose, onConfirm }: LabelDeleteConfirmType): React.ReactElement => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BaseDialog
|
||||
onClose={onClose}
|
||||
onSubmit={onConfirm}
|
||||
title={intl.formatMessage({ id: 'label.delete-title', defaultMessage: 'Confirm label deletion' })}
|
||||
submitButton={intl.formatMessage({
|
||||
id: 'action.delete-title',
|
||||
defaultMessage: 'Delete',
|
||||
})}
|
||||
>
|
||||
<Alert severity="warning">
|
||||
<AlertTitle>{intl.formatMessage({ id: 'label.delete-title', defaultMessage: 'Confirm label deletion' })}</AlertTitle>
|
||||
<span>
|
||||
<Typography fontWeight="bold" component="span">{label.title} </Typography>
|
||||
<FormattedMessage
|
||||
id="label.delete-description"
|
||||
defaultMessage="will be deleted, including its associations to all existing maps. Do you want to continue?"
|
||||
/>
|
||||
</span>
|
||||
</Alert>
|
||||
</BaseDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LabelDeleteConfirm;
|
@ -1,58 +1,55 @@
|
||||
import React from 'react';
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import Container from '@mui/material/Container';
|
||||
import { Label as LabelComponent } from '../label';
|
||||
import Client, { Label, ErrorInfo } from '../../../../classes/client';
|
||||
import LabelComponent from '../label';
|
||||
import Client, { Label, ErrorInfo, MapInfo } from '../../../../classes/client';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { activeInstance } from '../../../../redux/clientSlice';
|
||||
import { StyledButton } from './styled';
|
||||
import AddLabelForm from '../add-label-form';
|
||||
import { LabelListContainer } from './styled';
|
||||
|
||||
export function LabelSelector(): React.ReactElement {
|
||||
const client: Client = useSelector(activeInstance);
|
||||
export type LabelSelectorProps = {
|
||||
maps: MapInfo[];
|
||||
onChange: (label: Label, checked: boolean) => void;
|
||||
};
|
||||
|
||||
const { data: labels = [] } = useQuery<unknown, ErrorInfo, Label[]>('labels', async () => client.fetchLabels());
|
||||
export function LabelSelector({ onChange, maps }: LabelSelectorProps): React.ReactElement {
|
||||
const client: Client = useSelector(activeInstance);
|
||||
const { data: labels = [] } = useQuery<unknown, ErrorInfo, Label[]>('labels', async () =>
|
||||
client.fetchLabels()
|
||||
);
|
||||
|
||||
const [state, setState] = React.useState(labels.reduce((acc, label) => {
|
||||
acc[label.id] = false //label.checked;
|
||||
return acc;
|
||||
}, {}),);
|
||||
const checkedLabelIds = labels
|
||||
.map((l) => l.id)
|
||||
.filter((labelId) => maps.every((m) => m.labels.find((l) => l.id === labelId)));
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setState({ ...state, [event.target.id]: event.target.checked });
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<FormGroup>
|
||||
{labels.map(({ id, title, color}) => (
|
||||
<FormControlLabel
|
||||
key={id}
|
||||
control={
|
||||
<Checkbox
|
||||
id={`${id}`}
|
||||
checked={state[id]}
|
||||
onChange={handleChange}
|
||||
name={title}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label={<LabelComponent name={title} color={color} />}
|
||||
/>
|
||||
))}
|
||||
<Divider />
|
||||
<StyledButton
|
||||
color="primary"
|
||||
startIcon={<AddIcon />}
|
||||
>
|
||||
{/* i18n */}
|
||||
Add new label
|
||||
</StyledButton>
|
||||
</FormGroup>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
<FormGroup>
|
||||
<AddLabelForm onAdd={(label) => onChange(label, true)} />
|
||||
</FormGroup>
|
||||
<LabelListContainer>
|
||||
{labels.map(({ id, title, color }) => (
|
||||
<FormControlLabel
|
||||
key={id}
|
||||
control={
|
||||
<Checkbox
|
||||
id={`${id}`}
|
||||
checked={checkedLabelIds.includes(id)}
|
||||
onChange={(e) => {
|
||||
onChange({ id, title, color }, e.target.checked);
|
||||
}}
|
||||
name={title}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label={<LabelComponent label={{ id, title, color }} size="big" />}
|
||||
/>
|
||||
))}
|
||||
</LabelListContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import FormGroup from '@mui/material/FormGroup';
|
||||
import styled from 'styled-components';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
export const StyledButton = styled(Button)`
|
||||
margin: 4px;
|
||||
`;
|
||||
export const LabelListContainer = styled(FormGroup)`
|
||||
max-height: 400px;
|
||||
flex-wrap: nowrap;
|
||||
overflow-y: scroll;
|
||||
`;
|
@ -1,13 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Color, StyledLabel, Name } from './styled';
|
||||
import { LabelContainer, LabelText } from './styled';
|
||||
|
||||
type Props = { name: string, color: string };
|
||||
import { Label } from '../../../../classes/client';
|
||||
import LabelTwoTone from '@mui/icons-material/LabelTwoTone';
|
||||
import DeleteIcon from '@mui/icons-material/Clear';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
|
||||
export function Label({ name, color }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledLabel>
|
||||
<Color color={color} />
|
||||
<Name>{name}</Name>
|
||||
</StyledLabel>
|
||||
);
|
||||
|
||||
type LabelSize = 'small' | 'big';
|
||||
type LabelComponentProps = { label: Label; onDelete?: (label: Label) => void; size?: LabelSize };
|
||||
|
||||
export default function LabelComponent({ label, onDelete, size = 'small' }: LabelComponentProps): React.ReactElement<LabelComponentProps> {
|
||||
const iconSize = size === 'small' ? {
|
||||
height: '0.6em', width: '0.6em'
|
||||
} : { height: '0.9em', width: '0.9em' };
|
||||
|
||||
return (
|
||||
<LabelContainer color={label.color}>
|
||||
<LabelTwoTone htmlColor={label.color} style={iconSize} />
|
||||
<LabelText>{label.title}</LabelText>
|
||||
{onDelete && (
|
||||
<IconButton
|
||||
color="default"
|
||||
size="small"
|
||||
aria-label="delete tag"
|
||||
component="span"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(label);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon style={iconSize} />
|
||||
</IconButton>
|
||||
)}
|
||||
</LabelContainer>
|
||||
);
|
||||
}
|
||||
|
@ -1,23 +1,15 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const SIZE = 20;
|
||||
|
||||
export const Color = styled.div`
|
||||
width: ${SIZE}px;
|
||||
height: ${SIZE}px;
|
||||
border-radius: ${SIZE * 0.25}px;
|
||||
border: 1px solid black;
|
||||
margin: 1px ${SIZE * 0.5}px 1px 0px;
|
||||
${props => props.color && css`
|
||||
background-color: ${props.color};
|
||||
`}
|
||||
`;
|
||||
|
||||
export const StyledLabel = styled.div`
|
||||
display: flex;
|
||||
export const LabelContainer = styled.div`
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
margin: 4px;
|
||||
padding: 4px;
|
||||
align-items: center;
|
||||
font-size: smaller;
|
||||
`;
|
||||
|
||||
export const Name = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const LabelText = styled.span`
|
||||
margin-left: 4px;
|
||||
margin-right: 2px;
|
||||
`;
|
@ -1,26 +1,35 @@
|
||||
import React from 'react';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import { LabelContainer, LabelText } from './styled';
|
||||
|
||||
import { Label } from '../../../../classes/client';
|
||||
import LabelTwoTone from '@mui/icons-material/LabelTwoTone';
|
||||
import DeleteIcon from '@mui/icons-material/Clear';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
|
||||
type Props = {
|
||||
labels: Label[],
|
||||
onDelete: (label: Label) => void,
|
||||
};
|
||||
|
||||
export function LabelsCell({ labels }: Props): React.ReactElement<Props> {
|
||||
export function LabelsCell({ labels, onDelete }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<>
|
||||
{labels.map(label => (
|
||||
<Chip
|
||||
<LabelContainer
|
||||
key={label.id}
|
||||
size="small"
|
||||
icon={<LabelTwoTone />}
|
||||
label={label.title}
|
||||
clickable
|
||||
color="primary"
|
||||
style={{ backgroundColor: label.color, opacity: '0.75' }}
|
||||
onDelete={() => { return 1; }}
|
||||
/>
|
||||
color={label.color}
|
||||
>
|
||||
<LabelTwoTone htmlColor={label.color} style={{ height: '0.6em', width: '0.6em' }} />
|
||||
<LabelText>{ label.title }</LabelText>
|
||||
<IconButton color="default" size='small' aria-label="delete tag" component="span"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete(label);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon style={{ height: '0.6em', width: '0.6em' }} />
|
||||
</IconButton>
|
||||
</LabelContainer>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
@ -0,0 +1,15 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const LabelContainer = styled.div`
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
margin: 4px;
|
||||
padding: 4px;
|
||||
align-items: center;
|
||||
font-size: smaller;
|
||||
`;
|
||||
|
||||
export const LabelText = styled.span`
|
||||
margin-left: 4px;
|
||||
margin-right: 2px;
|
||||
`;
|
@ -33,6 +33,13 @@ export const useStyles = makeStyles((theme: Theme) =>
|
||||
bodyCell: {
|
||||
border: '0px',
|
||||
},
|
||||
labelsCell: {
|
||||
maxWidth: '300px',
|
||||
overflow: 'hidden',
|
||||
textAlign: 'right',
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis'
|
||||
},
|
||||
visuallyHidden: {
|
||||
border: 0,
|
||||
clip: 'rect(0 0 0 0)',
|
||||
|
@ -7,6 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
module.exports = merge(common, {
|
||||
mode: 'development',
|
||||
devtool: 'source-map',
|
||||
watch: true,
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'dist'),
|
||||
port: 3000,
|
||||
|
@ -5705,7 +5705,7 @@ dateformat@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
|
||||
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
|
||||
|
||||
dayjs@^1.10.4:
|
||||
dayjs@^1.10.4, dayjs@^1.10.7:
|
||||
version "1.10.7"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
|
||||
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
|
||||
|
Loading…
Reference in New Issue
Block a user