CVAT Canvas updates (#607)

* Canvas VIEW
* Some stubs added
* Changed type of frame storage
* Frame setup
* Big canvas
* Zoom and fit
* Move
* Minimized CVAT version
main
Boris Sekachev 7 years ago committed by Nikita Manovich
parent 5733423b13
commit 85ae933af5

@ -5,40 +5,48 @@
*/
module.exports = {
"env": {
"node": true,
"browser": true,
"es6": true,
'env': {
'node': true,
'browser': true,
'es6': true,
},
"parserOptions": {
"parser": "@typescript-eslint/parser",
"sourceType": "module",
"ecmaVersion": 6,
'parserOptions': {
'parser': '@typescript-eslint/parser',
'sourceType': 'module',
'ecmaVersion': 6,
},
"plugins": [
"security",
"no-unsanitized",
"no-unsafe-innerhtml",
"@typescript-eslint",
'plugins': [
'security',
'no-unsanitized',
'no-unsafe-innerhtml',
'@typescript-eslint',
],
"extends": [
"eslint:recommended",
"plugin:security/recommended",
"plugin:no-unsanitized/DOM",
"plugin:@typescript-eslint/recommended",
"airbnb",
'extends': [
'eslint:recommended',
'plugin:security/recommended',
'plugin:no-unsanitized/DOM',
'plugin:@typescript-eslint/recommended',
'airbnb',
],
"rules": {
"no-new": [0],
"class-methods-use-this": [0],
"no-plusplus": [0],
"no-restricted-syntax": [0, {"selector": "ForOfStatement"}],
"no-continue": [0],
"security/detect-object-injection": 0,
"indent": ["warn", 4],
"no-useless-constructor": 0,
"func-names": [0],
"no-console": [0], // this rule deprecates console.log, console.warn etc. because "it is not good in production code"
"@typescript-eslint/no-explicit-any": [0],
'rules': {
'no-new': [0],
'class-methods-use-this': [0],
'no-plusplus': [0],
'no-restricted-syntax': [0, {'selector': 'ForOfStatement'}],
'no-continue': [0],
'security/detect-object-injection': 0,
'indent': ['warn', 4],
'no-useless-constructor': 0,
'func-names': [0],
'no-console': [0], // this rule deprecates console.log, console.warn etc. because 'it is not good in production code'
'@typescript-eslint/no-explicit-any': [0],
'lines-between-class-members': [0],
},
'settings': {
'import/resolver': {
'node': {
'extensions': ['.ts', '.js', '.json'],
},
},
},
};

@ -50,7 +50,7 @@ Canvas itself handles:
All methods are sync.
```ts
html(): HTMLElement;
html(): HTMLDivElement;
setup(frameData: FrameData, objectStates: ObjectState): void;
activate(clientID: number, attributeID?: number): void;
rotate(direction: Rotation): void;
@ -76,6 +76,7 @@ All methods are sync.
```canvas_shape_drawing```
- Tags has a class ```canvas_tag```
- Canvas image has ID ```canvas_image```
- Grid on the canvas has ID ```canvas_grid_pattern```
### Events

@ -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, []);
});

@ -5,7 +5,7 @@
"main": "babel.config.js",
"scripts": {
"build": "tsc && webpack --config ./webpack.config.js",
"server": "nodemon --watch config --exec 'webpack-dev-server --config ./webpack.config.js --open'"
"server": "nodemon --watch config --exec 'webpack-dev-server --config ./webpack.config.js --mode=development --open'"
},
"author": "Intel",
"license": "MIT",
@ -17,10 +17,11 @@
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/preset-typescript": "^7.3.3",
"eslint": "^6.1.0",
"@types/node": "^12.6.8",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0",
"babel-loader": "^8.0.6",
"eslint": "^6.1.0",
"nodemon": "^1.19.1",
"typescript": "^3.5.3",
"webpack": "^4.36.1",

@ -3,11 +3,12 @@
* SPDX-License-Identifier: MIT
*/
/* eslint-disable */
// Temporary disable eslint
import { CanvasModel, CanvasModelImpl, Rotation } from './canvasModel';
import { CanvasController, CanvasControllerImpl } from './canvasController';
import { CanvasView, CanvasViewImpl } from './canvasView';
interface CanvasInterface {
html(): HTMLElement;
interface Canvas {
html(): HTMLDivElement;
setup(frameData: any, objectStates: any[]): void;
activate(clientID: number, attributeID?: number): void;
rotate(direction: Rotation): void;
@ -15,7 +16,7 @@ interface CanvasInterface {
fit(): void;
grid(stepX: number, stepY: number): void;
draw(shapeType: string, numberOfPoints: number, initialState: any): any;
draw(enabled?: boolean, shapeType?: string, numberOfPoints?: number, initialState?: any): any;
split(enabled?: boolean): any;
group(enabled?: boolean): any;
merge(enabled?: boolean): any;
@ -23,61 +24,67 @@ interface CanvasInterface {
cancel(): void;
}
export enum Rotation {
CLOCKWISE90,
ANTICLOCKWISE90,
}
class CanvasImpl implements Canvas {
private model: CanvasModel;
private controller: CanvasController;
private view: CanvasView;
export class Canvas implements CanvasInterface {
public constructor() {
return this;
this.model = new CanvasModelImpl();
this.controller = new CanvasControllerImpl(this.model);
this.view = new CanvasViewImpl(this.model, this.controller);
}
public html(): HTMLElement {
throw new Error('Method not implemented.');
public html(): HTMLDivElement {
return this.view.html();
}
public setup(frameData: any, objectStates: any[]): void {
throw new Error('Method not implemented.');
this.model.setup(frameData, objectStates);
}
public activate(clientID: number, attributeID: number = null): void {
throw new Error('Method not implemented.');
this.model.activate(clientID, attributeID);
}
public rotate(direction: Rotation): void {
throw new Error('Method not implemented.');
this.model.rotate(direction);
}
public focus(clientID: number, padding: number = 0): void {
throw new Error('Method not implemented.');
this.model.focus(clientID, padding);
}
public fit(): void {
throw new Error('Method not implemented.');
this.model.fit();
}
public grid(stepX: number, stepY: number): void {
throw new Error('Method not implemented.');
this.model.grid(stepX, stepY);
}
public draw(shapeType: string, numberOfPoints: number, initialState: any): any {
throw new Error('Method not implemented.');
public draw(enabled: boolean = false, shapeType: string = '', numberOfPoints: number = 0, initialState: any = null): any {
return this.model.draw(enabled, shapeType, numberOfPoints, initialState);
}
public split(enabled: boolean = false): any {
throw new Error('Method not implemented.');
return this.model.split(enabled);
}
public group(enabled: boolean = false): any {
throw new Error('Method not implemented.');
return this.model.group(enabled);
}
public merge(enabled: boolean = false): any {
throw new Error('Method not implemented.');
return this.model.merge(enabled);
}
public cancel(): void {
throw new Error('Method not implemented.');
this.model.cancel();
}
}
export {
CanvasImpl as Canvas,
Rotation,
};

@ -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"
],
}

@ -14,13 +14,16 @@ module.exports = {
path: path.resolve(__dirname, 'dist'),
filename: 'cvat-canvas.js',
library: 'canvas',
libraryTarget: 'commonjs',
libraryTarget: 'window',
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
compress: false,
inline: true,
port: 9000,
port: 3000,
},
resolve: {
extensions: ['.ts', '.js', '.json'],
},
module: {
rules: [{

@ -28,6 +28,7 @@
},
"dependencies": {
"axios": "^0.18.0",
"browser-or-node": "^1.2.1",
"error-stack-parser": "^2.0.2",
"form-data": "^2.5.0",
"jest-config": "^24.8.0",

@ -12,6 +12,7 @@
const PluginRegistry = require('./plugins');
const serverProxy = require('./server-proxy');
const { ArgumentError } = require('./exceptions');
const { isBrowser, isNode } = require('browser-or-node');
// This is the frames storage
const frameDataCache = {};
@ -65,29 +66,43 @@
* @memberof module:API.cvat.classes.FrameData
* @instance
* @async
* @param {function} [onServerRequest = () => {}]
* callback which will be called if data absences local
* @throws {module:API.cvat.exception.ServerError}
* @throws {module:API.cvat.exception.PluginError}
*/
async data() {
async data(onServerRequest = () => {}) {
const result = await PluginRegistry
.apiWrapper.call(this, FrameData.prototype.data);
.apiWrapper.call(this, FrameData.prototype.data, onServerRequest);
return result;
}
}
FrameData.prototype.data.implementation = async function () {
if (!(this.number in frameCache[this.tid])) {
const frame = await serverProxy.frames.getData(this.tid, this.number);
FrameData.prototype.data.implementation = async function (onServerRequest) {
return new Promise(async (resolve, reject) => {
try {
if (this.number in frameCache[this.tid]) {
resolve(frameCache[this.tid][this.number]);
} else {
onServerRequest();
const frame = await serverProxy.frames.getData(this.tid, this.number);
if (typeof (module) !== 'undefined' && module.exports) {
frameCache[this.tid][this.number] = global.Buffer.from(frame, 'binary').toString('base64');
} else {
const url = URL.createObjectURL(new Blob([frame]));
frameCache[this.tid][this.number] = url;
if (isNode) {
frameCache[this.tid][this.number] = global.Buffer.from(frame, 'binary').toString('base64');
resolve(frameCache[this.tid][this.number]);
} else if (isBrowser) {
const reader = new FileReader();
reader.onload = () => {
frameCache[this.tid][this.number] = reader.result;
resolve(frameCache[this.tid][this.number]);
};
reader.readAsDataURL(frame);
}
}
} catch (exception) {
reject(exception);
}
}
return frameCache[this.tid][this.number];
});
};
async function getFrame(taskID, mode, frame) {

Loading…
Cancel
Save