Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,34 @@ patchmap.fit('group-id-1')
patchmap.fit('grid-1')

// Fit on objects with ids 'item-1' and 'item-2'
patchmap.fit(['item-1', 'item-2'])
patchmap.fit(['item-1', 'item-2']);
```

<br/>

### `rotation`

Rotation controller for world view. Use degrees.

```js
patchmap.rotation.value = 90;
patchmap.rotation.rotateBy(90);
patchmap.rotation.reset();
```

<br/>

### `flip`

Flip controller for world view.

```js
patchmap.flip.x = true;
patchmap.flip.y = false;
patchmap.flip.set({ x: true, y: true });
patchmap.flip.toggleX();
patchmap.flip.toggleY();
patchmap.flip.reset();
```

<br/>
Expand Down
29 changes: 28 additions & 1 deletion README_KR.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,34 @@ patchmap.fit('group-id-1')
patchmap.fit('grid-1')

// id가 'item-1'과 'item-2'인 객체들을 기준으로 fit
patchmap.fit(['item-1', 'item-2'])
patchmap.fit(['item-1', 'item-2']);
```

<br/>

### `rotation`

월드 뷰 회전을 제어하는 컨트롤러입니다. 각도는 degrees 기준입니다.

```js
patchmap.rotation.value = 90;
patchmap.rotation.rotateBy(90);
patchmap.rotation.reset();
```

<br/>

### `flip`

월드 뷰 플립을 제어하는 컨트롤러입니다.

```js
patchmap.flip.x = true;
patchmap.flip.y = false;
patchmap.flip.set({ x: true, y: true });
patchmap.flip.toggleX();
patchmap.flip.toggleY();
patchmap.flip.reset();
```

<br/>
Expand Down
17 changes: 17 additions & 0 deletions src/display/World.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Container } from 'pixi.js';
import { canvasSchema } from './data-schema/element-schema';
import { Base } from './mixins/Base';
import { Childrenable } from './mixins/Childrenable';
import { mixins } from './mixins/utils';

const ComposedWorld = mixins(Container, Base, Childrenable);

export default class World extends ComposedWorld {
constructor(options) {
super({ type: 'canvas', ...options });
}

apply(changes, options) {
super.apply(changes, canvasSchema, options);
}
}
42 changes: 42 additions & 0 deletions src/display/components/Bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Showable } from '../mixins/Showable';
import { Sourceable } from '../mixins/Sourceable';
import { Tintable } from '../mixins/Tintable';
import { mixins } from '../mixins/utils';
import { WorldTransformable } from '../mixins/WorldTransformable';

const EXTRA_KEYS = {
PLACEMENT: ['source', 'size'],
Expand All @@ -21,20 +22,61 @@ const ComposedBar = mixins(
Tintable,
Animationable,
AnimationSizeable,
WorldTransformable,
Placementable,
);

export class Bar extends ComposedBar {
static useViewLayout = false;
static useViewPlacement = true;
static worldRotationOptions = { mode: 'readable' };
static worldTransformKeys = ['source', 'size'];

constructor(store) {
super({ type: 'bar', store, texture: Texture.WHITE });
this.useViewLayout = false;
this.useViewPlacement = true;

this.constructor.registerHandler(
EXTRA_KEYS.PLACEMENT,
this._applyPlacement,
);

this._boundOnObjectTransformed = this._onObjectTransformed.bind(this);
this.store?.viewport?.on(
'object_transformed',
this._boundOnObjectTransformed,
);
this._applyWorldTransform();
}

apply(changes, options) {
super.apply(changes, barSchema, options);
}

_onObjectTransformed(changedObject) {
if (changedObject !== this.store?.world) return;
this._applyWorldTransform();
this._applyPlacement({
placement: this.props.placement,
margin: this.props.margin,
});
this._applyAnimationSize({
animation: this.props.animation,
animationDuration: this.props.animationDuration,
source: this.props.source,
size: this.props.size,
margin: this.props.margin,
});
}

destroy(options) {
if (this.store?.viewport && this._boundOnObjectTransformed) {
this.store.viewport.off(
'object_transformed',
this._boundOnObjectTransformed,
);
}
super.destroy(options);
}
}
35 changes: 35 additions & 0 deletions src/display/components/Icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Showable } from '../mixins/Showable';
import { Sourceable } from '../mixins/Sourceable';
import { Tintable } from '../mixins/Tintable';
import { mixins } from '../mixins/utils';
import { WorldTransformable } from '../mixins/WorldTransformable';

const EXTRA_KEYS = {
PLACEMENT: ['source', 'size'],
Expand All @@ -19,20 +20,54 @@ const ComposedIcon = mixins(
Sourceable,
Tintable,
ComponentSizeable,
WorldTransformable,
Placementable,
);

export class Icon extends ComposedIcon {
static useViewLayout = false;
static useViewPlacement = true;
static worldRotationOptions = { mode: 'readable' };
static worldTransformKeys = ['source', 'size'];

constructor(store) {
super({ type: 'icon', store, texture: Texture.WHITE });
this.useViewLayout = false;
this.useViewPlacement = true;

this.constructor.registerHandler(
EXTRA_KEYS.PLACEMENT,
this._applyPlacement,
);

this._boundOnObjectTransformed = this._onObjectTransformed.bind(this);
this.store?.viewport?.on(
'object_transformed',
this._boundOnObjectTransformed,
);
this._applyWorldTransform();
}

apply(changes, options) {
super.apply(changes, iconSchema, options);
}

_onObjectTransformed(changedObject) {
if (changedObject !== this.store?.world) return;
this._applyWorldTransform();
this._applyPlacement({
placement: this.props.placement,
margin: this.props.margin,
});
}

destroy(options) {
if (this.store?.viewport && this._boundOnObjectTransformed) {
this.store.viewport.off(
'object_transformed',
this._boundOnObjectTransformed,
);
}
super.destroy(options);
}
}
37 changes: 35 additions & 2 deletions src/display/components/Text.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { Base } from '../mixins/Base';
import { Placementable } from '../mixins/Placementable';
import { Showable } from '../mixins/Showable';
import { Textable } from '../mixins/Textable';
import { TextLayoutable } from '../mixins/TextLayoutable';
import { Textstyleable } from '../mixins/Textstyleable';
import { Tintable } from '../mixins/Tintable';
import { mixins } from '../mixins/utils';
import { WorldTransformable } from '../mixins/WorldTransformable';

const EXTRA_KEYS = {
PLACEMENT: ['text', 'style', 'split'],
Expand All @@ -19,22 +19,55 @@ const ComposedText = mixins(
Showable,
Textable,
Textstyleable,
TextLayoutable,
Tintable,
WorldTransformable,
Placementable,
);

export class Text extends ComposedText {
static useViewLayout = false;
static useViewPlacement = true;
static worldRotationOptions = { mode: 'readable' };
static worldTransformKeys = ['text', 'style', 'split'];

constructor(store) {
super({ type: 'text', store, text: '' });
this.useViewLayout = false;
this.useViewPlacement = true;

this.constructor.registerHandler(
EXTRA_KEYS.PLACEMENT,
this._applyPlacement,
);

this._boundOnObjectTransformed = this._onObjectTransformed.bind(this);
this.store?.viewport?.on(
'object_transformed',
this._boundOnObjectTransformed,
);
this._applyWorldTransform();
}

apply(changes, options) {
super.apply(changes, textSchema, options);
}

_onObjectTransformed(changedObject) {
if (changedObject !== this.store?.world) return;
this._applyWorldTransform();
this._applyPlacement({
placement: this.props.placement,
margin: this.props.margin,
});
}

destroy(options) {
if (this.store?.viewport && this._boundOnObjectTransformed) {
this.store.viewport.off(
'object_transformed',
this._boundOnObjectTransformed,
);
}
super.destroy(options);
}
}
9 changes: 3 additions & 6 deletions src/display/draw.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import Element from './elements/Element';

export const draw = (store, data) => {
const { viewport } = store;
destroyChildren(viewport);
viewport.apply(
{ type: 'canvas', children: data },
{ mergeStrategy: 'replace' },
);
const root = store.world ?? store.viewport;
destroyChildren(root);
root.apply({ type: 'canvas', children: data }, { mergeStrategy: 'replace' });
};

const destroyChildren = (parent) => {
Expand Down
10 changes: 3 additions & 7 deletions src/display/mixins/Base.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const Base = (superClass) => {
if (!this.localTransform || !this.visible) return;

if (!this.localTransform.equals(this._lastLocalTransform)) {
this.store.viewport?.emit('object_transformed', this);
this.store?.viewport?.emit('object_transformed', this);
this._lastLocalTransform.copyFrom(this.localTransform);
}
}
Expand Down Expand Up @@ -81,13 +81,9 @@ export const Base = (superClass) => {
if (isValidationError(nextProps)) throw nextProps;
const actualChanges = diffReplace(this.props, nextProps) ?? {};

if (
options?.historyId &&
Object.keys(actualChanges).length > 0 &&
this.store.undoRedoManager
) {
if (options?.historyId && Object.keys(actualChanges).length > 0) {
const command = new UpdateCommand(this, changes, options);
this.store?.undoRedoManager.execute(command, options);
this.store.undoRedoManager.execute(command, options);
return;
}

Expand Down
31 changes: 24 additions & 7 deletions src/display/mixins/Childrenable.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,50 @@ export const Childrenable = (superClass) => {
let { children: childrenChanges } = relevantChanges;
const elements = [...this.children];

const overlay = this.type === 'canvas' ? this.store?.overlay : null;
const overlayElements = overlay
? [...overlay.children].filter((child) => child.type === 'relations')
: [];

const attachChild = (child, useOverlay) =>
useOverlay ? overlay.addChild(child) : this.addChild(child);
const detachChild = (child, useOverlay) =>
useOverlay ? overlay.removeChild(child) : this.removeChild(child);

childrenChanges = validateAndPrepareChanges(
elements,
[...elements, ...overlayElements],
childrenChanges,
mapDataSchema,
);

for (const childChange of childrenChanges) {
const idx = findIndexByPriority(elements, childChange);
const isOverlay = overlay && childChange.type === 'relations';
const searchElements = isOverlay ? overlayElements : elements;
const idx = findIndexByPriority(searchElements, childChange);
let element = null;

if (idx !== -1) {
element = elements[idx];
elements.splice(idx, 1);
element = searchElements[idx];
searchElements.splice(idx, 1);
if (options.mergeStrategy === 'replace') {
this.addChild(element);
attachChild(element, isOverlay);
}
} else {
element = newElement(childChange.type, this.store);
this.addChild(element);
attachChild(element, isOverlay);
}
element.apply(childChange, options);
}

if (options.mergeStrategy === 'replace') {
elements.forEach((element) => {
if (!element.type) return; // Don't remove children that are not managed by patchmap (e.g. raw PIXI objects)
this.removeChild(element);
detachChild(element, false);
element.destroy({ children: true });
});
overlayElements.forEach((element) => {
if (!element.type || !overlay) return;
detachChild(element, true);
element.destroy({ children: true });
});
}
Expand Down
Loading