CVAT Canvas updates (#607)
* Canvas VIEW * Some stubs added * Changed type of frame storage * Frame setup * Big canvas * Zoom and fit * Move * Minimized CVAT versionmain
parent
5733423b13
commit
85ae933af5
@ -0,0 +1,82 @@
|
||||
.canvas_hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#canvas_wrapper {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
border: 1px black solid;
|
||||
border-radius: 5px;
|
||||
background-color: #B0C4DE;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#canvas_rotation_wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#canvas_loading_animation {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
#canvas_loading_circle {
|
||||
fill-opacity: 0;
|
||||
stroke: #09c;
|
||||
stroke-width: 3px;
|
||||
stroke-dasharray: 50;
|
||||
animation: loadingAnimation 1s linear infinite;
|
||||
}
|
||||
|
||||
#canvas_text_content {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
transform-origin: center center;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#canvas_background {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
background-repeat: no-repeat;
|
||||
transform-origin: top left;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#canvas_grid {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
transform-origin: top left;
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#canvas_grid_pattern {
|
||||
opacity: 1;
|
||||
stroke: white;
|
||||
}
|
||||
|
||||
#canvas_content {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
outline: 10px solid black;
|
||||
transform-origin: top left;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@keyframes loadingAnimation {
|
||||
0% {stroke-dashoffset: 1; stroke: #09c;}
|
||||
50% {stroke-dashoffset: 100; stroke: #f44;}
|
||||
100% {stroke-dashoffset: 300; stroke: #09c;}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE HTML>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title> CVAT-CANVAS Dev Server </title>
|
||||
<script type="text/javascript" src="index.js"></script>
|
||||
<script type="text/javascript" src="cvat-core.js"></script>
|
||||
<script type="text/javascript" src="cvat-canvas.js"></script>
|
||||
<link rel = "stylesheet" type = "text/css" href = "canvas.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="htmlContainer" style="width: 1920px;height: 1080px;">
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,28 @@
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
await window.cvat.server.login('admin', 'nimda760');
|
||||
const [job] = (await window.cvat.jobs.get({ jobID: 21 }));
|
||||
const canvas = new window.canvas.Canvas();
|
||||
const htmlContainer = window.document.getElementById('htmlContainer');
|
||||
|
||||
|
||||
htmlContainer.appendChild(canvas.html());
|
||||
|
||||
let frame = 0;
|
||||
const callback = async () => {
|
||||
canvas.fit();
|
||||
const frameData = await job.frames.get(frame);
|
||||
canvas.setup(frameData, []);
|
||||
frame += 1;
|
||||
|
||||
if (frame > 50) {
|
||||
frame = 0;
|
||||
}
|
||||
};
|
||||
|
||||
canvas.html().addEventListener('canvas.setup', async () => {
|
||||
setTimeout(callback, 30);
|
||||
});
|
||||
|
||||
const frameData = await job.frames.get(frame);
|
||||
canvas.setup(frameData, []);
|
||||
});
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import {
|
||||
CanvasModel,
|
||||
Geometry,
|
||||
Size,
|
||||
Position,
|
||||
} from './canvasModel';
|
||||
|
||||
|
||||
export interface CanvasController {
|
||||
readonly geometry: Geometry;
|
||||
canvasSize: Size;
|
||||
|
||||
zoom(x: number, y: number, direction: number): void;
|
||||
enableDrag(x: number, y: number): void;
|
||||
drag(x: number, y: number): void;
|
||||
disableDrag(): void;
|
||||
|
||||
fit(): void;
|
||||
}
|
||||
|
||||
export class CanvasControllerImpl implements CanvasController {
|
||||
private model: CanvasModel;
|
||||
private lastDragPosition: Position;
|
||||
private isDragging: boolean;
|
||||
|
||||
public constructor(model: CanvasModel) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public get geometry(): Geometry {
|
||||
return this.model.geometry;
|
||||
}
|
||||
|
||||
public zoom(x: number, y: number, direction: number): void {
|
||||
this.model.zoom(x, y, direction);
|
||||
}
|
||||
|
||||
public fit(): void {
|
||||
this.model.fit();
|
||||
}
|
||||
|
||||
public set canvasSize(value: Size) {
|
||||
this.model.canvasSize = value;
|
||||
}
|
||||
|
||||
public get canvasSize(): Size {
|
||||
return this.model.canvasSize;
|
||||
}
|
||||
|
||||
public enableDrag(x: number, y: number): void {
|
||||
this.lastDragPosition = {
|
||||
x,
|
||||
y,
|
||||
};
|
||||
this.isDragging = true;
|
||||
}
|
||||
|
||||
public drag(x: number, y: number): void {
|
||||
if (this.isDragging) {
|
||||
const topOffset: number = y - this.lastDragPosition.y;
|
||||
const leftOffset: number = x - this.lastDragPosition.x;
|
||||
this.lastDragPosition = {
|
||||
x,
|
||||
y,
|
||||
};
|
||||
this.model.move(topOffset, leftOffset);
|
||||
}
|
||||
}
|
||||
|
||||
public disableDrag(): void {
|
||||
this.isDragging = false;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { MasterImpl } from './master';
|
||||
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Geometry {
|
||||
image: Size;
|
||||
canvas: Size;
|
||||
top: number;
|
||||
left: number;
|
||||
scale: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export enum FrameZoom {
|
||||
MIN = 0.1,
|
||||
MAX = 10,
|
||||
}
|
||||
|
||||
export enum Rotation {
|
||||
CLOCKWISE90,
|
||||
ANTICLOCKWISE90,
|
||||
}
|
||||
|
||||
export enum UpdateReasons {
|
||||
IMAGE = 'image',
|
||||
ZOOM = 'zoom',
|
||||
FIT = 'fit',
|
||||
MOVE = 'move',
|
||||
}
|
||||
|
||||
export interface CanvasModel extends MasterImpl {
|
||||
image: string;
|
||||
geometry: Geometry;
|
||||
imageSize: Size;
|
||||
canvasSize: Size;
|
||||
|
||||
zoom(x: number, y: number, direction: number): void;
|
||||
move(topOffset: number, leftOffset: number): void;
|
||||
|
||||
setup(frameData: any, objectStates: any[]): void;
|
||||
activate(clientID: number, attributeID: number): void;
|
||||
rotate(direction: Rotation): void;
|
||||
focus(clientID: number, padding: number): void;
|
||||
fit(): void;
|
||||
grid(stepX: number, stepY: number): void;
|
||||
|
||||
draw(enabled: boolean, shapeType: string, numberOfPoints: number, initialState: any): any;
|
||||
split(enabled: boolean): any;
|
||||
group(enabled: boolean): any;
|
||||
merge(enabled: boolean): any;
|
||||
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
export class CanvasModelImpl extends MasterImpl implements CanvasModel {
|
||||
private data: {
|
||||
image: string;
|
||||
imageSize: Size;
|
||||
canvasSize: Size;
|
||||
imageOffset: number;
|
||||
scale: number;
|
||||
top: number;
|
||||
left: number;
|
||||
};
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
|
||||
this.data = {
|
||||
image: '',
|
||||
imageSize: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
canvasSize: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
imageOffset: 0,
|
||||
scale: 1,
|
||||
top: 0,
|
||||
left: 0,
|
||||
};
|
||||
}
|
||||
|
||||
public zoom(x: number, y: number, direction: number): void {
|
||||
const oldScale: number = this.data.scale;
|
||||
const newScale: number = direction > 0 ? oldScale * 6 / 5 : oldScale * 5 / 6;
|
||||
this.data.scale = Math.min(Math.max(newScale, FrameZoom.MIN), FrameZoom.MAX);
|
||||
this.data.left += (x * (oldScale / this.data.scale - 1)) * this.data.scale;
|
||||
this.data.top += (y * (oldScale / this.data.scale - 1)) * this.data.scale;
|
||||
|
||||
this.notify(UpdateReasons.ZOOM);
|
||||
}
|
||||
|
||||
public move(topOffset: number, leftOffset: number): void {
|
||||
this.data.top += topOffset;
|
||||
this.data.left += leftOffset;
|
||||
this.notify(UpdateReasons.MOVE);
|
||||
}
|
||||
|
||||
|
||||
public setup(frameData: any, objectStates: any[]): void {
|
||||
frameData.data(
|
||||
(): void => {
|
||||
this.data.image = '';
|
||||
this.notify(UpdateReasons.IMAGE);
|
||||
},
|
||||
).then((data: string): void => {
|
||||
this.data.imageSize = {
|
||||
width: (frameData.width as number),
|
||||
height: (frameData.height as number),
|
||||
};
|
||||
|
||||
this.data.image = data;
|
||||
this.notify(UpdateReasons.IMAGE);
|
||||
}).catch((exception: any): void => {
|
||||
console.log(exception.toString());
|
||||
});
|
||||
|
||||
console.log(objectStates);
|
||||
}
|
||||
|
||||
public activate(clientID: number, attributeID: number): void {
|
||||
console.log(clientID, attributeID);
|
||||
}
|
||||
|
||||
public rotate(direction: Rotation): void {
|
||||
console.log(direction);
|
||||
}
|
||||
|
||||
public focus(clientID: number, padding: number): void {
|
||||
console.log(clientID, padding);
|
||||
}
|
||||
|
||||
public fit(): void {
|
||||
this.data.scale = Math.min(
|
||||
this.data.canvasSize.width / this.data.imageSize.width,
|
||||
this.data.canvasSize.height / this.data.imageSize.height,
|
||||
);
|
||||
|
||||
this.data.scale = Math.min(
|
||||
Math.max(this.data.scale, FrameZoom.MIN),
|
||||
FrameZoom.MAX,
|
||||
);
|
||||
|
||||
this.data.top = (this.data.canvasSize.height
|
||||
- this.data.imageSize.height * this.data.scale) / 2;
|
||||
this.data.left = (this.data.canvasSize.width
|
||||
- this.data.imageSize.width * this.data.scale) / 2;
|
||||
|
||||
this.notify(UpdateReasons.FIT);
|
||||
}
|
||||
|
||||
public grid(stepX: number, stepY: number): void {
|
||||
console.log(stepX, stepY);
|
||||
}
|
||||
|
||||
public draw(enabled: boolean, shapeType: string,
|
||||
numberOfPoints: number, initialState: any): any {
|
||||
return {
|
||||
enabled,
|
||||
shapeType,
|
||||
numberOfPoints,
|
||||
initialState,
|
||||
};
|
||||
}
|
||||
|
||||
public split(enabled: boolean): any {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public group(enabled: boolean): any {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public merge(enabled: boolean): any {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
|
||||
}
|
||||
|
||||
public get geometry(): Geometry {
|
||||
return {
|
||||
image: {
|
||||
width: this.data.imageSize.width,
|
||||
height: this.data.imageSize.height,
|
||||
},
|
||||
canvas: {
|
||||
width: this.data.canvasSize.width,
|
||||
height: this.data.canvasSize.height,
|
||||
},
|
||||
top: this.data.top,
|
||||
left: this.data.left,
|
||||
scale: this.data.scale,
|
||||
offset: this.data.imageOffset,
|
||||
};
|
||||
}
|
||||
|
||||
public get image(): string {
|
||||
return this.data.image;
|
||||
}
|
||||
|
||||
public set imageSize(value: Size) {
|
||||
this.data.imageSize = {
|
||||
width: value.width,
|
||||
height: value.height,
|
||||
};
|
||||
}
|
||||
|
||||
public get imageSize(): Size {
|
||||
return {
|
||||
width: this.data.imageSize.width,
|
||||
height: this.data.imageSize.height,
|
||||
};
|
||||
}
|
||||
|
||||
public set canvasSize(value: Size) {
|
||||
this.data.canvasSize = {
|
||||
width: value.width,
|
||||
height: value.height,
|
||||
};
|
||||
|
||||
this.data.imageOffset = Math.floor(Math.max(
|
||||
this.data.canvasSize.height / FrameZoom.MIN,
|
||||
this.data.canvasSize.width / FrameZoom.MIN,
|
||||
));
|
||||
}
|
||||
|
||||
public get canvasSize(): Size {
|
||||
return {
|
||||
width: this.data.canvasSize.width,
|
||||
height: this.data.canvasSize.height,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// TODO List:
|
||||
// 2) Rotate image
|
||||
// 3) Draw objects
|
||||
@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
import { CanvasModel, UpdateReasons, Geometry } from './canvasModel';
|
||||
import { Listener, Master } from './master';
|
||||
import { CanvasController } from './canvasController';
|
||||
|
||||
export interface CanvasView {
|
||||
html(): HTMLDivElement;
|
||||
}
|
||||
|
||||
interface HTMLAttribute {
|
||||
[index: string]: string;
|
||||
}
|
||||
|
||||
|
||||
function translateToSVG(svg: SVGSVGElement, points: number[]): number[] {
|
||||
const output = [];
|
||||
const transformationMatrix = svg.getScreenCTM().inverse();
|
||||
let pt = svg.createSVGPoint();
|
||||
for (let i = 0; i < points.length; i += 2) {
|
||||
[pt.x] = points;
|
||||
[, pt.y] = points;
|
||||
pt = pt.matrixTransform(transformationMatrix);
|
||||
output.push(pt.x, pt.y);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function translateFromSVG(svg: SVGSVGElement, points: number[]): number[] {
|
||||
const output = [];
|
||||
const transformationMatrix = svg.getScreenCTM();
|
||||
let pt = svg.createSVGPoint();
|
||||
for (let i = 0; i < points.length; i += 2) {
|
||||
[pt.x] = points;
|
||||
[, pt.y] = points;
|
||||
pt = pt.matrixTransform(transformationMatrix);
|
||||
output.push(pt.x, pt.y);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export class CanvasViewImpl implements CanvasView, Listener {
|
||||
private loadingAnimation: SVGSVGElement;
|
||||
private text: SVGSVGElement;
|
||||
private background: SVGSVGElement;
|
||||
private grid: SVGSVGElement;
|
||||
private content: SVGSVGElement;
|
||||
private rotationWrapper: HTMLDivElement;
|
||||
private canvas: HTMLDivElement;
|
||||
private gridPath: SVGPathElement;
|
||||
private controller: CanvasController;
|
||||
|
||||
public constructor(model: CanvasModel & Master, controller: CanvasController) {
|
||||
this.controller = controller;
|
||||
|
||||
// Create HTML elements
|
||||
this.loadingAnimation = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
this.text = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
this.background = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
|
||||
this.grid = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
this.gridPath = window.document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
|
||||
this.content = window.document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
this.rotationWrapper = window.document.createElement('div');
|
||||
this.canvas = window.document.createElement('div');
|
||||
|
||||
const loadingCircle: SVGCircleElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
const gridDefs: SVGDefsElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
||||
const gridPattern: SVGPatternElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
|
||||
const gridRect: SVGRectElement = window.document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
|
||||
// Setup loading animation
|
||||
this.loadingAnimation.setAttribute('id', 'canvas_loading_animation');
|
||||
loadingCircle.setAttribute('id', 'canvas_loading_circle');
|
||||
loadingCircle.setAttribute('r', '30');
|
||||
loadingCircle.setAttribute('cx', '50%');
|
||||
loadingCircle.setAttribute('cy', '50%');
|
||||
|
||||
// Setup grid
|
||||
this.grid.setAttribute('id', 'canvas_grid');
|
||||
this.grid.setAttribute('version', '2');
|
||||
this.gridPath.setAttribute('d', 'M 1000 0 L 0 0 0 1000');
|
||||
this.gridPath.setAttribute('fill', 'none');
|
||||
this.gridPath.setAttribute('stroke-width', '1.5');
|
||||
gridPattern.setAttribute('id', 'canvas_grid_pattern');
|
||||
gridPattern.setAttribute('width', '100');
|
||||
gridPattern.setAttribute('height', '100');
|
||||
gridPattern.setAttribute('patternUnits', 'userSpaceOnUse');
|
||||
gridRect.setAttribute('width', '100%');
|
||||
gridRect.setAttribute('height', '100%');
|
||||
gridRect.setAttribute('fill', 'url(#canvas_grid_pattern)');
|
||||
|
||||
|
||||
// Setup content
|
||||
this.text.setAttribute('id', 'canvas_text_content');
|
||||
this.background.setAttribute('id', 'canvas_background');
|
||||
this.content.setAttribute('id', 'canvas_content');
|
||||
|
||||
// Setup wrappers
|
||||
this.rotationWrapper.setAttribute('id', 'canvas_rotation_wrapper');
|
||||
this.canvas.setAttribute('id', 'canvas_wrapper');
|
||||
|
||||
// Unite created HTML elements together
|
||||
this.loadingAnimation.appendChild(loadingCircle);
|
||||
this.grid.appendChild(gridDefs);
|
||||
this.grid.appendChild(gridRect);
|
||||
|
||||
gridDefs.appendChild(gridPattern);
|
||||
gridPattern.appendChild(this.gridPath);
|
||||
|
||||
this.rotationWrapper.appendChild(this.loadingAnimation);
|
||||
this.rotationWrapper.appendChild(this.text);
|
||||
this.rotationWrapper.appendChild(this.background);
|
||||
this.rotationWrapper.appendChild(this.grid);
|
||||
this.rotationWrapper.appendChild(this.content);
|
||||
|
||||
this.canvas.appendChild(this.rotationWrapper);
|
||||
|
||||
// A little hack to get size after first mounting
|
||||
// http://www.backalleycoder.com/2012/04/25/i-want-a-damnodeinserted/
|
||||
const self = this;
|
||||
const canvasFirstMounted = (event: AnimationEvent): void => {
|
||||
if (event.animationName === 'loadingAnimation') {
|
||||
self.controller.canvasSize = {
|
||||
width: self.rotationWrapper.clientWidth,
|
||||
height: self.rotationWrapper.clientHeight,
|
||||
};
|
||||
|
||||
self.rotationWrapper.removeEventListener('animationstart', canvasFirstMounted);
|
||||
}
|
||||
};
|
||||
|
||||
this.canvas.addEventListener('animationstart', canvasFirstMounted);
|
||||
this.content.addEventListener('dblclick', (): void => {
|
||||
self.controller.fit();
|
||||
});
|
||||
|
||||
this.content.addEventListener('mousedown', (event): void => {
|
||||
self.controller.enableDrag(event.clientX, event.clientY);
|
||||
});
|
||||
|
||||
this.content.addEventListener('mousemove', (event): void => {
|
||||
self.controller.drag(event.clientX, event.clientY);
|
||||
});
|
||||
|
||||
window.document.addEventListener('mouseup', (): void => {
|
||||
self.controller.disableDrag();
|
||||
});
|
||||
|
||||
this.content.addEventListener('wheel', (event): void => {
|
||||
const point = translateToSVG(self.background, [event.clientX, event.clientY]);
|
||||
self.controller.zoom(point[0], point[1], event.deltaY > 0 ? -1 : 1);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
model.subscribe(this);
|
||||
}
|
||||
|
||||
public notify(model: CanvasModel & Master, reason: UpdateReasons): void {
|
||||
function transform(geometry: Geometry): void {
|
||||
for (const obj of [this.background, this.grid, this.loadingAnimation, this.content]) {
|
||||
obj.style.transform = `scale(${geometry.scale})`;
|
||||
}
|
||||
}
|
||||
|
||||
function resize(geometry: Geometry): void {
|
||||
for (const obj of [this.background, this.grid, this.loadingAnimation]) {
|
||||
obj.style.width = `${geometry.image.width}`;
|
||||
obj.style.height = `${geometry.image.height}`;
|
||||
}
|
||||
|
||||
for (const obj of [this.content, this.text]) {
|
||||
obj.style.width = `${geometry.image.width + geometry.offset * 2}`;
|
||||
obj.style.height = `${geometry.image.height + geometry.offset * 2}`;
|
||||
}
|
||||
}
|
||||
|
||||
function move(geometry: Geometry): void {
|
||||
for (const obj of [this.background, this.grid, this.loadingAnimation]) {
|
||||
obj.style.top = `${geometry.top}`;
|
||||
obj.style.left = `${geometry.left}`;
|
||||
}
|
||||
|
||||
for (const obj of [this.content, this.text]) {
|
||||
obj.style.top = `${geometry.top - geometry.offset * geometry.scale}`;
|
||||
obj.style.left = `${geometry.left - geometry.offset * geometry.scale}`;
|
||||
}
|
||||
|
||||
this.content.style.transform = `scale(${geometry.scale})`;
|
||||
}
|
||||
|
||||
const { geometry } = this.controller;
|
||||
if (reason === UpdateReasons.IMAGE) {
|
||||
if (!model.image.length) {
|
||||
this.loadingAnimation.classList.remove('canvas_hidden');
|
||||
} else {
|
||||
this.loadingAnimation.classList.add('canvas_hidden');
|
||||
this.background.style.backgroundImage = `url("${model.image}")`;
|
||||
move.call(this, geometry);
|
||||
resize.call(this, geometry);
|
||||
transform.call(this, geometry);
|
||||
const event: Event = new Event('canvas.setup');
|
||||
this.canvas.dispatchEvent(event);
|
||||
}
|
||||
} else if (reason === UpdateReasons.ZOOM || reason === UpdateReasons.FIT) {
|
||||
move.call(this, geometry);
|
||||
resize.call(this, geometry);
|
||||
transform.call(this, geometry);
|
||||
} else if (reason === UpdateReasons.MOVE) {
|
||||
move.call(this, geometry);
|
||||
}
|
||||
}
|
||||
|
||||
public html(): HTMLDivElement {
|
||||
return this.canvas;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
export interface Master {
|
||||
subscribe(listener: Listener): void;
|
||||
unsubscribe(listener: Listener): void;
|
||||
unsubscribeAll(): void;
|
||||
notify(reason: string): void;
|
||||
}
|
||||
|
||||
export interface Listener {
|
||||
notify(master: Master, reason: string): void;
|
||||
}
|
||||
|
||||
export class MasterImpl implements Master {
|
||||
private listeners: Listener[];
|
||||
|
||||
public constructor() {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
public subscribe(listener: Listener): void {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
public unsubscribe(listener: Listener): void {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsubscribeAll(): void {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
public notify(reason: string): void {
|
||||
for (const listener of this.listeners) {
|
||||
listener.notify(this, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"emitDeclarationOnly": true,
|
||||
"module": "commonjs",
|
||||
"module": "es6",
|
||||
"target": "es6",
|
||||
"noImplicitAny": true,
|
||||
"preserveConstEnums": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "dist/declaration"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
"src/*.ts"
|
||||
],
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue