mirror of
https://github.com/konvajs/konva.git
synced 2026-01-23 21:34:50 +08:00
autodraw
This commit is contained in:
@@ -3,7 +3,15 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## **NOT RELEASED V8**
|
||||||
|
|
||||||
|
- All updates on canvas will do automatic redraw with `layer.batchDraw()`. This features is configurable with `Konva.autoDrawEnbaled` property.
|
||||||
|
- Full migration to ES modules package, commonjs code is removed
|
||||||
|
- `konva-node` is merged into `konva` npm package. One package works for both environments.
|
||||||
- Fix `TextPath` recalculations on `fontSize` change
|
- Fix `TextPath` recalculations on `fontSize` change
|
||||||
|
- `Konva.Collection` is removed. `container.children` is a simple array now. `container.find()` will return also a simple array instead of `Konva.Collection()`.
|
||||||
|
- Better typescript support. Now every module has its own `*.d.ts` file.
|
||||||
|
- New method `layer.getNativeCanvasElement()`
|
||||||
|
|
||||||
## 7.2.5
|
## 7.2.5
|
||||||
|
|
||||||
|
|||||||
9256
konva.min.js
vendored
9256
konva.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -236,7 +236,7 @@ export class Animation {
|
|||||||
if (!layerHash.hasOwnProperty(key)) {
|
if (!layerHash.hasOwnProperty(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
layerHash[key].draw();
|
layerHash[key].batchDraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static _animationLoop() {
|
static _animationLoop() {
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ export abstract class Container<
|
|||||||
this._fire('add', {
|
this._fire('add', {
|
||||||
child: child,
|
child: child,
|
||||||
});
|
});
|
||||||
|
this._requestDraw();
|
||||||
// chainable
|
// chainable
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -324,6 +325,7 @@ export abstract class Container<
|
|||||||
this.children?.forEach(function (child, n) {
|
this.children?.forEach(function (child, n) {
|
||||||
child.index = n;
|
child.index = n;
|
||||||
});
|
});
|
||||||
|
this._requestDraw();
|
||||||
}
|
}
|
||||||
drawScene(can?: SceneCanvas, top?: Node) {
|
drawScene(can?: SceneCanvas, top?: Node) {
|
||||||
var layer = this.getLayer(),
|
var layer = this.getLayer(),
|
||||||
|
|||||||
@@ -97,7 +97,16 @@ export const Konva = {
|
|||||||
},
|
},
|
||||||
enableTrace: false,
|
enableTrace: false,
|
||||||
_pointerEventsEnabled: false,
|
_pointerEventsEnabled: false,
|
||||||
autoDrawEnabled: false,
|
/**
|
||||||
|
* Should Konva automatically update canvas on any changes. Default is true.
|
||||||
|
* @property autoDrawEnabled
|
||||||
|
* @default true
|
||||||
|
* @name autoDrawEnabled
|
||||||
|
* @memberof Konva
|
||||||
|
* @example
|
||||||
|
* Konva.autoDrawEnabled = true;
|
||||||
|
*/
|
||||||
|
autoDrawEnabled: true,
|
||||||
/**
|
/**
|
||||||
* Should we enable hit detection while dragging? For performance reasons, by default it is false.
|
* Should we enable hit detection while dragging? For performance reasons, by default it is false.
|
||||||
* But on some rare cases you want to see hit graph and check intersections. Just set it to true.
|
* But on some rare cases you want to see hit graph and check intersections. Just set it to true.
|
||||||
|
|||||||
28
src/Layer.ts
28
src/Layer.ts
@@ -88,7 +88,7 @@ export class Layer extends Container<Group | Shape> {
|
|||||||
* @method
|
* @method
|
||||||
* @name Konva.Layer#getCanvas
|
* @name Konva.Layer#getCanvas
|
||||||
*/
|
*/
|
||||||
getCanvasElement() {
|
getNativeCanvasElement() {
|
||||||
return this.canvas._canvas;
|
return this.canvas._canvas;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -136,15 +136,15 @@ export class Layer extends Container<Group | Shape> {
|
|||||||
super.setZIndex(index);
|
super.setZIndex(index);
|
||||||
var stage = this.getStage();
|
var stage = this.getStage();
|
||||||
if (stage && stage.content) {
|
if (stage && stage.content) {
|
||||||
stage.content.removeChild(this.getCanvasElement());
|
stage.content.removeChild(this.getNativeCanvasElement());
|
||||||
|
|
||||||
if (index < stage.children.length - 1) {
|
if (index < stage.children.length - 1) {
|
||||||
stage.content.insertBefore(
|
stage.content.insertBefore(
|
||||||
this.getCanvasElement(),
|
this.getNativeCanvasElement(),
|
||||||
stage.children[index + 1].getCanvas()._canvas
|
stage.children[index + 1].getCanvas()._canvas
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
stage.content.appendChild(this.getCanvasElement());
|
stage.content.appendChild(this.getNativeCanvasElement());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
@@ -153,8 +153,8 @@ export class Layer extends Container<Group | Shape> {
|
|||||||
Node.prototype.moveToTop.call(this);
|
Node.prototype.moveToTop.call(this);
|
||||||
var stage = this.getStage();
|
var stage = this.getStage();
|
||||||
if (stage && stage.content) {
|
if (stage && stage.content) {
|
||||||
stage.content.removeChild(this.getCanvasElement());
|
stage.content.removeChild(this.getNativeCanvasElement());
|
||||||
stage.content.appendChild(this.getCanvasElement());
|
stage.content.appendChild(this.getNativeCanvasElement());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -167,15 +167,15 @@ export class Layer extends Container<Group | Shape> {
|
|||||||
if (!stage || !stage.content) {
|
if (!stage || !stage.content) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
stage.content.removeChild(this.getCanvasElement());
|
stage.content.removeChild(this.getNativeCanvasElement());
|
||||||
|
|
||||||
if (this.index < stage.children.length - 1) {
|
if (this.index < stage.children.length - 1) {
|
||||||
stage.content.insertBefore(
|
stage.content.insertBefore(
|
||||||
this.getCanvasElement(),
|
this.getNativeCanvasElement(),
|
||||||
stage.children[this.index + 1].getCanvas()._canvas
|
stage.children[this.index + 1].getCanvas()._canvas
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
stage.content.appendChild(this.getCanvasElement());
|
stage.content.appendChild(this.getNativeCanvasElement());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -186,9 +186,9 @@ export class Layer extends Container<Group | Shape> {
|
|||||||
if (stage) {
|
if (stage) {
|
||||||
var children = stage.children;
|
var children = stage.children;
|
||||||
if (stage.content) {
|
if (stage.content) {
|
||||||
stage.content.removeChild(this.getCanvasElement());
|
stage.content.removeChild(this.getNativeCanvasElement());
|
||||||
stage.content.insertBefore(
|
stage.content.insertBefore(
|
||||||
this.getCanvasElement(),
|
this.getNativeCanvasElement(),
|
||||||
children[this.index + 1].getCanvas()._canvas
|
children[this.index + 1].getCanvas()._canvas
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -204,9 +204,9 @@ export class Layer extends Container<Group | Shape> {
|
|||||||
if (stage) {
|
if (stage) {
|
||||||
var children = stage.children;
|
var children = stage.children;
|
||||||
if (stage.content) {
|
if (stage.content) {
|
||||||
stage.content.removeChild(this.getCanvasElement());
|
stage.content.removeChild(this.getNativeCanvasElement());
|
||||||
stage.content.insertBefore(
|
stage.content.insertBefore(
|
||||||
this.getCanvasElement(),
|
this.getNativeCanvasElement(),
|
||||||
children[1].getCanvas()._canvas
|
children[1].getCanvas()._canvas
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ export class Layer extends Container<Group | Shape> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
remove() {
|
remove() {
|
||||||
var _canvas = this.getCanvasElement();
|
var _canvas = this.getNativeCanvasElement();
|
||||||
|
|
||||||
Node.prototype.remove.call(this);
|
Node.prototype.remove.call(this);
|
||||||
|
|
||||||
|
|||||||
14
src/Node.ts
14
src/Node.ts
@@ -303,6 +303,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
|||||||
clearCache() {
|
clearCache() {
|
||||||
this._cache.delete(CANVAS);
|
this._cache.delete(CANVAS);
|
||||||
this._clearSelfAndDescendantCache();
|
this._clearSelfAndDescendantCache();
|
||||||
|
this._requestDraw();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -463,6 +464,8 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
|||||||
y: y,
|
y: y,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._requestDraw();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2219,6 +2222,12 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
|||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
_requestDraw() {
|
||||||
|
if (Konva.autoDrawEnabled) {
|
||||||
|
const drawNode = this.getLayer() || this.getStage();
|
||||||
|
drawNode?.batchDraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
_setAttr(key, val, skipFire = false) {
|
_setAttr(key, val, skipFire = false) {
|
||||||
var oldVal = this.attrs[key];
|
var oldVal = this.attrs[key];
|
||||||
if (oldVal === val && !Util.isObject(val)) {
|
if (oldVal === val && !Util.isObject(val)) {
|
||||||
@@ -2232,10 +2241,7 @@ export abstract class Node<Config extends NodeConfig = NodeConfig> {
|
|||||||
if (this._shouldFireChangeEvents) {
|
if (this._shouldFireChangeEvents) {
|
||||||
this._fireChangeEvent(key, oldVal, val);
|
this._fireChangeEvent(key, oldVal, val);
|
||||||
}
|
}
|
||||||
if (Konva.autoDrawEnabled) {
|
this._requestDraw();
|
||||||
const drawNode = this.getLayer() || this.getStage();
|
|
||||||
drawNode?.batchDraw();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_setComponentAttr(key, component, val) {
|
_setComponentAttr(key, component, val) {
|
||||||
var oldVal;
|
var oldVal;
|
||||||
|
|||||||
@@ -1069,7 +1069,7 @@ export class Stage extends Container<Layer> {
|
|||||||
* @return {Konva.Stage} this
|
* @return {Konva.Stage} this
|
||||||
*/
|
*/
|
||||||
batchDraw() {
|
batchDraw() {
|
||||||
this.children.forEach(function (layer) {
|
this.getChildren().forEach(function (layer) {
|
||||||
layer.batchDraw();
|
layer.batchDraw();
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -36,6 +36,22 @@ export interface ImageConfig extends ShapeConfig {
|
|||||||
* imageObj.src = '/path/to/image.jpg'
|
* imageObj.src = '/path/to/image.jpg'
|
||||||
*/
|
*/
|
||||||
export class Image extends Shape<ImageConfig> {
|
export class Image extends Shape<ImageConfig> {
|
||||||
|
constructor(attrs: ImageConfig) {
|
||||||
|
super(attrs);
|
||||||
|
this.on('imageChange.konva', (e) => {
|
||||||
|
this._setImageLoad();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._setImageLoad();
|
||||||
|
}
|
||||||
|
_setImageLoad() {
|
||||||
|
const image = this.image();
|
||||||
|
if (image && image['addEventListener']) {
|
||||||
|
image['addEventListener']('load', () => {
|
||||||
|
this._requestDraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
_useBufferCanvas() {
|
_useBufferCanvas() {
|
||||||
return super._useBufferCanvas(true);
|
return super._useBufferCanvas(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,13 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="container"></div>
|
<div id="container"></div>
|
||||||
<!-- this line for dev version -->
|
<!-- this line for dev version -->
|
||||||
<script src="../../"></script>
|
<script src="../../src/index.ts"></script>
|
||||||
<!-- this line for old version -->
|
<!-- this line for old version -->
|
||||||
<!-- <script src="https://unpkg.com/konva@7.0.3/konva.min.js"></script> -->
|
<!-- <script src="https://unpkg.com/konva@7.0.3/konva.min.js"></script> -->
|
||||||
<script src="http://www.html5canvastutorials.com/lib/stats/stats.js"></script>
|
<script src="http://www.html5canvastutorials.com/lib/stats/stats.js"></script>
|
||||||
<script defer="defer">
|
<script defer="defer">
|
||||||
Konva.isUnminified = false;
|
Konva.isUnminified = false;
|
||||||
|
// Konva.autoDrawEnabled = trye;
|
||||||
var lastTime = 0;
|
var lastTime = 0;
|
||||||
|
|
||||||
var width = window.innerWidth;
|
var width = window.innerWidth;
|
||||||
@@ -189,7 +190,10 @@
|
|||||||
y: pos.y,
|
y: pos.y,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
layer.draw();
|
if (!Konva.autoDrawEnabled) {
|
||||||
|
layer.draw();
|
||||||
|
}
|
||||||
|
|
||||||
requestAnimationFrame(update);
|
requestAnimationFrame(update);
|
||||||
stats.end();
|
stats.end();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
import './unit/Shape-test.ts';
|
import './unit/Shape-test.ts';
|
||||||
import './unit/Node-test.ts';
|
import './unit/Node-test.ts';
|
||||||
import './unit/Node-cache-test.ts';
|
import './unit/Node-cache-test.ts';
|
||||||
|
import './unit/AutoDraw-test.ts';
|
||||||
|
|
||||||
// SHAPES
|
// SHAPES
|
||||||
import './unit/Rect-test.ts';
|
import './unit/Rect-test.ts';
|
||||||
|
|||||||
137
test/unit/AutoDraw-test.ts
Normal file
137
test/unit/AutoDraw-test.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { assert } from 'chai';
|
||||||
|
import { addStage, isNode, Konva } from './utis';
|
||||||
|
|
||||||
|
describe('AutoDraw', function () {
|
||||||
|
// ======================================================
|
||||||
|
it('schedule draw on shape add/change/remove', function () {
|
||||||
|
var stage = addStage();
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
let callCount = 0;
|
||||||
|
layer.batchDraw = function () {
|
||||||
|
callCount += 1;
|
||||||
|
Konva.Layer.prototype.batchDraw.call(this);
|
||||||
|
return layer;
|
||||||
|
};
|
||||||
|
var circle = new Konva.Circle({
|
||||||
|
x: stage.width() / 2,
|
||||||
|
y: stage.height() / 2,
|
||||||
|
radius: 70,
|
||||||
|
fill: 'green',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle',
|
||||||
|
});
|
||||||
|
layer.add(circle);
|
||||||
|
assert.equal(callCount, 1);
|
||||||
|
circle.radius(50);
|
||||||
|
assert.equal(callCount, 2);
|
||||||
|
circle.destroy();
|
||||||
|
assert.equal(callCount, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
it('schedule draw on order change', function () {
|
||||||
|
var stage = addStage();
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
var circle = new Konva.Circle({
|
||||||
|
x: stage.width() / 2,
|
||||||
|
y: stage.height() / 2,
|
||||||
|
radius: 70,
|
||||||
|
fill: 'green',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle',
|
||||||
|
});
|
||||||
|
layer.add(circle);
|
||||||
|
|
||||||
|
var circle2 = new Konva.Circle({
|
||||||
|
x: stage.width() / 2,
|
||||||
|
y: stage.height() / 2,
|
||||||
|
radius: 70,
|
||||||
|
fill: 'green',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle',
|
||||||
|
});
|
||||||
|
layer.add(circle2);
|
||||||
|
|
||||||
|
let callCount = 0;
|
||||||
|
layer.batchDraw = function () {
|
||||||
|
callCount += 1;
|
||||||
|
Konva.Layer.prototype.batchDraw.call(this);
|
||||||
|
return layer;
|
||||||
|
};
|
||||||
|
|
||||||
|
circle.moveToTop();
|
||||||
|
assert.equal(callCount, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
it('schedule draw on cache', function () {
|
||||||
|
var stage = addStage();
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
var circle = new Konva.Circle({
|
||||||
|
x: stage.width() / 2,
|
||||||
|
y: stage.height() / 2,
|
||||||
|
radius: 70,
|
||||||
|
fill: 'green',
|
||||||
|
stroke: 'black',
|
||||||
|
strokeWidth: 4,
|
||||||
|
name: 'myCircle',
|
||||||
|
});
|
||||||
|
layer.add(circle);
|
||||||
|
|
||||||
|
let callCount = 0;
|
||||||
|
layer.batchDraw = function () {
|
||||||
|
callCount += 1;
|
||||||
|
Konva.Layer.prototype.batchDraw.call(this);
|
||||||
|
return layer;
|
||||||
|
};
|
||||||
|
|
||||||
|
circle.cache();
|
||||||
|
assert.equal(callCount, 1);
|
||||||
|
|
||||||
|
circle.clearCache();
|
||||||
|
assert.equal(callCount, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
it.only('redraw for images', function (done) {
|
||||||
|
// don't test on node, because of specific url access
|
||||||
|
if (isNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var stage = addStage();
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
const { src } = document.getElementById(
|
||||||
|
'darth-vader.jpg'
|
||||||
|
) as HTMLImageElement;
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.src = src;
|
||||||
|
const image = new Konva.Image({
|
||||||
|
image: img,
|
||||||
|
});
|
||||||
|
layer.add(image);
|
||||||
|
|
||||||
|
let callCount = 0;
|
||||||
|
layer.batchDraw = function () {
|
||||||
|
callCount += 1;
|
||||||
|
Konva.Layer.prototype.batchDraw.call(this);
|
||||||
|
return layer;
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
assert.equal(callCount, 1);
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -302,7 +302,7 @@ describe('Layer', function () {
|
|||||||
|
|
||||||
stage.add(layer);
|
stage.add(layer);
|
||||||
|
|
||||||
assert(layer.getCanvasElement().style.display === 'none');
|
assert(layer.getNativeCanvasElement().style.display === 'none');
|
||||||
});
|
});
|
||||||
|
|
||||||
// ======================================================
|
// ======================================================
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { Stage } from '../../src/Stage';
|
|||||||
|
|
||||||
// reset some data
|
// reset some data
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
Konva.inDblClickWindow = false;
|
Konva['inDblClickWindow'] = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const isNode = typeof global.document === 'undefined';
|
export const isNode = typeof global.document === 'undefined';
|
||||||
@@ -120,7 +120,7 @@ export function compareCanvases(canvas1, canvas2, tol?, secondTol?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function compareLayerAndCanvas(layer: Layer, canvas, tol?, secondTol?) {
|
export function compareLayerAndCanvas(layer: Layer, canvas, tol?, secondTol?) {
|
||||||
compareCanvases(layer.getCanvasElement(), canvas, tol, secondTol);
|
compareCanvases(layer.getNativeCanvasElement(), canvas, tol, secondTol);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function cloneAndCompareLayer(layer: Layer, tol?, secondTol?) {
|
export function cloneAndCompareLayer(layer: Layer, tol?, secondTol?) {
|
||||||
@@ -131,7 +131,12 @@ export function cloneAndCompareLayer(layer: Layer, tol?, secondTol?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function compareLayers(layer1: Layer, layer2: Layer, tol?, secondTol?) {
|
export function compareLayers(layer1: Layer, layer2: Layer, tol?, secondTol?) {
|
||||||
compareLayerAndCanvas(layer1, layer2.getCanvasElement(), tol, secondTol);
|
compareLayerAndCanvas(
|
||||||
|
layer1,
|
||||||
|
layer2.getNativeCanvasElement(),
|
||||||
|
tol,
|
||||||
|
secondTol
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCanvas() {
|
export function createCanvas() {
|
||||||
@@ -152,7 +157,7 @@ export function showHit(layer) {
|
|||||||
getContainer().appendChild(canvas);
|
getContainer().appendChild(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
Konva.UA.mobile = false;
|
Konva['UA'].mobile = false;
|
||||||
|
|
||||||
export function simulateMouseDown(stage, pos) {
|
export function simulateMouseDown(stage, pos) {
|
||||||
var top = isNode ? 0 : stage.content.getBoundingClientRect().top;
|
var top = isNode ? 0 : stage.content.getBoundingClientRect().top;
|
||||||
|
|||||||
Reference in New Issue
Block a user