Moved development on public github.
- changed Content-type for save_job request to application/json, object… - Adopted public PR - PASCAL VOC converter - Added convert_to_mask.py script - Fixed player continue plaing at the end of video, lock for editable object broke the client - Same colors for shapes and menus, ability to change color for label or group - Undo redo - Added license header for all files - Added .gitattributes file (critical for bash scripts) - Fixed "Don't delete tasks if a user is deleted" - More strict check for 'checkbox' and 'number' values - Added convert_to_coco.py script - Job statistic were extended. Blowradius was removed - More strict labels verification - Drag polygons by requirement, norm stroke opacity, easy box dragging - Drawing with mouse outside the image area - Fixed 7z support - Boundig box size in drawer, switcheable grid - Split tracks feature - Fix flip parsing - Update color set & color by label feature - Add point for polygons feature - Added context menu - Polyshapesmain
@ -0,0 +1,29 @@
|
||||
* text=auto whitespace=trailing-space,space-before-tab,-indent-with-non-tab,tab-in-indent,tabwidth=4
|
||||
|
||||
.git* text export-ignore
|
||||
|
||||
*.txt text
|
||||
*.htm text
|
||||
*.html text
|
||||
*.js text
|
||||
*.py text
|
||||
*.css text
|
||||
*.md text
|
||||
*.yml text
|
||||
Dockerfile text
|
||||
LICENSE text
|
||||
*.conf text
|
||||
*.mimetypes text
|
||||
*.sh text eol=lf
|
||||
|
||||
*.avi binary
|
||||
*.bmp binary
|
||||
*.exr binary
|
||||
*.ico binary
|
||||
*.jpeg binary
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
*.ttf binary
|
||||
*.pdf binary
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@ -1 +1,7 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
default_app_config = 'cvat.apps.authentication.apps.AuthenticationConfig'
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@ -1,2 +1,8 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Specify groups that new users will have
|
||||
AUTH_SIMPLE_DEFAULT_GROUPS = []
|
||||
|
||||
|
||||
@ -1,2 +1,7 @@
|
||||
<!--
|
||||
Copyright (C) 2018 Intel Corporation
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
<p>
|
||||
</p>
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DashboardConfig(AppConfig):
|
||||
name = 'dashboard'
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from cvat.settings.base import JS_3RDPARTY
|
||||
|
||||
JS_3RDPARTY['dashboard'] = JS_3RDPARTY.get('dashboard', []) + ['documentation/js/shortcuts.js']
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DocumentationConfig(AppConfig):
|
||||
name = 'cvat.apps.documentation'
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
Mousetrap.bind(userConfig.shortkeys["open_help"].value, function() {
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
Mousetrap.bind(window.cvat.config.shortkeys["open_help"].value, function() {
|
||||
window.location.href = "/documentation/user_guide.html";
|
||||
|
||||
return false;
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class EngineConfig(AppConfig):
|
||||
name = 'engine'
|
||||
|
||||
|
||||
@ -0,0 +1,175 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Generated by Django 2.0.3 on 2018-05-30 09:53
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('engine', '0001_release_v0_1_0'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LabeledPoints',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('frame', models.PositiveIntegerField()),
|
||||
('occluded', models.BooleanField(default=False)),
|
||||
('points', models.TextField()),
|
||||
('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Job')),
|
||||
('label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Label')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LabeledPointsAttributeVal',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=64)),
|
||||
('points', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.LabeledPoints')),
|
||||
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LabeledPolygon',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('frame', models.PositiveIntegerField()),
|
||||
('occluded', models.BooleanField(default=False)),
|
||||
('points', models.TextField()),
|
||||
('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Job')),
|
||||
('label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Label')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LabeledPolygonAttributeVal',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=64)),
|
||||
('polygon', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.LabeledPolygon')),
|
||||
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LabeledPolyline',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('frame', models.PositiveIntegerField()),
|
||||
('occluded', models.BooleanField(default=False)),
|
||||
('points', models.TextField()),
|
||||
('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Job')),
|
||||
('label', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.Label')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LabeledPolylineAttributeVal',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=64)),
|
||||
('polyline', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.LabeledPolyline')),
|
||||
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrackedPoints',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('occluded', models.BooleanField(default=False)),
|
||||
('points', models.TextField()),
|
||||
('frame', models.PositiveIntegerField()),
|
||||
('outside', models.BooleanField(default=False)),
|
||||
('track', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.ObjectPath')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrackedPointsAttributeVal',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=64)),
|
||||
('points', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.TrackedPoints')),
|
||||
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrackedPolygon',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('occluded', models.BooleanField(default=False)),
|
||||
('points', models.TextField()),
|
||||
('frame', models.PositiveIntegerField()),
|
||||
('outside', models.BooleanField(default=False)),
|
||||
('track', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.ObjectPath')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrackedPolygonAttributeVal',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=64)),
|
||||
('polygon', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.TrackedPolygon')),
|
||||
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrackedPolyline',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('occluded', models.BooleanField(default=False)),
|
||||
('points', models.TextField()),
|
||||
('frame', models.PositiveIntegerField()),
|
||||
('outside', models.BooleanField(default=False)),
|
||||
('track', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.ObjectPath')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrackedPolylineAttributeVal',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=64)),
|
||||
('polyline', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.TrackedPolyline')),
|
||||
('spec', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engine.AttributeSpec')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Generated by Django 2.0.3 on 2018-06-04 11:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('engine', '0002_labeledpoints_labeledpointsattributeval_labeledpolygon_labeledpolygonattributeval_labeledpolyline_la'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='objectpath',
|
||||
name='shapes',
|
||||
field=models.CharField(default='boxes', max_length=10),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Generated by Django 2.0.3 on 2018-06-09 10:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('engine', '0003_objectpath_shapes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='z_order',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,58 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Generated by Django 2.0.3 on 2018-06-09 12:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('engine', '0004_task_z_order'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='labeledbox',
|
||||
name='z_order',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='labeledpoints',
|
||||
name='z_order',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='labeledpolygon',
|
||||
name='z_order',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='labeledpolyline',
|
||||
name='z_order',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='trackedbox',
|
||||
name='z_order',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='trackedpoints',
|
||||
name='z_order',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='trackedpolygon',
|
||||
name='z_order',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='trackedpolyline',
|
||||
name='z_order',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,43 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Generated by Django 2.0.3 on 2018-06-29 12:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('engine', '0005_auto_20180609_1512'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='labeledbox',
|
||||
name='group_id',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='labeledpoints',
|
||||
name='group_id',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='labeledpolygon',
|
||||
name='group_id',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='labeledpolyline',
|
||||
name='group_id',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='objectpath',
|
||||
name='group_id',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Generated by Django 2.0.3 on 2018-07-16 08:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('engine', '0006_auto_20180629_1501'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='flipped',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,5 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 532 B |
|
After Width: | Height: | Size: 690 B |
|
After Width: | Height: | Size: 668 B |
|
After Width: | Height: | Size: 657 B |
|
After Width: | Height: | Size: 634 B |
|
After Width: | Height: | Size: 673 B |
|
After Width: | Height: | Size: 409 B |
@ -0,0 +1,117 @@
|
||||
From b89380c65ea8bc9231cc98a6ae0e812227c85b3d Mon Sep 17 00:00:00 2001
|
||||
From: Boris Sekachev <boris.sekachev@intel.com>
|
||||
Date: Tue, 10 Jul 2018 14:31:13 +0300
|
||||
Subject: [PATCH] tmp
|
||||
|
||||
---
|
||||
.../engine/static/engine/js/3rdparty/svg.draggable.js | 1 +
|
||||
cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js | 17 +++++++++++++++--
|
||||
.../apps/engine/static/engine/js/3rdparty/svg.resize.js | 5 +++--
|
||||
.../apps/engine/static/engine/js/3rdparty/svg.select.js | 1 +
|
||||
4 files changed, 20 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
|
||||
index d88abf5..06158f1 100644
|
||||
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
|
||||
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.draggable.js
|
||||
@@ -109,6 +109,7 @@
|
||||
|
||||
// while dragging
|
||||
DragHandler.prototype.drag = function(e){
|
||||
+ this.m = this.el.node.getScreenCTM().inverse();
|
||||
|
||||
var box = this.getBBox()
|
||||
, p = this.transformPoint(e)
|
||||
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
|
||||
index 68dbf2a..9884b75 100644
|
||||
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
|
||||
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.draw.js
|
||||
@@ -18,6 +18,7 @@
|
||||
this.startPoint = null;
|
||||
this.lastUpdateCall = null;
|
||||
this.options = {};
|
||||
+ this.set = new SVG.Set();
|
||||
|
||||
// Merge options and defaults
|
||||
for (var i in this.el.draw.defaults) {
|
||||
@@ -139,6 +140,8 @@
|
||||
// Call the calc-function which calculates the new position and size
|
||||
this.calc(event);
|
||||
|
||||
+ this.m = this.el.node.getScreenCTM().inverse();
|
||||
+ this.offset = { x: window.pageXOffset, y: window.pageYOffset };
|
||||
// Fire the `drawupdate`-event
|
||||
this.el.fire('drawupdate', {event:event, p:this.p, m:this.m});
|
||||
};
|
||||
@@ -160,6 +163,16 @@
|
||||
this.el.fire('drawcancel');
|
||||
};
|
||||
|
||||
+ // Undo last drawed point
|
||||
+ PaintHandler.prototype.undo = function () {
|
||||
+ if (this.set.length()) {
|
||||
+ this.set.members.splice(-1, 1)[0].remove();
|
||||
+ this.el.array().value.splice(-2, 1);
|
||||
+ this.el.plot(this.el.array());
|
||||
+ this.el.fire('undopoint');
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
// Calculate the corrected position when using `snapToGrid`
|
||||
PaintHandler.prototype.snapToGrid = function (draw) {
|
||||
|
||||
@@ -371,14 +384,14 @@
|
||||
|
||||
this.set.clear();
|
||||
|
||||
- for (var i = 0; i < array.length; ++i) {
|
||||
+ for (var i = 0; i < array.length - 1; ++i) {
|
||||
|
||||
this.p.x = array[i][0]
|
||||
this.p.y = array[i][1]
|
||||
|
||||
var p = this.p.matrixTransform(this.parent.node.getScreenCTM().inverse().multiply(this.el.node.getScreenCTM()));
|
||||
|
||||
- this.set.add(this.parent.circle(5).stroke({width: 1}).fill('#ccc').center(p.x, p.y));
|
||||
+ this.set.add(this.parent.circle(5).stroke({width: 1}).fill('#ccc').center(p.x, p.y)).addClass("svg_draw_point");
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js
|
||||
index 0c3b63d..fb5dc26 100644
|
||||
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js
|
||||
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.resize.js
|
||||
@@ -34,8 +34,8 @@
|
||||
// Extract a position from a mouse/touch event.
|
||||
// Returns { x: .., y: .. }
|
||||
return {
|
||||
- x: event.clientX || event.touches[0].pageX,
|
||||
- y: event.clientY || event.touches[0].pageY
|
||||
+ x: event.clientX != null ? event.clientX : event.touches[0].clientX,
|
||||
+ y: event.clientY != null ? event.clientY : event.touches[0].clientY
|
||||
};
|
||||
};
|
||||
|
||||
@@ -343,6 +343,7 @@
|
||||
}
|
||||
return;
|
||||
}
|
||||
+ this.m = this.el.node.getScreenCTM().inverse();
|
||||
|
||||
// Calculate the difference between the mouseposition at start and now
|
||||
var txPt = this._extractPosition(event);
|
||||
diff --git a/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js b/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
|
||||
index 47e07bd..f1d0c02 100644
|
||||
--- a/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
|
||||
+++ b/cvat/apps/engine/static/engine/js/3rdparty/svg.select.js
|
||||
@@ -160,6 +160,7 @@ SelectHandler.prototype.drawPoints = function () {
|
||||
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
|
||||
ev.stopPropagation();
|
||||
|
||||
+ if (ev.which != 1) return false;
|
||||
var x = ev.pageX || ev.touches[0].pageX;
|
||||
var y = ev.pageY || ev.touches[0].pageY;
|
||||
_this.el.fire('point', {x: x, y: y, i: k, event: ev});
|
||||
--
|
||||
2.7.4
|
||||
|
||||
@ -0,0 +1,184 @@
|
||||
/**
|
||||
* @preserve jquery.fullscreen 1.1.5
|
||||
* https://github.com/kayahr/jquery-fullscreen-plugin
|
||||
* Copyright (C) 2012-2013 Klaus Reimer <k@ailis.de>
|
||||
* Licensed under the MIT license
|
||||
* (See http://www.opensource.org/licenses/mit-license)
|
||||
*/
|
||||
|
||||
(function(jQuery) {
|
||||
|
||||
/**
|
||||
* Sets or gets the fullscreen state.
|
||||
*
|
||||
* @param {boolean=} state
|
||||
* True to enable fullscreen mode, false to disable it. If not
|
||||
* specified then the current fullscreen state is returned.
|
||||
* @return {boolean|Element|jQuery|null}
|
||||
* When querying the fullscreen state then the current fullscreen
|
||||
* element (or true if browser doesn't support it) is returned
|
||||
* when browser is currently in full screen mode. False is returned
|
||||
* if browser is not in full screen mode. Null is returned if
|
||||
* browser doesn't support fullscreen mode at all. When setting
|
||||
* the fullscreen state then the current jQuery selection is
|
||||
* returned for chaining.
|
||||
* @this {jQuery}
|
||||
*/
|
||||
function fullScreen(state)
|
||||
{
|
||||
var e, func, doc;
|
||||
|
||||
// Do nothing when nothing was selected
|
||||
if (!this.length) return this;
|
||||
|
||||
// We only use the first selected element because it doesn't make sense
|
||||
// to fullscreen multiple elements.
|
||||
e = (/** @type {Element} */ this[0]);
|
||||
|
||||
// Find the real element and the document (Depends on whether the
|
||||
// document itself or a HTML element was selected)
|
||||
if (e.ownerDocument)
|
||||
{
|
||||
doc = e.ownerDocument;
|
||||
}
|
||||
else
|
||||
{
|
||||
doc = e;
|
||||
e = doc.documentElement;
|
||||
}
|
||||
|
||||
// When no state was specified then return the current state.
|
||||
if (state == null)
|
||||
{
|
||||
// When fullscreen mode is not supported then return null
|
||||
if (!((/** @type {?Function} */ doc["exitFullscreen"])
|
||||
|| (/** @type {?Function} */ doc["webkitExitFullscreen"])
|
||||
|| (/** @type {?Function} */ doc["webkitCancelFullScreen"])
|
||||
|| (/** @type {?Function} */ doc["msExitFullscreen"])
|
||||
|| (/** @type {?Function} */ doc["mozCancelFullScreen"])))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check fullscreen state
|
||||
state = !!doc["fullscreenElement"]
|
||||
|| !!doc["msFullscreenElement"]
|
||||
|| !!doc["webkitIsFullScreen"]
|
||||
|| !!doc["mozFullScreen"];
|
||||
if (!state) return state;
|
||||
|
||||
// Return current fullscreen element or "true" if browser doesn't
|
||||
// support this
|
||||
return (/** @type {?Element} */ doc["fullscreenElement"])
|
||||
|| (/** @type {?Element} */ doc["webkitFullscreenElement"])
|
||||
|| (/** @type {?Element} */ doc["webkitCurrentFullScreenElement"])
|
||||
|| (/** @type {?Element} */ doc["msFullscreenElement"])
|
||||
|| (/** @type {?Element} */ doc["mozFullScreenElement"])
|
||||
|| state;
|
||||
}
|
||||
|
||||
// When state was specified then enter or exit fullscreen mode.
|
||||
if (state)
|
||||
{
|
||||
// Enter fullscreen
|
||||
func = (/** @type {?Function} */ e["requestFullscreen"])
|
||||
|| (/** @type {?Function} */ e["webkitRequestFullscreen"])
|
||||
|| (/** @type {?Function} */ e["webkitRequestFullScreen"])
|
||||
|| (/** @type {?Function} */ e["msRequestFullscreen"])
|
||||
|| (/** @type {?Function} */ e["mozRequestFullScreen"]);
|
||||
if (func)
|
||||
{
|
||||
func.call(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Exit fullscreen
|
||||
func = (/** @type {?Function} */ doc["exitFullscreen"])
|
||||
|| (/** @type {?Function} */ doc["webkitExitFullscreen"])
|
||||
|| (/** @type {?Function} */ doc["webkitCancelFullScreen"])
|
||||
|| (/** @type {?Function} */ doc["msExitFullscreen"])
|
||||
|| (/** @type {?Function} */ doc["mozCancelFullScreen"]);
|
||||
if (func) func.call(doc);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the fullscreen mode.
|
||||
*
|
||||
* @return {!jQuery}
|
||||
* The jQuery selection for chaining.
|
||||
* @this {jQuery}
|
||||
*/
|
||||
function toggleFullScreen()
|
||||
{
|
||||
return (/** @type {!jQuery} */ fullScreen.call(this,
|
||||
!fullScreen.call(this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the browser-specific fullscreenchange event and triggers
|
||||
* a jquery event for it.
|
||||
*
|
||||
* @param {?Event} event
|
||||
* The fullscreenchange event.
|
||||
*/
|
||||
function fullScreenChangeHandler(event)
|
||||
{
|
||||
jQuery(document).trigger(new jQuery.Event("fullscreenchange"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the browser-specific fullscreenerror event and triggers
|
||||
* a jquery event for it.
|
||||
*
|
||||
* @param {?Event} event
|
||||
* The fullscreenerror event.
|
||||
*/
|
||||
function fullScreenErrorHandler(event)
|
||||
{
|
||||
jQuery(document).trigger(new jQuery.Event("fullscreenerror"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the fullscreenchange event handler.
|
||||
*/
|
||||
function installFullScreenHandlers()
|
||||
{
|
||||
var e, change, error;
|
||||
|
||||
// Determine event name
|
||||
e = document;
|
||||
if (e["webkitCancelFullScreen"])
|
||||
{
|
||||
change = "webkitfullscreenchange";
|
||||
error = "webkitfullscreenerror";
|
||||
}
|
||||
else if (e["msExitFullscreen"])
|
||||
{
|
||||
change = "MSFullscreenChange";
|
||||
error = "MSFullscreenError";
|
||||
}
|
||||
else if (e["mozCancelFullScreen"])
|
||||
{
|
||||
change = "mozfullscreenchange";
|
||||
error = "mozfullscreenerror";
|
||||
}
|
||||
else
|
||||
{
|
||||
change = "fullscreenchange";
|
||||
error = "fullscreenerror";
|
||||
}
|
||||
|
||||
// Install the event handlers
|
||||
jQuery(document).bind(change, fullScreenChangeHandler);
|
||||
jQuery(document).bind(error, fullScreenErrorHandler);
|
||||
}
|
||||
|
||||
jQuery.fn["fullScreen"] = fullScreen;
|
||||
jQuery.fn["toggleFullScreen"] = toggleFullScreen;
|
||||
installFullScreenHandlers();
|
||||
|
||||
})(jQuery);
|
||||
@ -0,0 +1,280 @@
|
||||
/*
|
||||
* JavaScript MD5
|
||||
* https://github.com/blueimp/JavaScript-MD5
|
||||
*
|
||||
* Copyright 2011, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* Based on
|
||||
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
|
||||
* Digest Algorithm, as defined in RFC 1321.
|
||||
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
|
||||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
|
||||
* Distributed under the BSD License
|
||||
* See http://pajhome.org.uk/crypt/md5 for more info.
|
||||
*/
|
||||
|
||||
/* global define */
|
||||
|
||||
;(function ($) {
|
||||
'use strict'
|
||||
|
||||
/*
|
||||
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
|
||||
* to work around bugs in some JS interpreters.
|
||||
*/
|
||||
function safeAdd (x, y) {
|
||||
var lsw = (x & 0xffff) + (y & 0xffff)
|
||||
var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
|
||||
return (msw << 16) | (lsw & 0xffff)
|
||||
}
|
||||
|
||||
/*
|
||||
* Bitwise rotate a 32-bit number to the left.
|
||||
*/
|
||||
function bitRotateLeft (num, cnt) {
|
||||
return (num << cnt) | (num >>> (32 - cnt))
|
||||
}
|
||||
|
||||
/*
|
||||
* These functions implement the four basic operations the algorithm uses.
|
||||
*/
|
||||
function md5cmn (q, a, b, x, s, t) {
|
||||
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)
|
||||
}
|
||||
function md5ff (a, b, c, d, x, s, t) {
|
||||
return md5cmn((b & c) | (~b & d), a, b, x, s, t)
|
||||
}
|
||||
function md5gg (a, b, c, d, x, s, t) {
|
||||
return md5cmn((b & d) | (c & ~d), a, b, x, s, t)
|
||||
}
|
||||
function md5hh (a, b, c, d, x, s, t) {
|
||||
return md5cmn(b ^ c ^ d, a, b, x, s, t)
|
||||
}
|
||||
function md5ii (a, b, c, d, x, s, t) {
|
||||
return md5cmn(c ^ (b | ~d), a, b, x, s, t)
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the MD5 of an array of little-endian words, and a bit length.
|
||||
*/
|
||||
function binlMD5 (x, len) {
|
||||
/* append padding */
|
||||
x[len >> 5] |= 0x80 << (len % 32)
|
||||
x[((len + 64) >>> 9 << 4) + 14] = len
|
||||
|
||||
var i
|
||||
var olda
|
||||
var oldb
|
||||
var oldc
|
||||
var oldd
|
||||
var a = 1732584193
|
||||
var b = -271733879
|
||||
var c = -1732584194
|
||||
var d = 271733878
|
||||
|
||||
for (i = 0; i < x.length; i += 16) {
|
||||
olda = a
|
||||
oldb = b
|
||||
oldc = c
|
||||
oldd = d
|
||||
|
||||
a = md5ff(a, b, c, d, x[i], 7, -680876936)
|
||||
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)
|
||||
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)
|
||||
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)
|
||||
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)
|
||||
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)
|
||||
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)
|
||||
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)
|
||||
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)
|
||||
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)
|
||||
c = md5ff(c, d, a, b, x[i + 10], 17, -42063)
|
||||
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)
|
||||
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)
|
||||
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)
|
||||
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)
|
||||
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)
|
||||
|
||||
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)
|
||||
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)
|
||||
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)
|
||||
b = md5gg(b, c, d, a, x[i], 20, -373897302)
|
||||
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)
|
||||
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)
|
||||
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)
|
||||
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)
|
||||
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)
|
||||
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)
|
||||
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)
|
||||
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)
|
||||
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)
|
||||
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)
|
||||
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)
|
||||
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)
|
||||
|
||||
a = md5hh(a, b, c, d, x[i + 5], 4, -378558)
|
||||
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)
|
||||
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)
|
||||
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)
|
||||
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)
|
||||
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)
|
||||
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)
|
||||
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)
|
||||
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)
|
||||
d = md5hh(d, a, b, c, x[i], 11, -358537222)
|
||||
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)
|
||||
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)
|
||||
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)
|
||||
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)
|
||||
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)
|
||||
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)
|
||||
|
||||
a = md5ii(a, b, c, d, x[i], 6, -198630844)
|
||||
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)
|
||||
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)
|
||||
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)
|
||||
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)
|
||||
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)
|
||||
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)
|
||||
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)
|
||||
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)
|
||||
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)
|
||||
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)
|
||||
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)
|
||||
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)
|
||||
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)
|
||||
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)
|
||||
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)
|
||||
|
||||
a = safeAdd(a, olda)
|
||||
b = safeAdd(b, oldb)
|
||||
c = safeAdd(c, oldc)
|
||||
d = safeAdd(d, oldd)
|
||||
}
|
||||
return [a, b, c, d]
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of little-endian words to a string
|
||||
*/
|
||||
function binl2rstr (input) {
|
||||
var i
|
||||
var output = ''
|
||||
var length32 = input.length * 32
|
||||
for (i = 0; i < length32; i += 8) {
|
||||
output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a raw string to an array of little-endian words
|
||||
* Characters >255 have their high-byte silently ignored.
|
||||
*/
|
||||
function rstr2binl (input) {
|
||||
var i
|
||||
var output = []
|
||||
output[(input.length >> 2) - 1] = undefined
|
||||
for (i = 0; i < output.length; i += 1) {
|
||||
output[i] = 0
|
||||
}
|
||||
var length8 = input.length * 8
|
||||
for (i = 0; i < length8; i += 8) {
|
||||
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the MD5 of a raw string
|
||||
*/
|
||||
function rstrMD5 (s) {
|
||||
return binl2rstr(binlMD5(rstr2binl(s), s.length * 8))
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the HMAC-MD5, of a key and some data (raw strings)
|
||||
*/
|
||||
function rstrHMACMD5 (key, data) {
|
||||
var i
|
||||
var bkey = rstr2binl(key)
|
||||
var ipad = []
|
||||
var opad = []
|
||||
var hash
|
||||
ipad[15] = opad[15] = undefined
|
||||
if (bkey.length > 16) {
|
||||
bkey = binlMD5(bkey, key.length * 8)
|
||||
}
|
||||
for (i = 0; i < 16; i += 1) {
|
||||
ipad[i] = bkey[i] ^ 0x36363636
|
||||
opad[i] = bkey[i] ^ 0x5c5c5c5c
|
||||
}
|
||||
hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8)
|
||||
return binl2rstr(binlMD5(opad.concat(hash), 512 + 128))
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a raw string to a hex string
|
||||
*/
|
||||
function rstr2hex (input) {
|
||||
var hexTab = '0123456789abcdef'
|
||||
var output = ''
|
||||
var x
|
||||
var i
|
||||
for (i = 0; i < input.length; i += 1) {
|
||||
x = input.charCodeAt(i)
|
||||
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode a string as utf-8
|
||||
*/
|
||||
function str2rstrUTF8 (input) {
|
||||
return unescape(encodeURIComponent(input))
|
||||
}
|
||||
|
||||
/*
|
||||
* Take string arguments and return either raw or hex encoded strings
|
||||
*/
|
||||
function rawMD5 (s) {
|
||||
return rstrMD5(str2rstrUTF8(s))
|
||||
}
|
||||
function hexMD5 (s) {
|
||||
return rstr2hex(rawMD5(s))
|
||||
}
|
||||
function rawHMACMD5 (k, d) {
|
||||
return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d))
|
||||
}
|
||||
function hexHMACMD5 (k, d) {
|
||||
return rstr2hex(rawHMACMD5(k, d))
|
||||
}
|
||||
|
||||
function md5 (string, key, raw) {
|
||||
if (!key) {
|
||||
if (!raw) {
|
||||
return hexMD5(string)
|
||||
}
|
||||
return rawMD5(string)
|
||||
}
|
||||
if (!raw) {
|
||||
return hexHMACMD5(key, string)
|
||||
}
|
||||
return rawHMACMD5(key, string)
|
||||
}
|
||||
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(function () {
|
||||
return md5
|
||||
})
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
module.exports = md5
|
||||
} else {
|
||||
$.md5 = md5
|
||||
}
|
||||
})(this)
|
||||
@ -0,0 +1,227 @@
|
||||
/*! svg.draggable.js - v2.2.1 - 2016-08-25
|
||||
* https://github.com/wout/svg.draggable.js
|
||||
* Copyright (c) 2016 Wout Fierens; Licensed MIT */
|
||||
;(function() {
|
||||
|
||||
// creates handler, saves it
|
||||
function DragHandler(el){
|
||||
el.remember('_draggable', this)
|
||||
this.el = el
|
||||
}
|
||||
|
||||
|
||||
// Sets new parameter, starts dragging
|
||||
DragHandler.prototype.init = function(constraint, val){
|
||||
var _this = this
|
||||
this.constraint = constraint
|
||||
this.value = val
|
||||
this.el.on('mousedown.drag', function(e){ _this.start(e) })
|
||||
this.el.on('touchstart.drag', function(e){ _this.start(e) })
|
||||
}
|
||||
|
||||
// transforms one point from screen to user coords
|
||||
DragHandler.prototype.transformPoint = function(event, offset){
|
||||
event = event || window.event
|
||||
var touches = event.changedTouches && event.changedTouches[0] || event
|
||||
this.p.x = touches.pageX - (offset || 0)
|
||||
this.p.y = touches.pageY
|
||||
return this.p.matrixTransform(this.m)
|
||||
}
|
||||
|
||||
// gets elements bounding box with special handling of groups, nested and use
|
||||
DragHandler.prototype.getBBox = function(){
|
||||
|
||||
var box = this.el.bbox()
|
||||
|
||||
if(this.el instanceof SVG.Nested) box = this.el.rbox()
|
||||
|
||||
if (this.el instanceof SVG.G || this.el instanceof SVG.Use || this.el instanceof SVG.Nested) {
|
||||
box.x = this.el.x()
|
||||
box.y = this.el.y()
|
||||
}
|
||||
|
||||
return box
|
||||
}
|
||||
|
||||
// start dragging
|
||||
DragHandler.prototype.start = function(e){
|
||||
|
||||
// check for left button
|
||||
if(e.type == 'click'|| e.type == 'mousedown' || e.type == 'mousemove'){
|
||||
if((e.which || e.buttons) != 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var _this = this
|
||||
|
||||
// fire beforedrag event
|
||||
this.el.fire('beforedrag', { event: e, handler: this })
|
||||
|
||||
// search for parent on the fly to make sure we can call
|
||||
// draggable() even when element is not in the dom currently
|
||||
this.parent = this.parent || this.el.parent(SVG.Nested) || this.el.parent(SVG.Doc)
|
||||
this.p = this.parent.node.createSVGPoint()
|
||||
|
||||
// save current transformation matrix
|
||||
this.m = this.el.node.getScreenCTM().inverse()
|
||||
|
||||
var box = this.getBBox()
|
||||
|
||||
var anchorOffset;
|
||||
|
||||
// fix text-anchor in text-element (#37)
|
||||
if(this.el instanceof SVG.Text){
|
||||
anchorOffset = this.el.node.getComputedTextLength();
|
||||
|
||||
switch(this.el.attr('text-anchor')){
|
||||
case 'middle':
|
||||
anchorOffset /= 2;
|
||||
break
|
||||
case 'start':
|
||||
anchorOffset = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.startPoints = {
|
||||
// We take absolute coordinates since we are just using a delta here
|
||||
point: this.transformPoint(e, anchorOffset),
|
||||
box: box,
|
||||
transform: this.el.transform()
|
||||
}
|
||||
|
||||
// add drag and end events to window
|
||||
SVG.on(window, 'mousemove.drag', function(e){ _this.drag(e) })
|
||||
SVG.on(window, 'touchmove.drag', function(e){ _this.drag(e) })
|
||||
SVG.on(window, 'mouseup.drag', function(e){ _this.end(e) })
|
||||
SVG.on(window, 'touchend.drag', function(e){ _this.end(e) })
|
||||
|
||||
// fire dragstart event
|
||||
this.el.fire('dragstart', {event: e, p: this.startPoints.point, m: this.m, handler: this})
|
||||
|
||||
// prevent browser drag behavior
|
||||
e.preventDefault()
|
||||
|
||||
// prevent propagation to a parent that might also have dragging enabled
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// while dragging
|
||||
DragHandler.prototype.drag = function(e){
|
||||
|
||||
var box = this.getBBox()
|
||||
, p = this.transformPoint(e)
|
||||
, x = this.startPoints.box.x + p.x - this.startPoints.point.x
|
||||
, y = this.startPoints.box.y + p.y - this.startPoints.point.y
|
||||
, c = this.constraint
|
||||
, gx = p.x - this.startPoints.point.x
|
||||
, gy = p.y - this.startPoints.point.y
|
||||
|
||||
var event = new CustomEvent('dragmove', {
|
||||
detail: {
|
||||
event: e
|
||||
, p: p
|
||||
, m: this.m
|
||||
, handler: this
|
||||
}
|
||||
, cancelable: true
|
||||
})
|
||||
|
||||
this.el.fire(event)
|
||||
|
||||
if(event.defaultPrevented) return p
|
||||
|
||||
// move the element to its new position, if possible by constraint
|
||||
if (typeof c == 'function') {
|
||||
|
||||
var coord = c.call(this.el, x, y, this.m)
|
||||
|
||||
// bool, just show us if movement is allowed or not
|
||||
if (typeof coord == 'boolean') {
|
||||
coord = {
|
||||
x: coord,
|
||||
y: coord
|
||||
}
|
||||
}
|
||||
|
||||
// if true, we just move. If !false its a number and we move it there
|
||||
if (coord.x === true) {
|
||||
this.el.x(x)
|
||||
} else if (coord.x !== false) {
|
||||
this.el.x(coord.x)
|
||||
}
|
||||
|
||||
if (coord.y === true) {
|
||||
this.el.y(y)
|
||||
} else if (coord.y !== false) {
|
||||
this.el.y(coord.y)
|
||||
}
|
||||
|
||||
} else if (typeof c == 'object') {
|
||||
|
||||
// keep element within constrained box
|
||||
if (c.minX != null && x < c.minX)
|
||||
x = c.minX
|
||||
else if (c.maxX != null && x > c.maxX - box.width){
|
||||
x = c.maxX - box.width
|
||||
}if (c.minY != null && y < c.minY)
|
||||
y = c.minY
|
||||
else if (c.maxY != null && y > c.maxY - box.height)
|
||||
y = c.maxY - box.height
|
||||
|
||||
if(this.el instanceof SVG.G)
|
||||
this.el.matrix(this.startPoints.transform).transform({x:gx, y: gy}, true)
|
||||
else
|
||||
this.el.move(x, y)
|
||||
}
|
||||
|
||||
// so we can use it in the end-method, too
|
||||
return p
|
||||
}
|
||||
|
||||
DragHandler.prototype.end = function(e){
|
||||
|
||||
// final drag
|
||||
var p = this.drag(e);
|
||||
|
||||
// fire dragend event
|
||||
this.el.fire('dragend', { event: e, p: p, m: this.m, handler: this })
|
||||
|
||||
// unbind events
|
||||
SVG.off(window, 'mousemove.drag')
|
||||
SVG.off(window, 'touchmove.drag')
|
||||
SVG.off(window, 'mouseup.drag')
|
||||
SVG.off(window, 'touchend.drag')
|
||||
|
||||
}
|
||||
|
||||
SVG.extend(SVG.Element, {
|
||||
// Make element draggable
|
||||
// Constraint might be an object (as described in readme.md) or a function in the form "function (x, y)" that gets called before every move.
|
||||
// The function can return a boolean or an object of the form {x, y}, to which the element will be moved. "False" skips moving, true moves to raw x, y.
|
||||
draggable: function(value, constraint) {
|
||||
|
||||
// Check the parameters and reassign if needed
|
||||
if (typeof value == 'function' || typeof value == 'object') {
|
||||
constraint = value
|
||||
value = true
|
||||
}
|
||||
|
||||
var dragHandler = this.remember('_draggable') || new DragHandler(this)
|
||||
|
||||
// When no parameter is given, value is true
|
||||
value = typeof value === 'undefined' ? true : value
|
||||
|
||||
if(value) dragHandler.init(constraint || {}, value)
|
||||
else {
|
||||
this.off('mousedown.drag')
|
||||
this.off('touchstart.drag')
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}).call(this);
|
||||
@ -0,0 +1,442 @@
|
||||
/*! svg.draw.js - v2.0.3 - 2017-06-19
|
||||
* https://github.com/svgdotjs/svg.draw.js
|
||||
* Copyright (c) 2017 Ulrich-Matthias Schäfer; Licensed MIT */
|
||||
|
||||
;(function () {
|
||||
// Our Object which manages drawing
|
||||
function PaintHandler(el, event, options) {
|
||||
|
||||
this.el = el;
|
||||
el.remember('_paintHandler', this);
|
||||
|
||||
var _this = this,
|
||||
plugin = this.getPlugin();
|
||||
|
||||
this.parent = el.parent(SVG.Nested) || el.parent(SVG.Doc);
|
||||
this.p = this.parent.node.createSVGPoint(); // Helping point for coord transformation
|
||||
this.m = null; // transformation matrix. We get it when drawing starts
|
||||
this.startPoint = null;
|
||||
this.lastUpdateCall = null;
|
||||
this.options = {};
|
||||
|
||||
// Merge options and defaults
|
||||
for (var i in this.el.draw.defaults) {
|
||||
this.options[i] = this.el.draw.defaults[i];
|
||||
if (typeof options[i] !== 'undefined') {
|
||||
this.options[i] = options[i];
|
||||
}
|
||||
}
|
||||
|
||||
if(plugin.point) {
|
||||
plugin['pointPlugin'] = plugin.point;
|
||||
delete plugin.point;
|
||||
}
|
||||
|
||||
// Import all methods from plugin into object
|
||||
for (var i in plugin){
|
||||
this[i] = plugin[i];
|
||||
}
|
||||
|
||||
// When we got an event, we use this for start, otherwise we use the click-event as default
|
||||
if (!event) {
|
||||
this.parent.on('click.draw', function (e) {
|
||||
_this.start(e);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PaintHandler.prototype.transformPoint = function(x, y){
|
||||
|
||||
this.p.x = x - (this.offset.x - window.pageXOffset);
|
||||
this.p.y = y - (this.offset.y - window.pageYOffset);
|
||||
|
||||
return this.p.matrixTransform(this.m);
|
||||
|
||||
}
|
||||
|
||||
PaintHandler.prototype.start = function (event) {
|
||||
|
||||
var _this = this;
|
||||
|
||||
// get the current transform matrix from screen to element (offset corrected)
|
||||
this.m = this.el.node.getScreenCTM().inverse();
|
||||
|
||||
// we save the current scrolling-offset here
|
||||
this.offset = { x: window.pageXOffset, y: window.pageYOffset };
|
||||
|
||||
// we want to snap in screen-coords, so we have to scale the snapToGrid accordingly
|
||||
this.options.snapToGrid *= Math.sqrt(this.m.a * this.m.a + this.m.b * this.m.b)
|
||||
|
||||
// save the startpoint
|
||||
this.startPoint = this.snapToGrid(this.transformPoint(event.clientX, event.clientY));
|
||||
|
||||
// the plugin may do some initial work
|
||||
if(this.init){ this.init(event); }
|
||||
|
||||
// Fire our `drawstart`-event. We send the offset-corrected cursor-position along
|
||||
this.el.fire('drawstart', {event:event, p:this.p, m:this.m});
|
||||
|
||||
// We need to bind the update-function to the mousemove event to keep track of the cursor
|
||||
SVG.on(window, 'mousemove.draw', function (e) {
|
||||
_this.update(e);
|
||||
});
|
||||
|
||||
// Every consecutive call to start should map to point now
|
||||
this.start = this.point;
|
||||
|
||||
|
||||
};
|
||||
|
||||
// This function draws a point if the element is a polyline or polygon
|
||||
// Otherwise it will just stop drawing the shape cause we are done
|
||||
PaintHandler.prototype.point = function (event) {
|
||||
if (this.point != this.start) return this.start(event);
|
||||
|
||||
if (this.pointPlugin) {
|
||||
return this.pointPlugin(event);
|
||||
}
|
||||
|
||||
// If this function is not overwritten we just call stop
|
||||
this.stop(event);
|
||||
};
|
||||
|
||||
|
||||
// The stop-function does the cleanup work
|
||||
PaintHandler.prototype.stop = function (event) {
|
||||
if (event) {
|
||||
this.update(event);
|
||||
}
|
||||
|
||||
// Plugin may want to clean something
|
||||
if(this.clean){ this.clean(); }
|
||||
|
||||
// Unbind from all events
|
||||
SVG.off(window, 'mousemove.draw');
|
||||
this.parent.off('click.draw');
|
||||
|
||||
// remove Refernce to PaintHandler
|
||||
this.el.forget('_paintHandler');
|
||||
|
||||
// overwrite draw-function since we never need it again for this element
|
||||
this.el.draw = function () {
|
||||
};
|
||||
|
||||
// Fire the `drawstop`-event
|
||||
this.el.fire('drawstop');
|
||||
};
|
||||
|
||||
// Updates the element while moving the cursor
|
||||
PaintHandler.prototype.update = function (event) {
|
||||
|
||||
if(!event && this.lastUpdateCall){
|
||||
event = this.lastUpdateCall;
|
||||
}
|
||||
|
||||
this.lastUpdateCall = event;
|
||||
|
||||
// Call the calc-function which calculates the new position and size
|
||||
this.calc(event);
|
||||
|
||||
// Fire the `drawupdate`-event
|
||||
this.el.fire('drawupdate', {event:event, p:this.p, m:this.m});
|
||||
};
|
||||
|
||||
// Called from outside. Finishs a poly-element
|
||||
PaintHandler.prototype.done = function () {
|
||||
this.calc();
|
||||
this.stop();
|
||||
|
||||
this.el.fire('drawdone');
|
||||
};
|
||||
|
||||
// Called from outside. Cancels a poly-element
|
||||
PaintHandler.prototype.cancel = function () {
|
||||
// stop drawing and remove the element
|
||||
this.stop();
|
||||
this.el.remove();
|
||||
|
||||
this.el.fire('drawcancel');
|
||||
};
|
||||
|
||||
// Calculate the corrected position when using `snapToGrid`
|
||||
PaintHandler.prototype.snapToGrid = function (draw) {
|
||||
|
||||
var temp = null;
|
||||
|
||||
// An array was given. Loop through every element
|
||||
if (draw.length) {
|
||||
temp = [draw[0] % this.options.snapToGrid, draw[1] % this.options.snapToGrid];
|
||||
draw[0] -= temp[0] < this.options.snapToGrid / 2 ? temp[0] : temp[0] - this.options.snapToGrid;
|
||||
draw[1] -= temp[1] < this.options.snapToGrid / 2 ? temp[1] : temp[1] - this.options.snapToGrid;
|
||||
return draw;
|
||||
}
|
||||
|
||||
// Properties of element were given. Snap them all
|
||||
for (var i in draw) {
|
||||
temp = draw[i] % this.options.snapToGrid;
|
||||
draw[i] -= (temp < this.options.snapToGrid / 2 ? temp : temp - this.options.snapToGrid) + (temp < 0 ? this.options.snapToGrid : 0);
|
||||
}
|
||||
|
||||
return draw;
|
||||
};
|
||||
|
||||
PaintHandler.prototype.param = function (key, value) {
|
||||
this.options[key] = value === null ? this.el.draw.defaults[key] : value;
|
||||
this.update();
|
||||
};
|
||||
|
||||
// Returns the plugin
|
||||
PaintHandler.prototype.getPlugin = function () {
|
||||
return this.el.draw.plugins[this.el.type];
|
||||
};
|
||||
|
||||
SVG.extend(SVG.Element, {
|
||||
// Draw element with mouse
|
||||
draw: function (event, options, value) {
|
||||
|
||||
// sort the parameters
|
||||
if (!(event instanceof Event || typeof event === 'string')) {
|
||||
options = event;
|
||||
event = null;
|
||||
}
|
||||
|
||||
// get the old Handler or create a new one from event and options
|
||||
var paintHandler = this.remember('_paintHandler') || new PaintHandler(this, event, options || {});
|
||||
|
||||
// When we got an event we have to start/continue drawing
|
||||
if (event instanceof Event) {
|
||||
paintHandler['start'](event);
|
||||
}
|
||||
|
||||
// if event is located in our PaintHandler we handle it as method
|
||||
if (paintHandler[event]) {
|
||||
paintHandler[event](options, value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Default values. Can be changed for the whole project if needed
|
||||
SVG.Element.prototype.draw.defaults = {
|
||||
snapToGrid: 1 // Snaps to a grid of `snapToGrid` px
|
||||
};
|
||||
|
||||
SVG.Element.prototype.draw.extend = function(name, obj){
|
||||
|
||||
var plugins = {};
|
||||
if(typeof name === 'string'){
|
||||
plugins[name] = obj;
|
||||
}else{
|
||||
plugins = name;
|
||||
}
|
||||
|
||||
for(var shapes in plugins){
|
||||
var shapesArr = shapes.trim().split(/\s+/);
|
||||
|
||||
for(var i in shapesArr){
|
||||
SVG.Element.prototype.draw.plugins[shapesArr[i]] = plugins[shapes];
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Container for all types not specified here
|
||||
SVG.Element.prototype.draw.plugins = {};
|
||||
|
||||
SVG.Element.prototype.draw.extend('rect image', {
|
||||
|
||||
init:function(e){
|
||||
|
||||
var p = this.startPoint;
|
||||
|
||||
this.el.attr({ x: p.x, y: p.y, height: 0, width: 0 });
|
||||
},
|
||||
|
||||
calc:function (e) {
|
||||
|
||||
var rect = {
|
||||
x: this.startPoint.x,
|
||||
y: this.startPoint.y
|
||||
}, p = this.transformPoint(e.clientX, e.clientY);
|
||||
|
||||
rect.width = p.x - rect.x;
|
||||
rect.height = p.y - rect.y;
|
||||
|
||||
// Snap the params to the grid we specified
|
||||
this.snapToGrid(rect);
|
||||
|
||||
// When width is less than zero, we have to draw to the left
|
||||
// which means we have to move the start-point to the left
|
||||
if (rect.width < 0) {
|
||||
rect.x = rect.x + rect.width;
|
||||
rect.width = -rect.width;
|
||||
}
|
||||
|
||||
// ...same with height
|
||||
if (rect.height < 0) {
|
||||
rect.y = rect.y + rect.height;
|
||||
rect.height = -rect.height;
|
||||
}
|
||||
|
||||
// draw the element
|
||||
this.el.attr(rect);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
SVG.Element.prototype.draw.extend('line polyline polygon', {
|
||||
|
||||
init:function(e){
|
||||
// When we draw a polygon, we immediately need 2 points.
|
||||
// One start-point and one point at the mouse-position
|
||||
|
||||
this.set = new SVG.Set();
|
||||
|
||||
var p = this.startPoint,
|
||||
arr = [
|
||||
[p.x, p.y],
|
||||
[p.x, p.y]
|
||||
];
|
||||
|
||||
this.el.plot(arr);
|
||||
|
||||
// We draw little circles around each point
|
||||
// This is absolutely not needed and maybe removed in a later release
|
||||
this.drawCircles();
|
||||
|
||||
},
|
||||
|
||||
|
||||
// The calc-function sets the position of the last point to the mouse-position (with offset ofc)
|
||||
calc:function (e) {
|
||||
var arr = this.el.array().valueOf();
|
||||
arr.pop();
|
||||
|
||||
if (e) {
|
||||
var p = this.transformPoint(e.clientX, e.clientY);
|
||||
arr.push(this.snapToGrid([p.x, p.y]));
|
||||
}
|
||||
|
||||
this.el.plot(arr);
|
||||
|
||||
},
|
||||
|
||||
point:function(e){
|
||||
|
||||
if (this.el.type.indexOf('poly') > -1) {
|
||||
// Add the new Point to the point-array
|
||||
var p = this.transformPoint(e.clientX, e.clientY),
|
||||
arr = this.el.array().valueOf();
|
||||
|
||||
arr.push(this.snapToGrid([p.x, p.y]));
|
||||
|
||||
this.el.plot(arr);
|
||||
this.drawCircles();
|
||||
|
||||
// Fire the `drawpoint`-event, which holds the coords of the new Point
|
||||
this.el.fire('drawpoint', {event:e, p:{x:p.x, y:p.y}, m:this.m});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We are done, if the element is no polyline or polygon
|
||||
this.stop(e);
|
||||
|
||||
},
|
||||
|
||||
clean:function(){
|
||||
|
||||
// Remove all circles
|
||||
this.set.each(function () {
|
||||
this.remove();
|
||||
});
|
||||
|
||||
this.set.clear();
|
||||
|
||||
delete this.set;
|
||||
|
||||
},
|
||||
|
||||
drawCircles:function () {
|
||||
var array = this.el.array().valueOf()
|
||||
|
||||
this.set.each(function () {
|
||||
this.remove();
|
||||
});
|
||||
|
||||
this.set.clear();
|
||||
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
|
||||
this.p.x = array[i][0]
|
||||
this.p.y = array[i][1]
|
||||
|
||||
var p = this.p.matrixTransform(this.parent.node.getScreenCTM().inverse().multiply(this.el.node.getScreenCTM()));
|
||||
|
||||
this.set.add(this.parent.circle(5).stroke({width: 1}).fill('#ccc').center(p.x, p.y));
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
SVG.Element.prototype.draw.extend('circle', {
|
||||
|
||||
init:function(e){
|
||||
|
||||
var p = this.startPoint;
|
||||
|
||||
this.el.attr({ cx: p.x, cy: p.y, r: 1 });
|
||||
},
|
||||
|
||||
// We determine the radius by the cursor position
|
||||
calc:function (e) {
|
||||
|
||||
var p = this.transformPoint(e.clientX, e.clientY),
|
||||
circle = {
|
||||
cx: this.startPoint.x,
|
||||
cy: this.startPoint.y,
|
||||
|
||||
// calculating the radius
|
||||
r: Math.sqrt(
|
||||
(p.x - this.startPoint.x) * (p.x - this.startPoint.x) +
|
||||
(p.y - this.startPoint.y) * (p.y - this.startPoint.y)
|
||||
)
|
||||
};
|
||||
|
||||
this.snapToGrid(circle);
|
||||
this.el.attr(circle);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
SVG.Element.prototype.draw.extend('ellipse', {
|
||||
|
||||
init:function(e){
|
||||
// We start with a circle with radius 1 at the position of the cursor
|
||||
var p = this.startPoint;
|
||||
|
||||
this.el.attr({ cx: p.x, cy: p.y, rx: 1, ry: 1 });
|
||||
|
||||
},
|
||||
|
||||
calc:function (e) {
|
||||
var p = this.transformPoint(e.clientX, e.clientY);
|
||||
|
||||
var ellipse = {
|
||||
cx: this.startPoint.x,
|
||||
cy: this.startPoint.y,
|
||||
rx: Math.abs(p.x - this.startPoint.x),
|
||||
ry: Math.abs(p.y - this.startPoint.y)
|
||||
};
|
||||
|
||||
this.snapToGrid(ellipse);
|
||||
this.el.attr(ellipse);
|
||||
}
|
||||
|
||||
});
|
||||
}).call(this);
|
||||
@ -0,0 +1,454 @@
|
||||
/*!
|
||||
* svg.resize.js - An extension for svg.js which allows to resize elements which are selected
|
||||
* @version 1.4.1
|
||||
* https://github.com/svgdotjs/svg.resize.js
|
||||
*
|
||||
* @copyright [object Object]
|
||||
* @license MIT
|
||||
*/;
|
||||
;(function() {
|
||||
"use strict";
|
||||
|
||||
;(function () {
|
||||
|
||||
function ResizeHandler(el) {
|
||||
|
||||
el.remember('_resizeHandler', this);
|
||||
|
||||
this.el = el;
|
||||
this.parameters = {};
|
||||
this.lastUpdateCall = null;
|
||||
this.p = el.doc().node.createSVGPoint();
|
||||
}
|
||||
|
||||
ResizeHandler.prototype.transformPoint = function(x, y, m){
|
||||
|
||||
this.p.x = x - (this.offset.x - window.pageXOffset);
|
||||
this.p.y = y - (this.offset.y - window.pageYOffset);
|
||||
|
||||
return this.p.matrixTransform(m || this.m);
|
||||
|
||||
};
|
||||
|
||||
ResizeHandler.prototype._extractPosition = function(event) {
|
||||
// Extract a position from a mouse/touch event.
|
||||
// Returns { x: .., y: .. }
|
||||
return {
|
||||
x: event.clientX || event.touches[0].pageX,
|
||||
y: event.clientY || event.touches[0].pageY
|
||||
};
|
||||
};
|
||||
|
||||
ResizeHandler.prototype.init = function (options) {
|
||||
|
||||
var _this = this;
|
||||
|
||||
this.stop();
|
||||
|
||||
if (options === 'stop') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.options = {};
|
||||
|
||||
// Merge options and defaults
|
||||
for (var i in this.el.resize.defaults) {
|
||||
this.options[i] = this.el.resize.defaults[i];
|
||||
if (typeof options[i] !== 'undefined') {
|
||||
this.options[i] = options[i];
|
||||
}
|
||||
}
|
||||
|
||||
// We listen to all these events which are specifying different edges
|
||||
this.el.on('lt.resize', function(e){ _this.resize(e || window.event); }); // Left-Top
|
||||
this.el.on('rt.resize', function(e){ _this.resize(e || window.event); }); // Right-Top
|
||||
this.el.on('rb.resize', function(e){ _this.resize(e || window.event); }); // Right-Bottom
|
||||
this.el.on('lb.resize', function(e){ _this.resize(e || window.event); }); // Left-Bottom
|
||||
|
||||
this.el.on('t.resize', function(e){ _this.resize(e || window.event); }); // Top
|
||||
this.el.on('r.resize', function(e){ _this.resize(e || window.event); }); // Right
|
||||
this.el.on('b.resize', function(e){ _this.resize(e || window.event); }); // Bottom
|
||||
this.el.on('l.resize', function(e){ _this.resize(e || window.event); }); // Left
|
||||
|
||||
this.el.on('rot.resize', function(e){ _this.resize(e || window.event); }); // Rotation
|
||||
|
||||
this.el.on('point.resize', function(e){ _this.resize(e || window.event); }); // Point-Moving
|
||||
|
||||
// This call ensures, that the plugin reacts to a change of snapToGrid immediately
|
||||
this.update();
|
||||
|
||||
};
|
||||
|
||||
ResizeHandler.prototype.stop = function(){
|
||||
this.el.off('lt.resize');
|
||||
this.el.off('rt.resize');
|
||||
this.el.off('rb.resize');
|
||||
this.el.off('lb.resize');
|
||||
|
||||
this.el.off('t.resize');
|
||||
this.el.off('r.resize');
|
||||
this.el.off('b.resize');
|
||||
this.el.off('l.resize');
|
||||
|
||||
this.el.off('rot.resize');
|
||||
|
||||
this.el.off('point.resize');
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
ResizeHandler.prototype.resize = function (event) {
|
||||
|
||||
var _this = this;
|
||||
|
||||
this.m = this.el.node.getScreenCTM().inverse();
|
||||
this.offset = { x: window.pageXOffset, y: window.pageYOffset };
|
||||
|
||||
var txPt = this._extractPosition(event.detail.event);
|
||||
this.parameters = {
|
||||
type: this.el.type, // the type of element
|
||||
p: this.transformPoint(txPt.x, txPt.y),
|
||||
x: event.detail.x, // x-position of the mouse when resizing started
|
||||
y: event.detail.y, // y-position of the mouse when resizing started
|
||||
box: this.el.bbox(), // The bounding-box of the element
|
||||
rotation: this.el.transform().rotation // The current rotation of the element
|
||||
};
|
||||
|
||||
// Add font-size parameter if the element type is text
|
||||
if (this.el.type === "text") {
|
||||
this.parameters.fontSize = this.el.attr()["font-size"];
|
||||
}
|
||||
|
||||
// the i-param in the event holds the index of the point which is moved, when using `deepSelect`
|
||||
if (event.detail.i !== undefined) {
|
||||
|
||||
// get the point array
|
||||
var array = this.el.array().valueOf();
|
||||
|
||||
// Save the index and the point which is moved
|
||||
this.parameters.i = event.detail.i;
|
||||
this.parameters.pointCoords = [array[event.detail.i][0], array[event.detail.i][1]];
|
||||
}
|
||||
|
||||
// Lets check which edge of the bounding-box was clicked and resize the this.el according to this
|
||||
switch (event.type) {
|
||||
|
||||
// Left-Top-Edge
|
||||
case 'lt':
|
||||
// We build a calculating function for every case which gives us the new position of the this.el
|
||||
this.calc = function (diffX, diffY) {
|
||||
// The procedure is always the same
|
||||
// First we snap the edge to the given grid (snapping to 1px grid is normal resizing)
|
||||
var snap = this.snapToGrid(diffX, diffY);
|
||||
|
||||
// Now we check if the new height and width still valid (> 0)
|
||||
if (this.parameters.box.width - snap[0] > 0 && this.parameters.box.height - snap[1] > 0) {
|
||||
// ...if valid, we resize the this.el (which can include moving because the coord-system starts at the left-top and this edge is moving sometimes when resized)
|
||||
|
||||
/*
|
||||
* but first check if the element is text box, so we can change the font size instead of
|
||||
* the width and height
|
||||
*/
|
||||
|
||||
if (this.parameters.type === "text") {
|
||||
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y);
|
||||
this.el.attr("font-size", this.parameters.fontSize - snap[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y + snap[1]).size(this.parameters.box.width - snap[0], this.parameters.box.height - snap[1]);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
// Right-Top
|
||||
case 'rt':
|
||||
// s.a.
|
||||
this.calc = function (diffX, diffY) {
|
||||
var snap = this.snapToGrid(diffX, diffY, 1 << 1);
|
||||
if (this.parameters.box.width + snap[0] > 0 && this.parameters.box.height - snap[1] > 0) {
|
||||
if (this.parameters.type === "text") {
|
||||
this.el.move(this.parameters.box.x - snap[0], this.parameters.box.y);
|
||||
this.el.attr("font-size", this.parameters.fontSize + snap[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.move(this.parameters.box.x, this.parameters.box.y + snap[1]).size(this.parameters.box.width + snap[0], this.parameters.box.height - snap[1]);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
// Right-Bottom
|
||||
case 'rb':
|
||||
// s.a.
|
||||
this.calc = function (diffX, diffY) {
|
||||
var snap = this.snapToGrid(diffX, diffY, 0);
|
||||
if (this.parameters.box.width + snap[0] > 0 && this.parameters.box.height + snap[1] > 0) {
|
||||
if (this.parameters.type === "text") {
|
||||
this.el.move(this.parameters.box.x - snap[0], this.parameters.box.y);
|
||||
this.el.attr("font-size", this.parameters.fontSize + snap[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.move(this.parameters.box.x, this.parameters.box.y).size(this.parameters.box.width + snap[0], this.parameters.box.height + snap[1]);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
// Left-Bottom
|
||||
case 'lb':
|
||||
// s.a.
|
||||
this.calc = function (diffX, diffY) {
|
||||
var snap = this.snapToGrid(diffX, diffY, 1);
|
||||
if (this.parameters.box.width - snap[0] > 0 && this.parameters.box.height + snap[1] > 0) {
|
||||
if (this.parameters.type === "text") {
|
||||
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y);
|
||||
this.el.attr("font-size", this.parameters.fontSize - snap[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y).size(this.parameters.box.width - snap[0], this.parameters.box.height + snap[1]);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
// Top
|
||||
case 't':
|
||||
// s.a.
|
||||
this.calc = function (diffX, diffY) {
|
||||
var snap = this.snapToGrid(diffX, diffY, 1 << 1);
|
||||
if (this.parameters.box.height - snap[1] > 0) {
|
||||
// Disable the font-resizing if it is not from the corner of bounding-box
|
||||
if (this.parameters.type === "text") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.move(this.parameters.box.x, this.parameters.box.y + snap[1]).height(this.parameters.box.height - snap[1]);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
// Right
|
||||
case 'r':
|
||||
// s.a.
|
||||
this.calc = function (diffX, diffY) {
|
||||
var snap = this.snapToGrid(diffX, diffY, 0);
|
||||
if (this.parameters.box.width + snap[0] > 0) {
|
||||
if (this.parameters.type === "text") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.move(this.parameters.box.x, this.parameters.box.y).width(this.parameters.box.width + snap[0]);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
// Bottom
|
||||
case 'b':
|
||||
// s.a.
|
||||
this.calc = function (diffX, diffY) {
|
||||
var snap = this.snapToGrid(diffX, diffY, 0);
|
||||
if (this.parameters.box.height + snap[1] > 0) {
|
||||
if (this.parameters.type === "text") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.move(this.parameters.box.x, this.parameters.box.y).height(this.parameters.box.height + snap[1]);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
// Left
|
||||
case 'l':
|
||||
// s.a.
|
||||
this.calc = function (diffX, diffY) {
|
||||
var snap = this.snapToGrid(diffX, diffY, 1);
|
||||
if (this.parameters.box.width - snap[0] > 0) {
|
||||
if (this.parameters.type === "text") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y).width(this.parameters.box.width - snap[0]);
|
||||
}
|
||||
};
|
||||
break;
|
||||
|
||||
// Rotation
|
||||
case 'rot':
|
||||
// s.a.
|
||||
this.calc = function (diffX, diffY) {
|
||||
|
||||
// yes this is kinda stupid but we need the mouse coords back...
|
||||
var current = {x: diffX + this.parameters.p.x, y: diffY + this.parameters.p.y};
|
||||
|
||||
// start minus middle
|
||||
var sAngle = Math.atan2((this.parameters.p.y - this.parameters.box.y - this.parameters.box.height / 2), (this.parameters.p.x - this.parameters.box.x - this.parameters.box.width / 2));
|
||||
|
||||
// end minus middle
|
||||
var pAngle = Math.atan2((current.y - this.parameters.box.y - this.parameters.box.height / 2), (current.x - this.parameters.box.x - this.parameters.box.width / 2));
|
||||
|
||||
var angle = (pAngle - sAngle) * 180 / Math.PI;
|
||||
|
||||
// We have to move the element to the center of the box first and change the rotation afterwards
|
||||
// because rotation always works around a rotation-center, which is changed when moving the element
|
||||
// We also set the new rotation center to the center of the box.
|
||||
this.el.center(this.parameters.box.cx, this.parameters.box.cy).rotate(this.parameters.rotation + angle - angle % this.options.snapToAngle, this.parameters.box.cx, this.parameters.box.cy);
|
||||
};
|
||||
break;
|
||||
|
||||
// Moving one single Point (needed when an element is deepSelected which means you can move every single point of the object)
|
||||
case 'point':
|
||||
this.calc = function (diffX, diffY) {
|
||||
|
||||
// Snapping the point to the grid
|
||||
var snap = this.snapToGrid(diffX, diffY, this.parameters.pointCoords[0], this.parameters.pointCoords[1]);
|
||||
|
||||
// Get the point array
|
||||
var array = this.el.array().valueOf();
|
||||
|
||||
// Changing the moved point in the array
|
||||
array[this.parameters.i][0] = this.parameters.pointCoords[0] + snap[0];
|
||||
array[this.parameters.i][1] = this.parameters.pointCoords[1] + snap[1];
|
||||
|
||||
// And plot the new this.el
|
||||
this.el.plot(array);
|
||||
};
|
||||
}
|
||||
|
||||
this.el.fire('resizestart', {dx: this.parameters.x, dy: this.parameters.y, event: event});
|
||||
// When resizing started, we have to register events for...
|
||||
// Touches.
|
||||
SVG.on(window, 'touchmove.resize', function(e) {
|
||||
_this.update(e || window.event);
|
||||
});
|
||||
SVG.on(window, 'touchend.resize', function() {
|
||||
_this.done();
|
||||
});
|
||||
// Mouse.
|
||||
SVG.on(window, 'mousemove.resize', function (e) {
|
||||
_this.update(e || window.event);
|
||||
});
|
||||
SVG.on(window, 'mouseup.resize', function () {
|
||||
_this.done();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// The update-function redraws the element every time the mouse is moving
|
||||
ResizeHandler.prototype.update = function (event) {
|
||||
|
||||
if (!event) {
|
||||
if (this.lastUpdateCall) {
|
||||
this.calc(this.lastUpdateCall[0], this.lastUpdateCall[1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the difference between the mouseposition at start and now
|
||||
var txPt = this._extractPosition(event);
|
||||
var p = this.transformPoint(txPt.x, txPt.y);
|
||||
|
||||
var diffX = p.x - this.parameters.p.x,
|
||||
diffY = p.y - this.parameters.p.y;
|
||||
|
||||
this.lastUpdateCall = [diffX, diffY];
|
||||
|
||||
// Calculate the new position and height / width of the element
|
||||
this.calc(diffX, diffY);
|
||||
|
||||
// Emit an event to say we have changed.
|
||||
this.el.fire('resizing', {dx: diffX, dy: diffY, event: event});
|
||||
};
|
||||
|
||||
// Is called on mouseup.
|
||||
// Removes the update-function from the mousemove event
|
||||
ResizeHandler.prototype.done = function () {
|
||||
this.lastUpdateCall = null;
|
||||
SVG.off(window, 'mousemove.resize');
|
||||
SVG.off(window, 'mouseup.resize');
|
||||
SVG.off(window, 'touchmove.resize');
|
||||
SVG.off(window, 'touchend.resize');
|
||||
this.el.fire('resizedone');
|
||||
};
|
||||
|
||||
// The flag is used to determine whether the resizing is used with a left-Point (first bit) and top-point (second bit)
|
||||
// In this cases the temp-values are calculated differently
|
||||
ResizeHandler.prototype.snapToGrid = function (diffX, diffY, flag, pointCoordsY) {
|
||||
|
||||
var temp;
|
||||
|
||||
// If `pointCoordsY` is given, a single Point has to be snapped (deepSelect). That's why we need a different temp-value
|
||||
if (typeof pointCoordsY !== 'undefined') {
|
||||
// Note that flag = pointCoordsX in this case
|
||||
temp = [(flag + diffX) % this.options.snapToGrid, (pointCoordsY + diffY) % this.options.snapToGrid];
|
||||
} else {
|
||||
// We check if the flag is set and if not we set a default-value (both bits set - which means upper-left-edge)
|
||||
flag = flag == null ? 1 | 1 << 1 : flag;
|
||||
temp = [(this.parameters.box.x + diffX + (flag & 1 ? 0 : this.parameters.box.width)) % this.options.snapToGrid, (this.parameters.box.y + diffY + (flag & (1 << 1) ? 0 : this.parameters.box.height)) % this.options.snapToGrid];
|
||||
}
|
||||
|
||||
|
||||
diffX -= (Math.abs(temp[0]) < this.options.snapToGrid / 2 ?
|
||||
temp[0] :
|
||||
temp[0] - (diffX < 0 ? -this.options.snapToGrid : this.options.snapToGrid));
|
||||
diffY -= (Math.abs(temp[1]) < this.options.snapToGrid / 2 ?
|
||||
temp[1] :
|
||||
temp[1] - (diffY < 0 ? -this.options.snapToGrid : this.options.snapToGrid));
|
||||
|
||||
return this.constraintToBox(diffX, diffY, flag, pointCoordsY);
|
||||
|
||||
};
|
||||
|
||||
// keep element within constrained box
|
||||
ResizeHandler.prototype.constraintToBox = function (diffX, diffY, flag, pointCoordsY) {
|
||||
//return [diffX, diffY]
|
||||
var c = this.options.constraint || {};
|
||||
var orgX, orgY;
|
||||
|
||||
if (typeof pointCoordsY !== 'undefined') {
|
||||
orgX = flag;
|
||||
orgY = pointCoordsY;
|
||||
} else {
|
||||
orgX = this.parameters.box.x + (flag & 1 ? 0 : this.parameters.box.width);
|
||||
orgY = this.parameters.box.y + (flag & (1<<1) ? 0 : this.parameters.box.height);
|
||||
}
|
||||
|
||||
if (typeof c.minX !== 'undefined' && orgX + diffX < c.minX) {
|
||||
diffX = c.minX - orgX;
|
||||
}
|
||||
|
||||
if (typeof c.maxX !== 'undefined' && orgX + diffX > c.maxX) {
|
||||
diffX = c.maxX - orgX;
|
||||
}
|
||||
|
||||
if (typeof c.minY !== 'undefined' && orgY + diffY < c.minY) {
|
||||
diffY = c.minY - orgY;
|
||||
}
|
||||
|
||||
if (typeof c.maxY !== 'undefined' && orgY + diffY > c.maxY) {
|
||||
diffY = c.maxY - orgY;
|
||||
}
|
||||
|
||||
return [diffX, diffY];
|
||||
};
|
||||
|
||||
SVG.extend(SVG.Element, {
|
||||
// Resize element with mouse
|
||||
resize: function (options) {
|
||||
|
||||
(this.remember('_resizeHandler') || new ResizeHandler(this)).init(options || {});
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
SVG.Element.prototype.resize.defaults = {
|
||||
snapToAngle: 0.1, // Specifies the speed the rotation is happening when moving the mouse
|
||||
snapToGrid: 1, // Snaps to a grid of `snapToGrid` Pixels
|
||||
constraint: {} // keep element within constrained box
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
}());
|
||||
@ -0,0 +1,417 @@
|
||||
/*!
|
||||
* svg.select.js - An extension of svg.js which allows to select elements with mouse
|
||||
* @version 3.0.0
|
||||
* https://github.com/svgdotjs/svg.select.js
|
||||
*
|
||||
* @copyright Ulrich-Matthias Schäfer
|
||||
* @license MIT
|
||||
*/;
|
||||
;(function() {
|
||||
"use strict";
|
||||
|
||||
function SelectHandler(el) {
|
||||
|
||||
this.el = el;
|
||||
el.remember('_selectHandler', this);
|
||||
this.pointSelection = {isSelected: false};
|
||||
this.rectSelection = {isSelected: false};
|
||||
|
||||
// helper list with position settings of each type of point
|
||||
this.pointsList = {
|
||||
lt: [ 0, 0 ],
|
||||
rt: [ 'width', 0 ],
|
||||
rb: [ 'width', 'height' ],
|
||||
lb: [ 0, 'height' ],
|
||||
t: [ 'width / 2', 0 ],
|
||||
r: [ 'width', 'height / 2' ],
|
||||
b: [ 'width / 2', 'height' ],
|
||||
l: [ 0, 'height / 2' ]
|
||||
};
|
||||
|
||||
// helper function to evaluate point coordinates based on settings above and an object (bbox in our case)
|
||||
this.pointCoord = function (setting, object) {
|
||||
return typeof setting !== 'string' ? setting : eval('object.' + setting)
|
||||
}
|
||||
|
||||
this.pointCoords = function (point, object) {
|
||||
var settings = this.pointsList[point];
|
||||
|
||||
return {
|
||||
x: this.pointCoord(settings[0], object),
|
||||
y: this.pointCoord(settings[1], object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SelectHandler.prototype.init = function (value, options) {
|
||||
|
||||
var bbox = this.el.bbox();
|
||||
this.options = {};
|
||||
|
||||
// store defaults list of points in order to verify users config
|
||||
var points = this.el.selectize.defaults.points;
|
||||
|
||||
// Merging the defaults and the options-object together
|
||||
for (var i in this.el.selectize.defaults) {
|
||||
this.options[i] = this.el.selectize.defaults[i];
|
||||
if (options[i] !== undefined) {
|
||||
this.options[i] = options[i];
|
||||
}
|
||||
}
|
||||
|
||||
// prepare & validate list of points to be added (or excluded)
|
||||
var pointsLists = ['points', 'pointsExclude'];
|
||||
|
||||
for (var i in pointsLists) {
|
||||
var option = this.options[pointsLists[i]];
|
||||
|
||||
if (typeof option === 'string') {
|
||||
if (option.length > 0) {
|
||||
// if set as comma separated string list => convert it into an array
|
||||
option = option.split(/\s*,\s*/i);
|
||||
} else {
|
||||
option = [];
|
||||
}
|
||||
} else if (typeof option === 'boolean' && pointsLists[i] === 'points') {
|
||||
// this is not needed, but let's have it for legacy support
|
||||
option = option ? points : [];
|
||||
}
|
||||
|
||||
this.options[pointsLists[i]] = option;
|
||||
}
|
||||
|
||||
// intersect correct all points options with users config (exclude unwanted points)
|
||||
// ES5 -> NO arrow functions nor Array.includes()
|
||||
this.options.points = [ points, this.options.points ].reduce(
|
||||
function (a, b) {
|
||||
return a.filter(
|
||||
function (c) {
|
||||
return b.indexOf(c) > -1;
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
// exclude pointsExclude, if wanted
|
||||
this.options.points = [ this.options.points, this.options.pointsExclude ].reduce(
|
||||
function (a, b) {
|
||||
return a.filter(
|
||||
function (c) {
|
||||
return b.indexOf(c) < 0;
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
this.parent = this.el.parent();
|
||||
this.nested = (this.nested || this.parent.group());
|
||||
this.nested.matrix(new SVG.Matrix(this.el).translate(bbox.x, bbox.y));
|
||||
|
||||
// When deepSelect is enabled and the element is a line/polyline/polygon, draw only points for moving
|
||||
if (this.options.deepSelect && ['line', 'polyline', 'polygon'].indexOf(this.el.type) !== -1) {
|
||||
this.selectPoints(value);
|
||||
} else {
|
||||
this.selectRect(value);
|
||||
}
|
||||
|
||||
this.observe();
|
||||
this.cleanup();
|
||||
|
||||
};
|
||||
|
||||
SelectHandler.prototype.selectPoints = function (value) {
|
||||
|
||||
this.pointSelection.isSelected = value;
|
||||
|
||||
// When set is already there we dont have to create one
|
||||
if (this.pointSelection.set) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Create our set of elements
|
||||
this.pointSelection.set = this.parent.set();
|
||||
// draw the points and mark the element as selected
|
||||
this.drawPoints();
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
// create the point-array which contains the 2 points of a line or simply the points-array of polyline/polygon
|
||||
SelectHandler.prototype.getPointArray = function () {
|
||||
var bbox = this.el.bbox();
|
||||
|
||||
return this.el.array().valueOf().map(function (el) {
|
||||
return [el[0] - bbox.x, el[1] - bbox.y];
|
||||
});
|
||||
};
|
||||
|
||||
// Draws a points
|
||||
SelectHandler.prototype.drawPoints = function () {
|
||||
|
||||
var _this = this, array = this.getPointArray();
|
||||
|
||||
// go through the array of points
|
||||
for (var i = 0, len = array.length; i < len; ++i) {
|
||||
|
||||
var curriedEvent = (function (k) {
|
||||
return function (ev) {
|
||||
ev = ev || window.event;
|
||||
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
|
||||
ev.stopPropagation();
|
||||
|
||||
var x = ev.pageX || ev.touches[0].pageX;
|
||||
var y = ev.pageY || ev.touches[0].pageY;
|
||||
_this.el.fire('point', {x: x, y: y, i: k, event: ev});
|
||||
};
|
||||
})(i);
|
||||
|
||||
// add every point to the set
|
||||
// add css-classes and a touchstart-event which fires our event for moving points
|
||||
var point = this.drawPoint(array[i][0], array[i][1])
|
||||
.addClass(this.options.classPoints)
|
||||
.addClass(this.options.classPoints + '_point')
|
||||
.on('touchstart', curriedEvent)
|
||||
.on('mousedown', curriedEvent)
|
||||
this.pointSelection.set.add(point);
|
||||
}
|
||||
};
|
||||
|
||||
// The function to draw single point
|
||||
SelectHandler.prototype.drawPoint = function (cx, cy) {
|
||||
var pointType = this.options.pointType;
|
||||
|
||||
switch (pointType) {
|
||||
case 'circle':
|
||||
return this.drawCircle(cx, cy);
|
||||
case 'rect':
|
||||
return this.drawRect(cx, cy);
|
||||
default:
|
||||
if (typeof pointType === 'function') {
|
||||
return pointType.call(this, cx, cy);
|
||||
}
|
||||
|
||||
throw new Error('Unknown ' + pointType + ' point type!');
|
||||
}
|
||||
};
|
||||
|
||||
// The function to draw the circle point
|
||||
SelectHandler.prototype.drawCircle = function (cx, cy) {
|
||||
return this.nested.circle(this.options.pointSize)
|
||||
.center(cx, cy);
|
||||
};
|
||||
|
||||
// The function to draw the rect point
|
||||
SelectHandler.prototype.drawRect = function (cx, cy) {
|
||||
return this.nested.rect(this.options.pointSize, this.options.pointSize)
|
||||
.center(cx, cy);
|
||||
};
|
||||
|
||||
// every time a point is moved, we have to update the positions of our point
|
||||
SelectHandler.prototype.updatePointSelection = function () {
|
||||
var array = this.getPointArray();
|
||||
|
||||
this.pointSelection.set.each(function (i) {
|
||||
if (this.cx() === array[i][0] && this.cy() === array[i][1]) {
|
||||
return;
|
||||
}
|
||||
this.center(array[i][0], array[i][1]);
|
||||
});
|
||||
};
|
||||
|
||||
SelectHandler.prototype.updateRectSelection = function () {
|
||||
var _this = this, bbox = this.el.bbox();
|
||||
|
||||
this.rectSelection.set.get(0).attr({
|
||||
width: bbox.width,
|
||||
height: bbox.height
|
||||
});
|
||||
|
||||
// set.get(1) is always in the upper left corner. no need to move it
|
||||
if (this.options.points.length) {
|
||||
this.options.points.map(function (point, index) {
|
||||
var coords = _this.pointCoords(point, bbox);
|
||||
|
||||
_this.rectSelection.set.get(index + 1).center(coords.x, coords.y);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.options.rotationPoint) {
|
||||
var length = this.rectSelection.set.length();
|
||||
|
||||
this.rectSelection.set.get(length - 1).center(bbox.width / 2, 20);
|
||||
}
|
||||
};
|
||||
|
||||
SelectHandler.prototype.selectRect = function (value) {
|
||||
|
||||
var _this = this, bbox = this.el.bbox();
|
||||
|
||||
this.rectSelection.isSelected = value;
|
||||
|
||||
// when set is already p
|
||||
this.rectSelection.set = this.rectSelection.set || this.parent.set();
|
||||
|
||||
// helperFunction to create a mouse-down function which triggers the event specified in `eventName`
|
||||
function getMoseDownFunc(eventName) {
|
||||
return function (ev) {
|
||||
ev = ev || window.event;
|
||||
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
|
||||
ev.stopPropagation();
|
||||
|
||||
var x = ev.pageX || ev.touches[0].pageX;
|
||||
var y = ev.pageY || ev.touches[0].pageY;
|
||||
_this.el.fire(eventName, {x: x, y: y, event: ev});
|
||||
};
|
||||
}
|
||||
|
||||
// create the selection-rectangle and add the css-class
|
||||
if (!this.rectSelection.set.get(0)) {
|
||||
this.rectSelection.set.add(this.nested.rect(bbox.width, bbox.height).addClass(this.options.classRect));
|
||||
}
|
||||
|
||||
// Draw Points at the edges, if enabled
|
||||
if (this.options.points.length && this.rectSelection.set.length() < 2) {
|
||||
var ename ="touchstart", mname = "mousedown";
|
||||
|
||||
this.options.points.map(function (point, index) {
|
||||
var coords = _this.pointCoords(point, bbox);
|
||||
|
||||
var pointElement = _this.drawPoint(coords.x, coords.y)
|
||||
.attr('class', _this.options.classPoints + '_' + point)
|
||||
.on(mname, getMoseDownFunc(point))
|
||||
.on(ename, getMoseDownFunc(point));
|
||||
_this.rectSelection.set.add(pointElement);
|
||||
});
|
||||
|
||||
this.rectSelection.set.each(function () {
|
||||
this.addClass(_this.options.classPoints);
|
||||
});
|
||||
}
|
||||
|
||||
// draw rotationPint, if enabled
|
||||
if (this.options.rotationPoint && ((this.options.points && !this.rectSelection.set.get(9)) || (!this.options.points && !this.rectSelection.set.get(1)))) {
|
||||
|
||||
var curriedEvent = function (ev) {
|
||||
ev = ev || window.event;
|
||||
ev.preventDefault ? ev.preventDefault() : ev.returnValue = false;
|
||||
ev.stopPropagation();
|
||||
|
||||
var x = ev.pageX || ev.touches[0].pageX;
|
||||
var y = ev.pageY || ev.touches[0].pageY;
|
||||
_this.el.fire('rot', {x: x, y: y, event: ev});
|
||||
};
|
||||
|
||||
var pointElement = this.drawPoint(bbox.width / 2, 20)
|
||||
.attr('class', this.options.classPoints + '_rot')
|
||||
.on("touchstart", curriedEvent)
|
||||
.on("mousedown", curriedEvent);
|
||||
this.rectSelection.set.add(pointElement);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
SelectHandler.prototype.handler = function () {
|
||||
|
||||
var bbox = this.el.bbox();
|
||||
this.nested.matrix(new SVG.Matrix(this.el).translate(bbox.x, bbox.y));
|
||||
|
||||
if (this.rectSelection.isSelected) {
|
||||
this.updateRectSelection();
|
||||
}
|
||||
|
||||
if (this.pointSelection.isSelected) {
|
||||
this.updatePointSelection();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
SelectHandler.prototype.observe = function () {
|
||||
var _this = this;
|
||||
|
||||
if (MutationObserver) {
|
||||
if (this.rectSelection.isSelected || this.pointSelection.isSelected) {
|
||||
this.observerInst = this.observerInst || new MutationObserver(function () {
|
||||
_this.handler();
|
||||
});
|
||||
this.observerInst.observe(this.el.node, {attributes: true});
|
||||
} else {
|
||||
try {
|
||||
this.observerInst.disconnect();
|
||||
delete this.observerInst;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.el.off('DOMAttrModified.select');
|
||||
|
||||
if (this.rectSelection.isSelected || this.pointSelection.isSelected) {
|
||||
this.el.on('DOMAttrModified.select', function () {
|
||||
_this.handler();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SelectHandler.prototype.cleanup = function () {
|
||||
|
||||
//var _this = this;
|
||||
|
||||
if (!this.rectSelection.isSelected && this.rectSelection.set) {
|
||||
// stop watching the element, remove the selection
|
||||
this.rectSelection.set.each(function () {
|
||||
this.remove();
|
||||
});
|
||||
|
||||
this.rectSelection.set.clear();
|
||||
delete this.rectSelection.set;
|
||||
}
|
||||
|
||||
if (!this.pointSelection.isSelected && this.pointSelection.set) {
|
||||
// Remove all points, clear the set, stop watching the element
|
||||
this.pointSelection.set.each(function () {
|
||||
this.remove();
|
||||
});
|
||||
|
||||
this.pointSelection.set.clear();
|
||||
delete this.pointSelection.set;
|
||||
}
|
||||
|
||||
if (!this.pointSelection.isSelected && !this.rectSelection.isSelected) {
|
||||
this.nested.remove();
|
||||
delete this.nested;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
SVG.extend(SVG.Element, {
|
||||
// Select element with mouse
|
||||
selectize: function (value, options) {
|
||||
|
||||
// Check the parameters and reassign if needed
|
||||
if (typeof value === 'object') {
|
||||
options = value;
|
||||
value = true;
|
||||
}
|
||||
|
||||
var selectHandler = this.remember('_selectHandler') || new SelectHandler(this);
|
||||
|
||||
selectHandler.init(value === undefined ? true : value, options || {});
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
SVG.Element.prototype.selectize.defaults = {
|
||||
points: ['lt', 'rt', 'rb', 'lb', 't', 'r', 'b', 'l'], // which points to draw, default all
|
||||
pointsExclude: [], // easier option if to exclude few than rewrite all
|
||||
classRect: 'svg_select_boundingRect', // Css-class added to the rect
|
||||
classPoints: 'svg_select_points', // Css-class added to the points
|
||||
pointSize: 7, // size of point
|
||||
rotationPoint: true, // If true, rotation point is drawn. Needed for rotation!
|
||||
deepSelect: false, // If true, moving of single points is possible (only line, polyline, polyon)
|
||||
pointType: 'circle' // Point type: circle or rect, default circle
|
||||
};
|
||||
}());
|
||||
@ -1,22 +0,0 @@
|
||||
/* exported setupAPI */
|
||||
|
||||
function setupAPI(collectionModel, job) {
|
||||
window.cvat = {};
|
||||
let space = window.cvat;
|
||||
|
||||
space.tracks = {
|
||||
get: collectionModel.exportTracks.bind(collectionModel),
|
||||
set: (data) => {
|
||||
collectionModel.importTracks(data);
|
||||
collectionModel.update();
|
||||
},
|
||||
clear: collectionModel.removeTracks.bind(collectionModel),
|
||||
};
|
||||
|
||||
space.job = {
|
||||
id: job.jobid,
|
||||
mode: job.mode,
|
||||
start: job.start,
|
||||
stop: job.stop
|
||||
};
|
||||
}
|
||||
@ -1,507 +1,393 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/* exported AAMModel AAMController AAMView */
|
||||
"use strict";
|
||||
|
||||
const AAMUndefinedKeyword = '__undefined__';
|
||||
|
||||
/* AAM is Attribute Annotation Mode with using keyboard only */
|
||||
class AAMModel extends Listener {
|
||||
constructor(labelsInfo, setActiveTrack, resetActiveTrack, focus) {
|
||||
super('onAAMUpdate', getState);
|
||||
this._activeAAM = false;
|
||||
this._labelsInfo = labelsInfo;
|
||||
this._currentTracks = [];
|
||||
this._activeTrack = null;
|
||||
this._curFrame = null;
|
||||
this._setActiveTrackCallback = setActiveTrack;
|
||||
this._resetActiveTrackCallback = resetActiveTrack;
|
||||
class AAMModel extends Listener {
|
||||
constructor(shapeCollection, focus) {
|
||||
super('onAAMUpdate', () => this);
|
||||
this._shapeCollection = shapeCollection;
|
||||
this._focus = focus;
|
||||
this._zoomBoxes = true;
|
||||
this._zoomMargin = 100;
|
||||
this._hideNonActive = true;
|
||||
|
||||
this._attributeByLabel = new Object();
|
||||
this._titles = new Object();
|
||||
this._helps = new Object();
|
||||
|
||||
let labels = labelsInfo.labels();
|
||||
for (let labelId in labels) {
|
||||
let attributes = labelsInfo.labelAttributes(labelId);
|
||||
|
||||
this._attributeByLabel[labelId] = Object.keys(attributes).length ? 0 : null;
|
||||
for (let attrId in attributes) {
|
||||
this._titles[attrId] = labels[labelId].normalize() + ' : ' + attributes[attrId].normalize();
|
||||
this._helps[attrId] = [];
|
||||
|
||||
let attrInfo = labelsInfo.attrInfo(attrId);
|
||||
switch (attrInfo.type) {
|
||||
case 'text':
|
||||
case 'number':
|
||||
continue;
|
||||
case 'checkbox':
|
||||
this._helps[attrId].push('0 - ' + attrInfo.values[0]);
|
||||
this._helps[attrId].push('1 - ' + !attrInfo.values[0]);
|
||||
break;
|
||||
default:
|
||||
for (let idx = 0; idx < attrInfo.values.length; idx ++) {
|
||||
if (idx > 9) break;
|
||||
if (attrInfo.values[0] === AAMUndefinedKeyword) {
|
||||
if (!idx) continue;
|
||||
this._helps[attrId].push(idx - 1 + ' - ' + attrInfo.values[idx]);
|
||||
}
|
||||
else {
|
||||
this._helps[attrId].push(idx + ' - ' + attrInfo.values[idx]);
|
||||
}
|
||||
}
|
||||
} // switch
|
||||
} // attribute for
|
||||
} // label for
|
||||
|
||||
let self = this;
|
||||
function getState() {
|
||||
return self;
|
||||
this._activeAAM = false;
|
||||
this._activeIdx = null;
|
||||
this._active = null;
|
||||
this._margin = 100;
|
||||
this._currentShapes = [];
|
||||
this._attrNumberByLabel = {};
|
||||
this._helps = {};
|
||||
for (let labelId in window.cvat.labelsInfo.labels()) {
|
||||
let labelAttributes = window.cvat.labelsInfo.labelAttributes(labelId);
|
||||
if (Object.keys(labelAttributes).length) {
|
||||
this._attrNumberByLabel[labelId] = {
|
||||
current: 0,
|
||||
end: Object.keys(labelAttributes).length
|
||||
};
|
||||
|
||||
for (let attrId in labelAttributes) {
|
||||
this._helps[attrId] = {
|
||||
title: `${window.cvat.labelsInfo.labels()[labelId]}, ${window.cvat.labelsInfo.attributes()[attrId]}`,
|
||||
help: getHelp(attrId),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} // constructor
|
||||
|
||||
function getHelp(attrId) {
|
||||
let attrInfo = window.cvat.labelsInfo.attrInfo(attrId);
|
||||
let help = [];
|
||||
switch (attrInfo.type) {
|
||||
case 'checkbox':
|
||||
help.push('0 - ' + attrInfo.values[0]);
|
||||
help.push('1 - ' + !attrInfo.values[0]);
|
||||
break;
|
||||
default:
|
||||
for (let idx = 0; idx < attrInfo.values.length; idx ++) {
|
||||
if (idx > 9) break;
|
||||
if (attrInfo.values[0] === AAMUndefinedKeyword) {
|
||||
if (!idx) continue;
|
||||
help.push(idx - 1 + ' - ' + attrInfo.values[idx]);
|
||||
}
|
||||
else {
|
||||
help.push(idx + ' - ' + attrInfo.values[idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_labelId() {
|
||||
if (this._activeTrack != null) {
|
||||
return this._currentTracks[this._activeTrack].label;
|
||||
return help;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
_attributeId() {
|
||||
let labelId = this._labelId();
|
||||
let attrKeys = Object.keys(this._labelsInfo.labelAttributes(labelId));
|
||||
let attrOrderIdx = this._attributeByLabel[labelId];
|
||||
if (attrOrderIdx != null) {
|
||||
return +attrKeys[attrOrderIdx];
|
||||
}
|
||||
shapeCollection.subscribe(this);
|
||||
}
|
||||
|
||||
|
||||
_updateActiveTrack(active) {
|
||||
if (this._activeTrack === null) return;
|
||||
let track = this._currentTracks[this._activeTrack];
|
||||
track.activeAAMTrack = active;
|
||||
track.hidden = !active && this._hideNonActive;
|
||||
if (active) {
|
||||
if (this._curFrame != null && this._zoomBoxes) {
|
||||
let pos = track.interpolate(this._curFrame).position;
|
||||
this._focus(pos.xtl - this._zoomMargin, pos.xbr + this._zoomMargin, pos.ytl - this._zoomMargin, pos.ybr + this._zoomMargin);
|
||||
_bbRect(pos) {
|
||||
if ('points' in pos) {
|
||||
let points = PolyShapeModel.convertStringToNumberArray(pos.points);
|
||||
let xtl = Number.MAX_SAFE_INTEGER;
|
||||
let ytl = Number.MAX_SAFE_INTEGER;
|
||||
let xbr = Number.MIN_SAFE_INTEGER;
|
||||
let ybr = Number.MIN_SAFE_INTEGER;
|
||||
for (let point of points) {
|
||||
xtl = Math.min(xtl, point.x);
|
||||
ytl = Math.min(ytl, point.y);
|
||||
xbr = Math.max(xbr, point.x);
|
||||
ybr = Math.max(ybr, point.y);
|
||||
}
|
||||
this._setActiveTrackCallback(track.id); // via track collection for occluded property
|
||||
return [xtl, ytl, xbr, ybr];
|
||||
}
|
||||
else {
|
||||
this._resetActiveTrackCallback();
|
||||
return [pos.xtl, pos.ytl, pos.xbr, pos.ybr];
|
||||
}
|
||||
}
|
||||
|
||||
_updateCollection() {
|
||||
this._currentShapes = [];
|
||||
|
||||
_updateActiveAttribute(active) {
|
||||
if (this._activeTrack === null) return;
|
||||
if (active) {
|
||||
let attrId = this._attributeId();
|
||||
if (Number.isInteger(+attrId)) {
|
||||
this._currentTracks[this._activeTrack].activeAttribute = attrId;
|
||||
for (let shape of this._shapeCollection.currentShapes) {
|
||||
let labelAttributes = window.cvat.labelsInfo.labelAttributes(shape.model.label);
|
||||
if (Object.keys(labelAttributes).length && !shape.model.removed && !shape.interpolation.position.outside) {
|
||||
this._currentShapes.push(shape);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._currentShapes.length) {
|
||||
this._activeIdx = 0;
|
||||
this._active = this._currentShapes[0].model;
|
||||
}
|
||||
else {
|
||||
this._currentTracks[this._activeTrack].activeAttribute = null;
|
||||
this._activeIdx = null;
|
||||
this._active = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_switchHidden(value) {
|
||||
for (let idx = 0; idx < this._currentTracks.length; idx++) {
|
||||
this._currentTracks[idx].hidden = this._hideNonActive && value && (idx != this._activeTrack);
|
||||
}
|
||||
_attrIdByIdx(labelId, attrIdx) {
|
||||
return Object.keys(window.cvat.labelsInfo.labelAttributes(labelId))[attrIdx];
|
||||
}
|
||||
|
||||
_activate() {
|
||||
if (this._activeAAM && this._active) {
|
||||
let label = this._active.label;
|
||||
let attrId = +this._attrIdByIdx(label, this._attrNumberByLabel[label].current);
|
||||
let attrInfo = window.cvat.labelsInfo.attrInfo(attrId);
|
||||
|
||||
switchAAM() {
|
||||
if (this._activeAAM) {
|
||||
this.disableAAM();
|
||||
let [xtl, ytl, xbr, ybr] = this._bbRect(this._currentShapes[this._activeIdx].interpolation.position);
|
||||
this._focus(xtl - this._margin, xbr + this._margin, ytl - this._margin, ybr + this._margin);
|
||||
|
||||
this._active.activeAAM = {
|
||||
shape: true,
|
||||
attribute: attrId,
|
||||
};
|
||||
|
||||
this.notify();
|
||||
|
||||
if (attrInfo.type === 'text' || attrInfo.type === 'number') {
|
||||
this._active.aamAttributeFocus();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.enableAAM();
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
_deactivate() {
|
||||
if (this._activeAAM && this._active) {
|
||||
this._active.activeAAM = {
|
||||
shape: false,
|
||||
attribute: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enableAAM() {
|
||||
this._activeAAM = true;
|
||||
this.notify();
|
||||
this._switchHidden(true);
|
||||
this._updateActiveAttribute(true);
|
||||
this._updateActiveTrack(true);
|
||||
_enable() {
|
||||
if (window.cvat.mode === null) {
|
||||
window.cvat.mode = 'aam';
|
||||
this._shapeCollection.resetActive();
|
||||
this._activeAAM = true;
|
||||
this._updateCollection();
|
||||
this.notify();
|
||||
this._activate();
|
||||
}
|
||||
}
|
||||
|
||||
_disable() {
|
||||
if (this._activeAAM && window.cvat.mode === 'aam') {
|
||||
this._deactivate();
|
||||
window.cvat.mode = null;
|
||||
this._activeAAM = false;
|
||||
this._activeIdx = null;
|
||||
this._active = null;
|
||||
|
||||
disableAAM() {
|
||||
this._activeAAM = false;
|
||||
this._updateActiveAttribute(false);
|
||||
this._updateActiveTrack(false);
|
||||
this._switchHidden(false);
|
||||
this.notify();
|
||||
// Notify for remove aam UI
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
nextTrack(direction) {
|
||||
if (!this._activeAAM || !this._currentTracks.length) return;
|
||||
this._updateActiveAttribute(false);
|
||||
let newActiveTrack = this._activeTrack + 1 * direction;
|
||||
if (newActiveTrack < 0) {
|
||||
newActiveTrack = this._currentTracks.length - 1;
|
||||
switchAAMMode() {
|
||||
if (this._activeAAM) {
|
||||
this._disable();
|
||||
}
|
||||
else if (newActiveTrack >= this._currentTracks.length) {
|
||||
newActiveTrack = 0;
|
||||
else {
|
||||
this._enable();
|
||||
}
|
||||
this._updateActiveTrack(false);
|
||||
this._activeTrack = newActiveTrack;
|
||||
this._updateActiveTrack(true);
|
||||
this.nextAttribute(0);
|
||||
}
|
||||
|
||||
moveShape(direction) {
|
||||
if (!this._activeAAM || this._currentShapes.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextAttribute(direction) {
|
||||
if (!this._activeAAM || !this._currentTracks.length) return;
|
||||
let labelId = this._labelId();
|
||||
let attributes = this._labelsInfo.labelAttributes(labelId);
|
||||
let numOfAttributes = Object.keys(attributes).length;
|
||||
let currentAttr = this._attributeByLabel[labelId];
|
||||
if (numOfAttributes >= 2) {
|
||||
currentAttr += 1 * direction;
|
||||
if (currentAttr < 0) {
|
||||
this._attributeByLabel[labelId] = numOfAttributes - 1;
|
||||
}
|
||||
else if (currentAttr >= numOfAttributes) {
|
||||
this._attributeByLabel[labelId] = 0;
|
||||
this._deactivate();
|
||||
if (Math.sign(direction) > 0) {
|
||||
// next
|
||||
this._activeIdx ++;
|
||||
if (this._activeIdx >= this._currentShapes.length) {
|
||||
this._activeIdx = 0;
|
||||
}
|
||||
else {
|
||||
this._attributeByLabel[labelId] = currentAttr;
|
||||
}
|
||||
else {
|
||||
// prev
|
||||
this._activeIdx --;
|
||||
if (this._activeIdx < 0) {
|
||||
this._activeIdx = this._currentShapes.length - 1;
|
||||
}
|
||||
}
|
||||
this._updateActiveAttribute(true);
|
||||
this.notify(); // notify for help update in view
|
||||
}
|
||||
|
||||
this._active = this._currentShapes[this._activeIdx].model;
|
||||
this._activate();
|
||||
}
|
||||
|
||||
setupAttributeValue(attrValueIndex) {
|
||||
if (!this._activeAAM || this._activeTrack === null) return;
|
||||
let labelId = this._labelId();
|
||||
let attrOrderIdx = this._attributeByLabel[labelId];
|
||||
if (attrOrderIdx === null) return;
|
||||
let attrId = this._attributeId();
|
||||
let attrInfo = this._labelsInfo.attrInfo(attrId);
|
||||
let values = attrInfo.values;
|
||||
if (attrInfo.type === 'text' || attrInfo.type === 'number') return;
|
||||
if (attrValueIndex >= values.length) {
|
||||
if (attrInfo.type === 'checkbox' && attrValueIndex < 2) {
|
||||
values.push(!values[0]);
|
||||
}
|
||||
else return;
|
||||
}
|
||||
if (values[0] === AAMUndefinedKeyword) {
|
||||
if (attrValueIndex >= values.length - 1) return;
|
||||
attrValueIndex ++;
|
||||
moveAttr(direction) {
|
||||
if (!this._activeAAM || !this._active) {
|
||||
return;
|
||||
}
|
||||
this._currentTracks[this._activeTrack].recordAttribute(attrId, values[attrValueIndex]);
|
||||
}
|
||||
|
||||
let curAttr = this._attrNumberByLabel[this._active.label];
|
||||
|
||||
onCollectionUpdate(collection) {
|
||||
if (this._activeAAM) {
|
||||
this._updateActiveTrack(false);
|
||||
this._updateActiveAttribute(false);
|
||||
this._switchHidden(false);
|
||||
if (curAttr.end < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currentTracks = [];
|
||||
this._curFrame = collection._curFrame;
|
||||
for (let track of collection.currentTracks) {
|
||||
if (!track.trackModel.removed) {
|
||||
this._currentTracks.push(track.trackModel);
|
||||
if (Math.sign(direction) > 0) {
|
||||
// next
|
||||
curAttr.current ++;
|
||||
if (curAttr.current >= curAttr.end) {
|
||||
curAttr.current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (let track of this._currentTracks) {
|
||||
track.subscribe(this);
|
||||
else {
|
||||
// prev
|
||||
curAttr.current --;
|
||||
if (curAttr.current < 0) {
|
||||
curAttr.current = curAttr.end - 1;
|
||||
}
|
||||
}
|
||||
this._activate();
|
||||
}
|
||||
|
||||
if (this._currentTracks.length) this._activeTrack = 0;
|
||||
else this._activeTrack = null;
|
||||
|
||||
if (this._activeAAM) {
|
||||
this._switchHidden(true);
|
||||
this._updateActiveTrack(true);
|
||||
this._updateActiveAttribute(true);
|
||||
setupAttributeValue(key) {
|
||||
if (!this._activeAAM || !this._active) {
|
||||
return;
|
||||
}
|
||||
this.notify();
|
||||
}
|
||||
|
||||
let label = this._active.label;
|
||||
let frame = window.cvat.player.frames.current;
|
||||
let attrId = this._attrIdByIdx(label, this._attrNumberByLabel[label].current);
|
||||
let attrInfo = window.cvat.labelsInfo.attrInfo(attrId);
|
||||
|
||||
onTrackUpdate(track) {
|
||||
if (track.model.removed) {
|
||||
let idx = this._currentTracks.indexOf(track.model);
|
||||
if (idx != -1) {
|
||||
this._currentTracks.splice(idx, 1);
|
||||
if (this._currentTracks.length) {
|
||||
if (this._activeTrack != null) {
|
||||
if (idx <= this._activeTrack && this._activeTrack > 0) this._activeTrack --;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._activeTrack = null;
|
||||
}
|
||||
if (key >= attrInfo.values.length) {
|
||||
if (attrInfo.type === 'checkbox' && key < 2) {
|
||||
this._active.updateAttribute(frame, attrId, !attrInfo.values[0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
set zoomBoxes(value) {
|
||||
if (value != true && value != false) {
|
||||
throw new Error(`Input value must be boolean, but ${value} found.`);
|
||||
if (attrInfo.values[0] === AAMUndefinedKeyword) {
|
||||
if (key >= attrInfo.values.length - 1) {
|
||||
return;
|
||||
}
|
||||
key ++;
|
||||
}
|
||||
this._zoomBoxes = value;
|
||||
}
|
||||
|
||||
this._active.updateAttribute(frame, attrId, attrInfo.values[key]);
|
||||
}
|
||||
|
||||
set hideNonActive(value) {
|
||||
if (value != true && value != false) {
|
||||
throw new Error(`Input value must be boolean, but ${value} found.`);
|
||||
}
|
||||
this._hideNonActive = value;
|
||||
onCollectionUpdate() {
|
||||
if (this._activeAAM) {
|
||||
this._switchHidden(value);
|
||||
// No need deactivate active view because all listeners already unsubscribed
|
||||
this._updateCollection();
|
||||
this._activate();
|
||||
}
|
||||
}
|
||||
|
||||
set zoomMargin(value) {
|
||||
value = Math.clamp(value, 0, 500);
|
||||
this._zoomMargin = value;
|
||||
}
|
||||
|
||||
get zoomMargin() {
|
||||
return this._zoomMargin;
|
||||
generateHelps() {
|
||||
if (this._active) {
|
||||
let label = this._active.label;
|
||||
let attrId = +this._attrIdByIdx(label, this._attrNumberByLabel[label].current);
|
||||
return [this._helps[attrId].title, this._helps[attrId].help, `${this._activeIdx + 1}/${this._currentShapes.length}`];
|
||||
}
|
||||
else {
|
||||
return ['No Shapes Found', '', '0/0'];
|
||||
}
|
||||
}
|
||||
|
||||
get activeAAM() {
|
||||
return this._activeAAM;
|
||||
}
|
||||
|
||||
|
||||
get helps() {
|
||||
let title = '';
|
||||
let helps = [];
|
||||
let counter = '0/0';
|
||||
let label = this._labelId();
|
||||
if (label != null) {
|
||||
counter = (this._activeTrack + 1) + '/' + this._currentTracks.length;
|
||||
let attrOrderIdx = this._attributeByLabel[label];
|
||||
if (attrOrderIdx != null) {
|
||||
let attrId = this._attributeId();
|
||||
title = this._titles[attrId];
|
||||
helps = this._helps[attrId].slice();
|
||||
}
|
||||
}
|
||||
|
||||
return [title, helps, counter];
|
||||
}
|
||||
|
||||
|
||||
get activeAttribute() {
|
||||
let labelId = this._labelId();
|
||||
if (labelId === null) return [null];
|
||||
|
||||
let activeTrack = this._currentTracks[this._activeTrack];
|
||||
let activeTrackId = activeTrack.id;
|
||||
|
||||
let attrOrderIdx = this._attributeByLabel[labelId];
|
||||
if (attrOrderIdx === null) return [null];
|
||||
|
||||
let attrId = this._attributeId();
|
||||
let attrInfo = this._labelsInfo.attrInfo(attrId);
|
||||
|
||||
return [activeTrackId, attrInfo.type, attrInfo.name];
|
||||
}
|
||||
|
||||
|
||||
get zoomBoxes() {
|
||||
return this._zoomBoxes;
|
||||
}
|
||||
|
||||
get hideNonActive() {
|
||||
return this._hideNonActive;
|
||||
set margin(value) {
|
||||
this._margin = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class AAMController {
|
||||
constructor(model) {
|
||||
this._model = model;
|
||||
constructor(aamModel) {
|
||||
this._model = aamModel;
|
||||
|
||||
setupAAMShortkeys.call(this);
|
||||
|
||||
function setupAAMShortkeys() {
|
||||
let switchAAMHandler = Logger.shortkeyLogDecorator(function() {
|
||||
this._model.switchAAM();
|
||||
this._model.switchAAMMode();
|
||||
}.bind(this));
|
||||
|
||||
let nextAttributeHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
this._model.nextAttribute(1);
|
||||
this._model.moveAttr(1);
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
|
||||
let prevAttributeHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
this._model.nextAttribute(-1);
|
||||
this._model.moveAttr(-1);
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
|
||||
let nextTrackHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
this._model.nextTrack(1);
|
||||
let nextShapeHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
this._model.moveShape(1);
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
|
||||
let prevTrackHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
this._model.nextTrack(-1);
|
||||
let prevShapeHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
this._model.moveShape(-1);
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
|
||||
let selectAttributeHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
let key = e.keyCode;
|
||||
if (key >= 48 && key <= 57) key -= 48; // 0 and 9
|
||||
else if (key >= 96 && key <= 105) key -= 96; // num 0 and 9
|
||||
else return;
|
||||
if (key >= 48 && key <= 57) {
|
||||
key -= 48; // 0 and 9
|
||||
}
|
||||
else if (key >= 96 && key <= 105) {
|
||||
key -= 96; // num 0 and 9
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
this._model.setupAttributeValue(key);
|
||||
}.bind(this));
|
||||
|
||||
let shortkeys = userConfig.shortkeys;
|
||||
|
||||
let shortkeys = window.cvat.config.shortkeys;
|
||||
Mousetrap.bind(shortkeys["switch_aam_mode"].value, switchAAMHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["aam_next_attribute"].value, nextAttributeHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["aam_prev_attribute"].value, prevAttributeHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["aam_next_track"].value, nextTrackHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["aam_prev_track"].value, prevTrackHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["select_i_attribute"].value.split(','), selectAttributeHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["aam_next_shape"].value, nextShapeHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["aam_prev_shape"].value, prevShapeHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["select_i_attribute"].value, selectAttributeHandler, 'keydown');
|
||||
}
|
||||
}
|
||||
|
||||
zoomMargin(value) {
|
||||
this._model.zoomMargin = value;
|
||||
}
|
||||
|
||||
zoomBoxes(value) {
|
||||
this._model.zoomBoxes = value;
|
||||
}
|
||||
|
||||
hideNonActive(value) {
|
||||
this._model.hideNonActive = value;
|
||||
setMargin(value) {
|
||||
this._model.margin = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AAMView {
|
||||
constructor(model, controller) {
|
||||
this._controller = controller;
|
||||
this._trackManagementMenu = $('#trackManagement');
|
||||
constructor(aamModel, aamController) {
|
||||
this._trackManagement = $('#trackManagement');
|
||||
this._aamMenu = $('#aamMenu');
|
||||
this._aamTitle = $('#aamTitle');
|
||||
this._aamCounter = $('#aamCounter');
|
||||
this._aamHelpContainer = $('#aamHelpContainer');
|
||||
this._removeAnnotationButton = $('#removeAnnotationButton');
|
||||
this._zoomBoxesBox = $('#zoomAAMBoxesBox');
|
||||
this._hideNonActiveBox = $('#hideNonActiveBox');
|
||||
this._zoomMargin = $('#aamZoomMargin');
|
||||
this._controller = aamController;
|
||||
|
||||
this._zoomBoxesBox.prop('checked', model.zoomBoxes);
|
||||
this._zoomBoxesBox.on('change', function(e) {
|
||||
let value = e.target.checked;
|
||||
this._controller.zoomBoxes(value);
|
||||
}.bind(this));
|
||||
|
||||
this._zoomMargin.prop('checked', model.zoomMargin);
|
||||
this._zoomMargin.on('change', function(e) {
|
||||
this._zoomMargin.on('change', (e) => {
|
||||
let value = +e.target.value;
|
||||
this._controller.zoomMargin(value);
|
||||
}.bind(this));
|
||||
|
||||
this._hideNonActiveBox.prop('checked', model.hideNonActive);
|
||||
this._hideNonActiveBox.on('change', function(e) {
|
||||
let value = e.target.checked;
|
||||
this._controller.hideNonActive(value);
|
||||
}.bind(this));
|
||||
|
||||
model.subscribe(this);
|
||||
}
|
||||
|
||||
|
||||
_blurAll(){
|
||||
var tmp = document.createElement("input");
|
||||
document.body.appendChild(tmp);
|
||||
tmp.focus();
|
||||
document.body.removeChild(tmp);
|
||||
this._controller.setMargin(value);
|
||||
}).trigger('change');
|
||||
aamModel.subscribe(this);
|
||||
}
|
||||
|
||||
|
||||
onAAMUpdate(aam) {
|
||||
if (aam.activeAAM && this._aamMenu.hasClass('hidden')) {
|
||||
this._trackManagementMenu.addClass('hidden');
|
||||
this._aamMenu.removeClass('hidden');
|
||||
this._removeAnnotationButton.prop('disabled', true);
|
||||
}
|
||||
else if (!aam.activeAAM) {
|
||||
this._blurAll();
|
||||
window.getSelection().removeAllRanges();
|
||||
this._aamMenu.addClass('hidden');
|
||||
this._trackManagementMenu.removeClass('hidden');
|
||||
this._removeAnnotationButton.prop('disabled', false);
|
||||
return;
|
||||
}
|
||||
let [title, helps, counter] = aam.helps;
|
||||
this._aamTitle.text(title);
|
||||
this._aamCounter.text(counter);
|
||||
this._aamHelpContainer.empty();
|
||||
|
||||
let table = $('<table></table>').css({
|
||||
'width': '100%',
|
||||
'text-align': 'left'
|
||||
}).appendTo(this._aamHelpContainer);
|
||||
while (helps.length) {
|
||||
let row = $('<tr></tr>').appendTo(table);
|
||||
let first = helps.shift();
|
||||
let second = helps.shift();
|
||||
row.append(`<td>${first}</td>`);
|
||||
if (second) {
|
||||
row.append(`<td>${second}</td>`);
|
||||
if (aam.activeAAM) {
|
||||
if (this._aamMenu.hasClass('hidden')) {
|
||||
this._trackManagement.addClass('hidden');
|
||||
this._aamMenu.removeClass('hidden');
|
||||
}
|
||||
}
|
||||
for (let help of helps) {
|
||||
let helpView = $(`<label> ${help} </label>`).addClass('regular');
|
||||
helpView.appendTo(this._aamHelpContainer);
|
||||
$('<br>').appendTo(this._aamHelpContainer);
|
||||
}
|
||||
|
||||
let [activeTrackId, type, name] = aam.activeAttribute;
|
||||
if (activeTrackId === null) return;
|
||||
let [title, help, counter] = aam.generateHelps();
|
||||
this._aamHelpContainer.empty();
|
||||
this._aamCounter.text(counter);
|
||||
this._aamTitle.text(title);
|
||||
|
||||
switch (type) {
|
||||
case 'text': {
|
||||
let text = $(`#${activeTrackId}_${name}_text`)[0];
|
||||
text.focus();
|
||||
text.select();
|
||||
break;
|
||||
}
|
||||
case 'number': {
|
||||
let number = $(`#${activeTrackId}_${name}_number`)[0];
|
||||
number.focus();
|
||||
number.select();
|
||||
break;
|
||||
for (let helpRow of help) {
|
||||
$(`<label> ${helpRow} <label> <br>`).appendTo(this._aamHelpContainer);
|
||||
}
|
||||
}
|
||||
default:
|
||||
this._blurAll();
|
||||
window.getSelection().removeAllRanges();
|
||||
else {
|
||||
if (this._trackManagement.hasClass('hidden')) {
|
||||
this._aamMenu.addClass('hidden');
|
||||
this._trackManagement.removeClass('hidden');
|
||||
}
|
||||
}
|
||||
// blur on change text attribute to other or on exit from aam
|
||||
blurAllElements();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,213 +0,0 @@
|
||||
/* exported CollectionController */
|
||||
"use strict";
|
||||
|
||||
class CollectionController {
|
||||
constructor(collectionModel) {
|
||||
this._collectionModel = collectionModel;
|
||||
this._playerScale = 1;
|
||||
this._moveMode = false;
|
||||
this._resizeMode = false;
|
||||
this._drawMode = false;
|
||||
this._pasteMode = false;
|
||||
this._AAM = false;
|
||||
this._lastMousePos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
this._collectionHash = this._collectionModel.getHash();
|
||||
setupInteract.call(this);
|
||||
setupCollectionShortkeys.call(this);
|
||||
|
||||
|
||||
function setupCollectionShortkeys() {
|
||||
let deleteHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
if (this._AAM) return;
|
||||
this._collectionModel.removeactivetrack(e.shiftKey);
|
||||
}.bind(this));
|
||||
|
||||
let lockHandler = Logger.shortkeyLogDecorator(function() {
|
||||
if (this._AAM) return;
|
||||
this._collectionModel.switchLockForActive();
|
||||
}.bind(this));
|
||||
|
||||
let lockAllHandler = Logger.shortkeyLogDecorator(function() {
|
||||
if (this._AAM) return;
|
||||
this._collectionModel.switchLockForAll();
|
||||
}.bind(this));
|
||||
|
||||
let occludedHandler = Logger.shortkeyLogDecorator(function() {
|
||||
this._collectionModel.switchOccludedForActive();
|
||||
}.bind(this));
|
||||
|
||||
let changeColorHandler = Logger.shortkeyLogDecorator(function() {
|
||||
this._collectionModel.switchColorForActive();
|
||||
}.bind(this));
|
||||
|
||||
let changeLabelHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
let labelIdx = e.keyCode - '1'.charCodeAt(0);
|
||||
this._collectionModel.reinitializeActive(labelIdx);
|
||||
let fakeKeyState = {
|
||||
ctrl: false,
|
||||
shift: false,
|
||||
alt: false
|
||||
};
|
||||
if (!this._AAM) this._collectionModel.onmousemove(this._lastMousePos.x, this._lastMousePos.y, fakeKeyState);
|
||||
}.bind(this));
|
||||
|
||||
let shortkeys = userConfig.shortkeys;
|
||||
|
||||
Mousetrap.bind(shortkeys["delete_track"].value.split(','), deleteHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["switch_lock_property"].value, lockHandler, 'keyup');
|
||||
Mousetrap.bind(shortkeys["switch_all_lock_property"].value, lockAllHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["switch_occluded_property"].value.split(','), occludedHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["change_shape_color"].value, changeColorHandler, 'keydown');
|
||||
Mousetrap.bind(shortkeys["change_track_label"].value.split(','), changeLabelHandler, 'keydown');
|
||||
}
|
||||
|
||||
function setupInteract() {
|
||||
interact('.highlightedShape').draggable({
|
||||
onstart: function() {
|
||||
this._moveMode = true;
|
||||
}.bind(this),
|
||||
onmove: function(event) {
|
||||
let scale = this._playerScale;
|
||||
let target = $(event.target);
|
||||
if (this._drawMode || !target.hasClass('highlightedShape') || event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
target.attr('x', +target.attr('x') + event.dx/scale);
|
||||
target.attr('y', +target.attr('y') + event.dy/scale);
|
||||
target.trigger('drag', scale);
|
||||
}.bind(this),
|
||||
onend: function () {
|
||||
this._moveMode = false;
|
||||
}.bind(this)
|
||||
}).resizable({
|
||||
margin: 8,
|
||||
edges: { left: true, right: true, bottom: true, top: true },
|
||||
restrict: {
|
||||
restriction: $('#frameContent')[0]
|
||||
},
|
||||
onstart: function() {
|
||||
this._resizeMode = true;
|
||||
}.bind(this),
|
||||
onmove: function(event) {
|
||||
if (this._drawMode) return;
|
||||
let target = $(event.target);
|
||||
let scale = this._playerScale;
|
||||
if (this._drawMode || !target.hasClass('highlightedShape') || event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newX = +target.attr('x') + event.deltaRect.left / scale;
|
||||
let newY = +target.attr('y') + event.deltaRect.top / scale;
|
||||
let newHeight = +target.attr('height') + event.deltaRect.height / scale;
|
||||
let newWidth = +target.attr('width') + event.deltaRect.width / scale;
|
||||
if (newHeight < 1) newHeight = 1;
|
||||
if (newWidth < 1) newWidth = 1;
|
||||
target.attr('x', newX );
|
||||
target.attr('y', newY );
|
||||
target.attr('height', newHeight );
|
||||
target.attr('width', newWidth);
|
||||
target.trigger('resize', scale);
|
||||
}.bind(this),
|
||||
onend: function () {
|
||||
this._resizeMode = false;
|
||||
}.bind(this)
|
||||
});
|
||||
} // end of setupInteract
|
||||
}
|
||||
|
||||
setShowAllInterTracks(value) {
|
||||
this._collectionModel.allInterTracks = value;
|
||||
}
|
||||
|
||||
setHiddenForAll(value) {
|
||||
this._collectionModel.setHiddenForAll(value);
|
||||
}
|
||||
|
||||
setHiddenLabelForAll(value) {
|
||||
this._collectionModel.setHiddenLabelForAll(value);
|
||||
}
|
||||
|
||||
onchangeframe(newframe) {
|
||||
this._collectionModel.onchangeframe(newframe);
|
||||
if (this._AAM) return;
|
||||
let fakeKeyState = {
|
||||
ctrl: false,
|
||||
shift: false,
|
||||
alt: false
|
||||
};
|
||||
this._collectionModel.onmousemove(this._lastMousePos.x, this._lastMousePos.y, fakeKeyState);
|
||||
}
|
||||
|
||||
updateFrame() {
|
||||
this._collectionModel.updateFrame();
|
||||
}
|
||||
|
||||
resetactivetrack(e) {
|
||||
if (this._moveMode || this._resizeMode || this._AAM || e.ctrlKey) return;
|
||||
this._collectionModel.resetactivetrack();
|
||||
}
|
||||
|
||||
setactivetrack(id, e) {
|
||||
if (this._pasteMode || this._AAM || e.ctrlKey) return;
|
||||
this._collectionModel.setactivetrack(id);
|
||||
}
|
||||
|
||||
getmodifierkeysstate(e) {
|
||||
return {
|
||||
ctrl: e.ctrlKey,
|
||||
shift: e.shiftKey,
|
||||
alt: e.altKey,
|
||||
};
|
||||
}
|
||||
|
||||
onmousemove(e) {
|
||||
let pos = translateSVGPos($('#frameContent')[0], e.clientX, e.clientY, this._playerScale);
|
||||
this._lastMousePos = {
|
||||
x: pos.x,
|
||||
y: pos.y
|
||||
};
|
||||
if (this._moveMode || this._resizeMode || this._drawMode || this._pasteMode || this._AAM) return;
|
||||
let modKeysState = this.getmodifierkeysstate(e);
|
||||
this._collectionModel.onmousemove(pos.x, pos.y, modKeysState);
|
||||
}
|
||||
|
||||
onDrawerUpdate(drawer) {
|
||||
this._drawMode = drawer.drawMode;
|
||||
if (this._drawMode) this._collectionModel.resetactivetrack();
|
||||
}
|
||||
|
||||
onBufferUpdate(buffer) {
|
||||
this._pasteMode = buffer.pasteMode;
|
||||
if (this._pasteMode) {
|
||||
this._collectionModel.resetactivetrack();
|
||||
}
|
||||
}
|
||||
|
||||
onAAMUpdate(aam) {
|
||||
if (aam.activeAAM && !this._AAM) {
|
||||
this._collectionModel.resetactivetrack();
|
||||
interact('.highlightedShape').draggable(false);
|
||||
interact('.highlightedShape').resizable(false);
|
||||
}
|
||||
else if (this._AAM && !aam.activeAAM) {
|
||||
interact('.highlightedShape').draggable(true);
|
||||
interact('.highlightedShape').resizable(true);
|
||||
}
|
||||
this._AAM = aam.activeAAM;
|
||||
}
|
||||
|
||||
set playerScale(value) {
|
||||
this._playerScale = value;
|
||||
}
|
||||
|
||||
hasUnsavedChanges() {
|
||||
return this._collectionModel.getHash() !== this._collectionHash;
|
||||
}
|
||||
|
||||
updateHash() {
|
||||
this._collectionHash = this._collectionModel.getHash();
|
||||
}
|
||||
}
|
||||
@ -1,437 +0,0 @@
|
||||
/* exported CollectionModel */
|
||||
"use strict";
|
||||
|
||||
class CollectionModel extends Listener {
|
||||
constructor(labelsInfo, job, trackFilterModel) {
|
||||
super('onCollectionUpdate', getState);
|
||||
this._labelsInfo = labelsInfo;
|
||||
this._curFrame = null;
|
||||
this._frameChanged = false;
|
||||
this._stopFrame = job.stop;
|
||||
this._startFrame = job.start;
|
||||
this._trackCounter = 0;
|
||||
this._allTracks = [];
|
||||
this._annotationTracks = new Object();
|
||||
this._interpolationTracks = new Object();
|
||||
this._currentTracks = [];
|
||||
this._activeTrack = null;
|
||||
this._drawing = false;
|
||||
this._allInterTracks = false;
|
||||
this._trackFilter = trackFilterModel;
|
||||
this._trackFilter._updateCollection = () => this.update();
|
||||
|
||||
this._colorIndex = 0;
|
||||
this._colorSets = {
|
||||
background: ["#FFFFCC", "#FFFF66", "#FFCC66", "#FF9900", "#FF6633", "#FF6666", "#FF9999",
|
||||
"#FF6699", "#27EBF9", "#FF99CC", "#FF99FF", "#CC66FF", "#CC99FF", "#16E532",
|
||||
"#6666FF", "#0099FF", "#66CCCC", "#99FFFF", "#99FFCC", "#66FF99", "#CCFF99"],
|
||||
|
||||
border: ["#FFFF66", "#FFFF00", "#FFCC00", "#FF6600", "#FF3300", "#CC0033", "#FF3333",
|
||||
"#FF0066", "#4EF0FC", "#CC0066", "#FF00FF", "#9900CC", "#9933FF", "#02F423",
|
||||
"#3300CC", "#0033FF", "#006666", "#00CCCC", "#00FFCC", "#00FF66", "#66CC00"],
|
||||
|
||||
length: 21
|
||||
};
|
||||
|
||||
let self = this;
|
||||
function getState() {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_interpolate(tracks, frame) {
|
||||
let result = [];
|
||||
for (let trackId in tracks) {
|
||||
let track = tracks[trackId];
|
||||
if (track.removed) continue;
|
||||
let interpolation = track.interpolate(frame);
|
||||
|
||||
let outsided = interpolation.position.outsided;
|
||||
/* !outsided is provide the adding all tracks with non-outsided box
|
||||
* track.isKeyFrame(frame) is provide the adding tracks if frame is keyframe (needed in cases when track become outsided on this frame)
|
||||
* this._allInterTracks is provide the adding any tracks (in mode when right panel show all tracks) */
|
||||
if (!outsided || track.isKeyFrame(frame) || this._allInterTracks) {
|
||||
result.push({
|
||||
trackModel: track,
|
||||
interpolation: interpolation
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
collectStatistic() {
|
||||
let statByLabel = new Object();
|
||||
let labels = this._labelsInfo.labels();
|
||||
|
||||
for (let labelId in labels) {
|
||||
let labelName =labels[labelId];
|
||||
statByLabel[labelName] = {
|
||||
tracks: 0,
|
||||
manuallyShapes: 0,
|
||||
interpolatedShapes: 0
|
||||
};
|
||||
}
|
||||
|
||||
for (let track of this._allTracks) {
|
||||
if (!track.removed) {
|
||||
track.getStatToObject(statByLabel);
|
||||
}
|
||||
}
|
||||
|
||||
let totalTracks = 0;
|
||||
let totalManually = 0;
|
||||
let totalInterpolated = 0;
|
||||
|
||||
for (let key in statByLabel) {
|
||||
totalTracks += statByLabel[key].tracks;
|
||||
totalManually += statByLabel[key].manuallyShapes;
|
||||
totalInterpolated += statByLabel[key].interpolatedShapes;
|
||||
}
|
||||
|
||||
Object.defineProperty(statByLabel, 'totalObjectCount', {
|
||||
enumerable: false,
|
||||
value: {
|
||||
tracks: totalTracks,
|
||||
manuallyShapes: totalManually,
|
||||
interpolatedShapes: totalInterpolated
|
||||
}
|
||||
});
|
||||
|
||||
return statByLabel;
|
||||
}
|
||||
|
||||
removeTracks() {
|
||||
for (let i = 0; i < this._allTracks.length; i ++) {
|
||||
this._allTracks[i].remove(true);
|
||||
}
|
||||
this._allTracks = [];
|
||||
this._annotationTracks = new Object();
|
||||
this._interpolationTracks = new Object();
|
||||
this._currentTracks = [];
|
||||
this._activeTrack = null;
|
||||
this._trackCounter = 0;
|
||||
this.notify();
|
||||
}
|
||||
|
||||
importTracks(data) {
|
||||
let tracks = [];
|
||||
for (let box of data.boxes) {
|
||||
let box0 = [box.xtl, box.ytl, box.xbr, box.ybr, box.frame, false, box.occluded];
|
||||
let box1 = box0.slice();
|
||||
box1[4] += 1; // next frame
|
||||
box1[5] = true; // outside property
|
||||
|
||||
let attributes = [];
|
||||
for (let attr of box.attributes) {
|
||||
attributes.push([attr.id, box.frame, attr.value]);
|
||||
}
|
||||
|
||||
tracks.push({
|
||||
label: box.label_id,
|
||||
boxes: [box0, box1],
|
||||
attributes: attributes
|
||||
});
|
||||
}
|
||||
|
||||
for (let track of data.tracks) {
|
||||
let attributes = [];
|
||||
for (let attr of track.attributes) {
|
||||
attributes.push([attr.id, 0, attr.value]);
|
||||
}
|
||||
|
||||
let boxes = [];
|
||||
for (let box of track.boxes) {
|
||||
boxes.push([box.xtl, box.ytl, box.xbr, box.ybr, box.frame,
|
||||
box.outside, box.occluded]);
|
||||
for (let attr of box.attributes) {
|
||||
attributes.push([attr.id, box.frame, attr.value]);
|
||||
}
|
||||
}
|
||||
|
||||
tracks.push({
|
||||
label: track.label_id,
|
||||
boxes: boxes,
|
||||
attributes: attributes
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0; i < tracks.length; i ++) {
|
||||
if (!tracks[i].boxes.length) continue; // Remove saved wrong tracks with empty path
|
||||
this.add(tracks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
exportTracks() {
|
||||
let response = {"boxes": [], "tracks": []};
|
||||
for (let i = 0; i < this._allTracks.length; i ++ ) {
|
||||
let track = this._allTracks[i];
|
||||
// !Number.isInteger(track._firstFrame) need for remove fully outsided tracks. First frame for such tracks is undefined.
|
||||
if (track.removed || !Number.isInteger(track._firstFrame)) continue;
|
||||
if (track.trackType == "annotation") {
|
||||
response["boxes"].push(track.export());
|
||||
} else {
|
||||
response["tracks"].push(track.export());
|
||||
}
|
||||
|
||||
}
|
||||
return JSON.stringify(response);
|
||||
}
|
||||
|
||||
add(data) {
|
||||
let colors = this.getColors();
|
||||
let trackModel = new TrackModel('box', data, this._trackCounter,
|
||||
this._labelsInfo, this._stopFrame, this._startFrame, colors);
|
||||
let trackType = trackModel.trackType;
|
||||
if (trackType === 'interpolation') {
|
||||
this._interpolationTracks[this._trackCounter] = trackModel;
|
||||
}
|
||||
else {
|
||||
let firstFrame = trackModel.firstFrame;
|
||||
if (! (firstFrame in this._annotationTracks)) {
|
||||
this._annotationTracks[firstFrame] = new Object();
|
||||
}
|
||||
this._annotationTracks[firstFrame][this._trackCounter] = trackModel;
|
||||
}
|
||||
this._allTracks.push(trackModel);
|
||||
this._trackCounter ++;
|
||||
}
|
||||
|
||||
|
||||
createFromPos(pos, label, type) {
|
||||
let boxes = [];
|
||||
|
||||
let current = [pos.xtl, pos.ytl, pos.xbr, pos.ybr, this._curFrame, 0, 0];
|
||||
boxes.push(current);
|
||||
|
||||
if (type === 'annotation') {
|
||||
let next = [pos.xtl, pos.ytl, pos.xbr, pos.ybr, this._curFrame + 1, 1, 0];
|
||||
boxes.push(next);
|
||||
}
|
||||
|
||||
this.add({
|
||||
attributes: [],
|
||||
boxes: boxes,
|
||||
label: label
|
||||
});
|
||||
|
||||
this.onchangeframe(this._curFrame);
|
||||
}
|
||||
|
||||
|
||||
update() {
|
||||
if (this._curFrame != null) {
|
||||
this.onchangeframe(this._curFrame);
|
||||
}
|
||||
}
|
||||
|
||||
onchangeframe(newframe) {
|
||||
for (let i = 0; i < this._currentTracks.length; i ++ ) {
|
||||
this._currentTracks[i].trackModel.active = false;
|
||||
this._currentTracks[i].trackModel.unsubscribeAll();
|
||||
}
|
||||
this._currentTracks = [];
|
||||
this._activeTrack = null;
|
||||
this._frameChanged = this._curFrame != newframe;
|
||||
this._curFrame = newframe;
|
||||
|
||||
let annotationTracks = [];
|
||||
let interpolationTracks = [];
|
||||
|
||||
if (newframe in this._annotationTracks) {
|
||||
for (let trackId in this._annotationTracks[newframe]) {
|
||||
this._annotationTracks[newframe][trackId].curFrame = newframe;
|
||||
}
|
||||
annotationTracks = this._interpolate(this._annotationTracks[newframe], newframe);
|
||||
}
|
||||
|
||||
for (let trackId in this._interpolationTracks) {
|
||||
this._interpolationTracks[trackId].curFrame = newframe;
|
||||
}
|
||||
interpolationTracks = this._interpolate(this._interpolationTracks, newframe);
|
||||
this._currentTracks = annotationTracks.concat(interpolationTracks);
|
||||
this._trackFilter.filter(this._currentTracks);
|
||||
this.notify();
|
||||
}
|
||||
|
||||
|
||||
findFilterFrame(direction) {
|
||||
if (direction != 1 && direction != -1) {
|
||||
throw Error('Bad direction value: ', direction);
|
||||
}
|
||||
let frame = this._curFrame;
|
||||
while (frame <= this._stopFrame && frame >= this._startFrame) {
|
||||
frame += direction;
|
||||
if (frame in this._annotationTracks) {
|
||||
let annotationTracks = this._interpolate(this._annotationTracks[frame], frame);
|
||||
let filteredIndexes = this._trackFilter.filter(annotationTracks);
|
||||
if (filteredIndexes.length) return frame;
|
||||
}
|
||||
let interpolationTracks = this._interpolate(this._interpolationTracks, frame);
|
||||
let filteredIndexes = this._trackFilter.filter(interpolationTracks);
|
||||
if (filteredIndexes.length) return frame;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
updateFrame() {
|
||||
if (this._curFrame != null) {
|
||||
this.onchangeframe(this._curFrame);
|
||||
}
|
||||
}
|
||||
|
||||
switchLockForActive() {
|
||||
if (this._activeTrack != null) {
|
||||
let value = this._allTracks[this._activeTrack].lock;
|
||||
this._allTracks[this._activeTrack].lock = !value;
|
||||
Logger.addEvent(Logger.EventType.lockObject, {value: !value, mode: 'single object'});
|
||||
}
|
||||
}
|
||||
|
||||
switchLockForAll() {
|
||||
let value = true;
|
||||
for (let track of this._currentTracks) {
|
||||
value = value && track.trackModel.lock;
|
||||
if (!value) break;
|
||||
}
|
||||
|
||||
for (let track of this._currentTracks) {
|
||||
track.trackModel.lock = !value;
|
||||
}
|
||||
|
||||
if (this._currentTracks.length > 0) {
|
||||
Logger.addEvent(Logger.EventType.lockObject, {value: !value, mode: 'multy object'});
|
||||
}
|
||||
}
|
||||
|
||||
switchOccludedForActive() {
|
||||
if (this._activeTrack != null) {
|
||||
this._allTracks[this._activeTrack].occluded ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
switchColorForActive() {
|
||||
if (this._activeTrack != null) {
|
||||
this._allTracks[this._activeTrack].colors = this.getColors();
|
||||
}
|
||||
}
|
||||
|
||||
setHiddenForAll(value) {
|
||||
for (let track of this._allTracks) {
|
||||
track.hidden = value;
|
||||
}
|
||||
}
|
||||
|
||||
setHiddenLabelForAll(value) {
|
||||
for (let track of this._allTracks) {
|
||||
track.hiddenLabel = value;
|
||||
}
|
||||
}
|
||||
|
||||
reinitializeActive(newLabelIdx) {
|
||||
let keys = Object.keys(this._labelsInfo.labels());
|
||||
if (this._activeTrack != null && newLabelIdx < keys.length) {
|
||||
this._allTracks[this._activeTrack].reinitialize(keys[newLabelIdx]);
|
||||
this.updateFrame();
|
||||
}
|
||||
}
|
||||
|
||||
setactivetrack(trackID) {
|
||||
if (trackID != this._activeTrack) {
|
||||
this.resetactivetrack();
|
||||
this._activeTrack = trackID;
|
||||
this._allTracks[this._activeTrack].active = true;
|
||||
}
|
||||
}
|
||||
|
||||
resetactivetrack() {
|
||||
if (this._activeTrack != null) {
|
||||
this._allTracks[this._activeTrack].active = false;
|
||||
this._activeTrack = null;
|
||||
}
|
||||
}
|
||||
|
||||
removeactivetrack(force) {
|
||||
if (this._activeTrack != null) {
|
||||
this._allTracks[this._activeTrack].remove(force);
|
||||
}
|
||||
}
|
||||
|
||||
onmousemove(x, y, modKeysStates) {
|
||||
if (this._activeTrack != null && modKeysStates.ctrl) return;
|
||||
let activeTrack = null;
|
||||
let minArea = Number.MAX_SAFE_INTEGER;
|
||||
this._currentTracks.forEach((item) => {
|
||||
if (item.trackModel.removed) return;
|
||||
let pos = item.trackModel._shape.interpolatePosition(this._curFrame, item.trackModel.firstFrame);
|
||||
let type = item.trackModel.shapeType;
|
||||
if (pos.outsided || item.trackModel.hidden) return;
|
||||
if (TrackModel.ShapeContain(pos, x, y, type)) {
|
||||
let area = TrackModel.ShapeArea(pos, type);
|
||||
if (area < minArea || this._allTracks[activeTrack].lock) {
|
||||
if (activeTrack != null) {
|
||||
if (!this._allTracks[activeTrack].lock && this._allTracks[item.trackModel.id].lock) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
minArea = area;
|
||||
activeTrack = item.trackModel.id;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (activeTrack != null) {
|
||||
this.setactivetrack(activeTrack);
|
||||
}
|
||||
else this.resetactivetrack();
|
||||
return activeTrack;
|
||||
}
|
||||
|
||||
getColors() {
|
||||
let oldColorIndex = this._colorIndex;
|
||||
this._colorIndex ++;
|
||||
if (this._colorIndex >= this._colorSets.length) {
|
||||
this._colorIndex = 0;
|
||||
}
|
||||
var colors = {
|
||||
background: this._colorSets.background[oldColorIndex],
|
||||
border: this._colorSets.border[oldColorIndex]
|
||||
};
|
||||
return colors;
|
||||
}
|
||||
|
||||
set allInterTracks(value) {
|
||||
if (value != true && value != false) {
|
||||
throw new Error(`Value must be boolean, but ${typeof(value)} extracted.`);
|
||||
}
|
||||
this._allInterTracks = value;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get currentTracks() {
|
||||
return this._currentTracks;
|
||||
}
|
||||
|
||||
get activeTrack() {
|
||||
if (this._activeTrack != null) {
|
||||
return this._allTracks[this._activeTrack];
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
get allTracks() {
|
||||
return this._allTracks;
|
||||
}
|
||||
|
||||
get frameChanged() {
|
||||
return this._frameChanged;
|
||||
}
|
||||
|
||||
get allInterTracks() {
|
||||
return this._allInterTracks;
|
||||
}
|
||||
|
||||
getHash() {
|
||||
return objectHash.sha1(this.exportTracks());
|
||||
}
|
||||
}
|
||||
@ -1,140 +0,0 @@
|
||||
/*exported CollectionView */
|
||||
"use strict";
|
||||
|
||||
class CollectionView {
|
||||
constructor(collectionController, collectionModel, playerModel, labelsInfo) {
|
||||
this._frameContent = $('#frameContent');
|
||||
this._createTrackButton = $('#createTrackButton');
|
||||
this._mergeTracksButton = $('#mergeTracksButton');
|
||||
this._labelSelect = $('#labelSelect');
|
||||
this._trackTypeSelect = $('#trackTypeSelect');
|
||||
this._hideBoxesCheck = $('#hideBoxesBox');
|
||||
this._hideLabelsCheck = $('#hideLabelsBox');
|
||||
this._showAllInterBox = $('#showAllInterBox');
|
||||
this._uiContent = $('#uiContent');
|
||||
this._collectionController = collectionController;
|
||||
this._playerScale = 1;
|
||||
this._revPlayerScale = 1;
|
||||
this._tracks = [];
|
||||
this._labelsInfo = labelsInfo;
|
||||
this._playerModel = playerModel;
|
||||
|
||||
this._hideLabelsCheck.prop('checked', true);
|
||||
this._showAllInterBox.prop('checked', collectionModel.allInterTracks);
|
||||
playerModel.subscribe(this);
|
||||
collectionModel.subscribe(this);
|
||||
|
||||
this._frameContent.on('mousemove', collectionController.onmousemove.bind(collectionController));
|
||||
this._hideBoxesCheck.on('change', this.hideBoxes.bind(this));
|
||||
this._hideLabelsCheck.on('change', this.hideLabels.bind(this));
|
||||
this._showAllInterBox.on('change', (e) => collectionController.setShowAllInterTracks(e.target.checked));
|
||||
}
|
||||
|
||||
hideBoxes(e) {
|
||||
let value = e.target.checked;
|
||||
this._collectionController.setHiddenForAll(value);
|
||||
}
|
||||
|
||||
hideLabels(e) {
|
||||
let value = e.target.checked;
|
||||
this._collectionController.setHiddenLabelForAll(value);
|
||||
}
|
||||
|
||||
|
||||
onPlayerUpdate(player) {
|
||||
let geometry = player.geometry;
|
||||
let frames = player.frames;
|
||||
|
||||
if (this._playerScale != geometry.scale) {
|
||||
this._playerScale = geometry.scale;
|
||||
this._revPlayerScale = 1 / geometry.scale;
|
||||
for (let i = 0; i < this._tracks.length; i ++ ) {
|
||||
this._tracks[i].view.revscale = this._revPlayerScale;
|
||||
this._tracks[i].view.updateViewGeometry();
|
||||
}
|
||||
this._collectionController.playerScale = this._playerScale;
|
||||
}
|
||||
|
||||
if (frames.previous != frames.current) {
|
||||
this._collectionController.onchangeframe(frames.current);
|
||||
}
|
||||
}
|
||||
|
||||
onCollectionUpdate(collection) {
|
||||
let offset = this._uiContent.prop('scrollTop');
|
||||
let numOfTracksBefore = this._tracks.length;
|
||||
|
||||
for (let i = 0; i < this._tracks.length; i ++) {
|
||||
this._tracks[i].view.removeView();
|
||||
}
|
||||
this._tracks = [];
|
||||
this._frameContent.find('defs').remove();
|
||||
this._frameContent.find('rect.outsideRect').remove();
|
||||
|
||||
let newcollection = collection.currentTracks;
|
||||
newcollection.sort(trackComparator);
|
||||
|
||||
|
||||
for (let i = 0; i < newcollection.length; i ++ ) {
|
||||
let interpolation = newcollection[i].interpolation;
|
||||
let trackModel = newcollection[i].trackModel;
|
||||
let colors = trackModel.colors;
|
||||
let trackController = new TrackController(trackModel);
|
||||
let trackView = new TrackView(trackController, trackModel, interpolation, this._labelsInfo, colors);
|
||||
trackModel.notify();
|
||||
trackView.revscale = this._revPlayerScale;
|
||||
trackView.updateViewGeometry();
|
||||
trackView.onoverUI = (id, e) => this._collectionController.setactivetrack(id, e);
|
||||
trackView.onoutUI = (e) => this._collectionController.resetactivetrack(e);
|
||||
trackView.onshift = (frame) => this._playerModel.shift(frame, true);
|
||||
trackView.onchangelabel = function(trackModel, newLabelId) {
|
||||
trackModel.reinitialize(newLabelId);
|
||||
this._collectionController.updateFrame();
|
||||
}.bind(this);
|
||||
|
||||
this._tracks.push({
|
||||
model: trackModel,
|
||||
controller: trackController,
|
||||
view: trackView
|
||||
});
|
||||
}
|
||||
|
||||
if (numOfTracksBefore === this._tracks.length && !collection.frameChanged) {
|
||||
this._uiContent.prop('scrollTop', offset);
|
||||
}
|
||||
|
||||
function trackComparator(a,b) {
|
||||
return b.trackModel.id - a.trackModel.id;
|
||||
}
|
||||
}
|
||||
|
||||
onstartdraw() {
|
||||
this._createTrackButton.prop('disabled', true);
|
||||
this._mergeTracksButton.prop('disabled', true);
|
||||
this._labelSelect.prop('disabled', true);
|
||||
this._trackTypeSelect.prop('disabled', true);
|
||||
this._frameContent.css('cursor', 'crosshair');
|
||||
}
|
||||
|
||||
onDrawerUpdate(drawer) {
|
||||
if (drawer.drawMode && !drawer.clicks.length) {
|
||||
this.lockChange(true);
|
||||
}
|
||||
else if (!drawer.drawMode) {
|
||||
this.lockChange(false);
|
||||
}
|
||||
}
|
||||
|
||||
lockChange(value) {
|
||||
if (value) {
|
||||
for (let track of this._tracks) {
|
||||
track.view._shape.removeClass('changeable');
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (let track of this._tracks) {
|
||||
track.view._shape.addClass('changeable');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,285 +0,0 @@
|
||||
/* exported DrawerModel DrawerController DrawerView */
|
||||
"use strict";
|
||||
|
||||
class DrawerModel extends Listener {
|
||||
constructor(trackCollection) {
|
||||
super('onDrawerUpdate', getState);
|
||||
this._drawMode = false;
|
||||
this._drawShape = 'rect';
|
||||
this._collection = trackCollection;
|
||||
this._trackType = null;
|
||||
this._label = null;
|
||||
this._clicks = [];
|
||||
this._drawObjectEvent = null;
|
||||
this._pasteMode = false;
|
||||
this._mergeMode = false;
|
||||
this._AAM = false;
|
||||
|
||||
let self = this;
|
||||
function getState() {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
switchDraw() {
|
||||
if (this._drawMode) this.endDraw();
|
||||
else this.startDraw();
|
||||
this.notify();
|
||||
}
|
||||
|
||||
endDraw() {
|
||||
this._drawMode = false;
|
||||
this._clicks = [];
|
||||
this._trackType = null;
|
||||
this._label = null;
|
||||
this._drawObjectEvent = null;
|
||||
}
|
||||
|
||||
startDraw() {
|
||||
if (this._pasteMode || this._mergeMode || this._AAM) return;
|
||||
this._drawMode = true;
|
||||
this._drawObjectEvent = Logger.addContinuedEvent(Logger.EventType.drawObject);
|
||||
}
|
||||
|
||||
addPoint(pos) {
|
||||
if (!this._drawMode) return;
|
||||
if (this._drawShape === 'rect') {
|
||||
|
||||
for (let i = 0; i < this._clicks.length; i ++) {
|
||||
if (!this._clicks[i].fixed) {
|
||||
this._clicks.splice(i,1);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._clicks.length) {
|
||||
let diffX = Math.abs(this._clicks[0].x - pos.x);
|
||||
let diffY = Math.abs(this._clicks[0].y - pos.y);
|
||||
if (diffX >= MIN_BOX_SIZE && diffY >= MIN_BOX_SIZE) {
|
||||
this._clicks.push(pos);
|
||||
if (pos.fixed) {
|
||||
let rectPos = {
|
||||
xtl: Math.min(this._clicks[0].x, this._clicks[1].x),
|
||||
ytl: Math.min(this._clicks[0].y, this._clicks[1].y),
|
||||
xbr: Math.min(this._clicks[0].x, this._clicks[1].x) + Math.abs(this._clicks[0].x - this._clicks[1].x),
|
||||
ybr: Math.min(this._clicks[0].y, this._clicks[1].y) + Math.abs(this._clicks[0].y - this._clicks[1].y)
|
||||
};
|
||||
|
||||
this._collection.createFromPos(rectPos, this._label, this._trackType);
|
||||
Logger.addEvent(Logger.EventType.addObject, {count: 1});
|
||||
this._drawObjectEvent.close();
|
||||
this.endDraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._clicks.push(pos);
|
||||
}
|
||||
this.notify();
|
||||
}
|
||||
else throw new Error('Unknown shape type when draw');
|
||||
}
|
||||
|
||||
get drawMode() {
|
||||
return this._drawMode;
|
||||
}
|
||||
|
||||
get clicks() {
|
||||
return this._clicks;
|
||||
}
|
||||
|
||||
get drawShape() {
|
||||
return this._drawShape;
|
||||
}
|
||||
|
||||
set label(value) {
|
||||
this._label = value;
|
||||
}
|
||||
|
||||
set trackType(value) {
|
||||
this._trackType = value;
|
||||
}
|
||||
|
||||
onBufferUpdate(buffer) {
|
||||
this._pasteMode = buffer.pasteMode;
|
||||
}
|
||||
|
||||
onMergerUpdate(merger) {
|
||||
this._mergeMode = merger.mergeMode;
|
||||
}
|
||||
|
||||
onAAMUpdate(aam) {
|
||||
this._AAM = aam.activeAAM;
|
||||
if (this._AAM && this._drawMode) {
|
||||
this.endDraw();
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class DrawerController {
|
||||
constructor(drawerModel) {
|
||||
this._model = drawerModel;
|
||||
setupDrawerShortkeys.call(this);
|
||||
|
||||
function setupDrawerShortkeys() {
|
||||
let drawHandler = Logger.shortkeyLogDecorator(function() {
|
||||
this.onDrawPressed();
|
||||
}.bind(this));
|
||||
|
||||
let shortkeys = userConfig.shortkeys;
|
||||
|
||||
Mousetrap.bind(shortkeys["switch_draw_mode"].value, drawHandler, 'keydown');
|
||||
}
|
||||
}
|
||||
|
||||
onDrawPressed() {
|
||||
this._model.switchDraw();
|
||||
}
|
||||
|
||||
onAddPoint(pos) {
|
||||
this._model.addPoint(pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class DrawerView {
|
||||
constructor(drawerController) {
|
||||
this._controller = drawerController;
|
||||
this._drawButton = $('#createTrackButton');
|
||||
this._drawLabelSelect = $('#labelSelect');
|
||||
this._drawTrackTypeSelect = $('#trackTypeSelect');
|
||||
this._frameContent = $('#frameContent');
|
||||
this._drawMode = false;
|
||||
this._drawShapeType = null;
|
||||
this._aim = {
|
||||
aimX: null,
|
||||
aimY: null
|
||||
};
|
||||
this._drawShape = null;
|
||||
this._playerScale = 1;
|
||||
|
||||
this._drawButton.on('click', () => this._controller.onDrawPressed.call(this._controller));
|
||||
}
|
||||
|
||||
onDrawerUpdate(drawer) {
|
||||
if (drawer.drawMode) {
|
||||
if (!this._drawMode) {
|
||||
this._drawButton.text('Cancel Draw (N)');
|
||||
|
||||
this._frameContent.on('mousemove.drawer', mousemoveHandler.bind(this));
|
||||
this._frameContent.on('mouseleave.drawer', mouseleaveHandler.bind(this));
|
||||
this._frameContent.on('mousedown.drawer', mousedownHandler.bind(this));
|
||||
|
||||
this._drawMode = true;
|
||||
this._drawShapeType = drawer.drawShape;
|
||||
if (this._drawShapeType == 'rect') {
|
||||
this._drawShape = $(document.createElementNS('http://www.w3.org/2000/svg', 'rect')).attr({
|
||||
'stroke': '#ffffff'
|
||||
}).addClass('shape').css({
|
||||
'stroke-width': 2 / this._playerScale,
|
||||
}).appendTo(this._frameContent);
|
||||
}
|
||||
this._aim = {
|
||||
aimX: $(document.createElementNS('http://www.w3.org/2000/svg', 'line')).attr({
|
||||
'stroke': 'red',
|
||||
}).css({
|
||||
'stroke-width': 2 / this._playerScale
|
||||
}).appendTo(this._frameContent)
|
||||
,
|
||||
aimY: $(document.createElementNS('http://www.w3.org/2000/svg', 'line')).attr({
|
||||
'stroke': 'red',
|
||||
}).css({
|
||||
'stroke-width': 2 / this._playerScale
|
||||
}).appendTo(this._frameContent)
|
||||
};
|
||||
|
||||
drawer.label = +this._drawLabelSelect.prop('value');
|
||||
drawer.trackType = this._drawTrackTypeSelect.prop('value');
|
||||
}
|
||||
else {
|
||||
let clicks = drawer.clicks;
|
||||
if (this._drawShapeType == 'rect' && clicks.length == 2) {
|
||||
this._drawShape.attr({
|
||||
x: Math.min(clicks[0].x, clicks[1].x),
|
||||
y: Math.min(clicks[0].y, clicks[1].y),
|
||||
width: Math.abs(clicks[0].x - clicks[1].x),
|
||||
height: Math.abs(clicks[0].y - clicks[1].y)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this._drawMode) {
|
||||
this._drawButton.text('Create Track');
|
||||
|
||||
this._frameContent.off('mousemove.drawer');
|
||||
this._frameContent.off('mouseleave.drawer');
|
||||
this._frameContent.off('mousedown.drawer');
|
||||
|
||||
this._drawMode = false;
|
||||
this._drawShapeType = null;
|
||||
this._drawShape.remove();
|
||||
this._drawShape = null;
|
||||
this._aim.aimX.remove();
|
||||
this._aim.aimX = null;
|
||||
this._aim.aimY.remove();
|
||||
this._aim.aimY = null;
|
||||
}
|
||||
}
|
||||
|
||||
this._drawLabelSelect.prop('disabled', this._drawMode);
|
||||
this._drawTrackTypeSelect.prop('disabled', this._drawMode);
|
||||
|
||||
function mousemoveHandler(e) {
|
||||
let pos = translateSVGPos(this._frameContent['0'], e.clientX, e.clientY, this._playerScale);
|
||||
pos.fixed = false;
|
||||
this._controller.onAddPoint(pos);
|
||||
|
||||
this._aim.aimX.attr({
|
||||
x1: 0,
|
||||
y1: pos.y,
|
||||
x2: this._frameContent.css('width'),
|
||||
y2: pos.y
|
||||
}).css('display', '');
|
||||
|
||||
this._aim.aimY.attr({
|
||||
x1: pos.x,
|
||||
y1: 0,
|
||||
x2: pos.x,
|
||||
y2: this._frameContent.css('height')
|
||||
}).css('display', '');
|
||||
}
|
||||
|
||||
function mouseleaveHandler() {
|
||||
if (this._drawMode) {
|
||||
this._aim.aimX.css('display', 'none');
|
||||
this._aim.aimY.css('display', 'none');
|
||||
}
|
||||
}
|
||||
|
||||
function mousedownHandler(e) {
|
||||
if (e.shiftKey) return;
|
||||
let pos = translateSVGPos(this._frameContent['0'], e.clientX, e.clientY, this._playerScale);
|
||||
pos.fixed = true;
|
||||
this._controller.onAddPoint(pos);
|
||||
}
|
||||
}
|
||||
|
||||
onPlayerUpdate(player) {
|
||||
this._playerScale = player.geometry.scale;
|
||||
if (this._drawMode) {
|
||||
this._aim.aimX.css({
|
||||
'stroke-width': 2 / this._playerScale
|
||||
});
|
||||
this._aim.aimY.css({
|
||||
'stroke-width': 2 / this._playerScale
|
||||
});
|
||||
this._drawShape.css({
|
||||
'stroke-width': 2 / this._playerScale
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,207 @@
|
||||
/* exported HistoryModel HistoryController HistoryView */
|
||||
"use strict";
|
||||
|
||||
class HistoryModel extends Listener {
|
||||
constructor(playerModel) {
|
||||
super('onHistoryUpdate', () => this );
|
||||
|
||||
this._deep = 128;
|
||||
this._id = 0;
|
||||
this._undo_stack = [];
|
||||
this._redo_stack = [];
|
||||
this._locked = false;
|
||||
this._player = playerModel;
|
||||
|
||||
window.cvat.addAction = (name, undo, redo, frame) => this.addAction(name, undo, redo, frame);
|
||||
}
|
||||
|
||||
undo() {
|
||||
let frame = window.cvat.player.frames.current;
|
||||
let undo = this._undo_stack.pop();
|
||||
|
||||
if (undo) {
|
||||
try {
|
||||
Logger.addEvent(Logger.EventType.undoAction, {
|
||||
name: undo.name,
|
||||
frame: undo.frame,
|
||||
});
|
||||
|
||||
if (undo.frame != frame) {
|
||||
this._player.shift(undo.frame, true);
|
||||
}
|
||||
this._locked = true;
|
||||
undo.undo();
|
||||
}
|
||||
catch(err) {
|
||||
this.notify();
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
this._locked = false;
|
||||
}
|
||||
|
||||
this._redo_stack.push(undo);
|
||||
}
|
||||
|
||||
this.notify();
|
||||
}
|
||||
|
||||
redo() {
|
||||
let frame = window.cvat.player.frames.current;
|
||||
let redo = this._redo_stack.pop();
|
||||
|
||||
if (redo) {
|
||||
try {
|
||||
Logger.addEvent(Logger.EventType.redoAction, {
|
||||
name: redo.name,
|
||||
frame: redo.frame,
|
||||
});
|
||||
|
||||
if (redo.frame != frame) {
|
||||
this._player.shift(redo.frame, true);
|
||||
}
|
||||
this._locked = true;
|
||||
redo.redo();
|
||||
}
|
||||
catch(err) {
|
||||
this.notify();
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
this._locked = false;
|
||||
}
|
||||
|
||||
this._undo_stack.push(redo);
|
||||
}
|
||||
|
||||
this.notify();
|
||||
}
|
||||
|
||||
addAction(name, undo, redo, frame) {
|
||||
if (this._locked) return;
|
||||
if (this._undo_stack.length >= this._deep) {
|
||||
this._undo_stack.shift();
|
||||
}
|
||||
|
||||
this._undo_stack.push({
|
||||
name: name,
|
||||
undo: undo,
|
||||
redo: redo,
|
||||
frame: frame,
|
||||
id: this._id++,
|
||||
});
|
||||
this._redo_stack = [];
|
||||
this.notify();
|
||||
}
|
||||
|
||||
empty() {
|
||||
this._undo_stack = [];
|
||||
this._redo_stack = [];
|
||||
this._id = 0;
|
||||
this.notify();
|
||||
}
|
||||
|
||||
get undoLength() {
|
||||
return this._undo_stack.length;
|
||||
}
|
||||
|
||||
get redoLength() {
|
||||
return this._redo_stack.length;
|
||||
}
|
||||
|
||||
get lastUndoText() {
|
||||
let lastUndo = this._undo_stack[this._undo_stack.length - 1];
|
||||
if (lastUndo) {
|
||||
return `${lastUndo.name} [Frame ${lastUndo.frame}] [Id ${lastUndo.id}]`;
|
||||
}
|
||||
else return 'None';
|
||||
}
|
||||
|
||||
get lastRedoText() {
|
||||
let lastRedo = this._redo_stack[this._redo_stack.length - 1];
|
||||
if (lastRedo) {
|
||||
return `${lastRedo.name} [Frame ${lastRedo.frame}] [Id ${lastRedo.id}]`;
|
||||
}
|
||||
else return 'None';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class HistoryController {
|
||||
constructor(model) {
|
||||
this._model = model;
|
||||
setupCollectionShortcuts.call(this);
|
||||
|
||||
function setupCollectionShortcuts() {
|
||||
let undoHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
this.undo();
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
|
||||
let redoHandler = Logger.shortkeyLogDecorator(function(e) {
|
||||
this.redo();
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
|
||||
let shortkeys = window.cvat.config.shortkeys;
|
||||
Mousetrap.bind(shortkeys["undo"].value, undoHandler.bind(this), 'keydown');
|
||||
Mousetrap.bind(shortkeys["redo"].value, redoHandler.bind(this), 'keydown');
|
||||
}
|
||||
}
|
||||
|
||||
undo() {
|
||||
if (!window.cvat.mode) {
|
||||
this._model.undo();
|
||||
}
|
||||
}
|
||||
|
||||
redo() {
|
||||
if (!window.cvat.mode) {
|
||||
this._model.redo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class HistoryView {
|
||||
constructor(controller, model) {
|
||||
this._controller = controller;
|
||||
this._undoButton = $('#undoButton');
|
||||
this._redoButton = $('#redoButton');
|
||||
this._lastUndoText = $('#lastUndoText');
|
||||
this._lastRedoText = $('#lastRedoText');
|
||||
|
||||
let shortkeys = window.cvat.config.shortkeys;
|
||||
this._undoButton.attr('title', `${shortkeys['undo'].view_value} - ${shortkeys['undo'].description}`);
|
||||
this._redoButton.attr('title', `${shortkeys['redo'].view_value} - ${shortkeys['redo'].description}`);
|
||||
|
||||
this._undoButton.on('click', () => {
|
||||
this._controller.undo();
|
||||
});
|
||||
|
||||
this._redoButton.on('click', () => {
|
||||
this._controller.redo();
|
||||
});
|
||||
|
||||
model.subscribe(this);
|
||||
}
|
||||
|
||||
onHistoryUpdate(model) {
|
||||
if (model.undoLength) {
|
||||
this._undoButton.prop('disabled', false);
|
||||
}
|
||||
else {
|
||||
this._undoButton.prop('disabled', true);
|
||||
}
|
||||
|
||||
if (model.redoLength) {
|
||||
this._redoButton.prop('disabled', false);
|
||||
}
|
||||
else {
|
||||
this._redoButton.prop('disabled', true);
|
||||
}
|
||||
|
||||
this._lastUndoText.text(model.lastUndoText);
|
||||
this._lastRedoText.text(model.lastRedoText);
|
||||
}
|
||||
}
|
||||