From c2192cda9a0ded2c9da4fea35c0d5b0c3c845b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz> Date: Sun, 17 Apr 2022 14:12:59 +0200 Subject: [PATCH 01/12] Selection detection and calculation of start and end position in original document --- .../annotation/DocumentAnnotationView.tsx | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/webapp/components/annotation/DocumentAnnotationView.tsx b/webapp/components/annotation/DocumentAnnotationView.tsx index f55fe05..8c2090e 100644 --- a/webapp/components/annotation/DocumentAnnotationView.tsx +++ b/webapp/components/annotation/DocumentAnnotationView.tsx @@ -15,6 +15,66 @@ export function DocumentAnnotationView() { return ( <div> + <Button + onClick={() => { + const selection = window.getSelection(); + if (!selection) { + return; + } + if (!annotation?.tagStartPositions || !annotation.tagLengths) { + console.log('start or lengths not found'); + return; + } + + let startTag = selection.anchorNode; + let endTag = selection.focusNode; + + if (!startTag || !endTag) { + console.log('Selection not found'); + return; + } + + if (startTag.nodeName.includes('#')) { + startTag = startTag.parentNode; + } + if (endTag.nodeName.includes('#')) { + endTag = endTag.parentNode; + } + if (!startTag || !endTag) { + console.log('Selection element not found'); + return; + } + + if (!(startTag instanceof Element)) { + console.log('StartTag is not instance of Element'); + return; + } + if (!(endTag instanceof Element)) { + console.log('EndTag is not instance of Element'); + return; + } + + const startElement = startTag as Element; + const endElement = endTag as Element; + + const startId: number = + Number(startElement.getAttribute('aswi-tag-id')) ?? -1; + const endId: number = + Number(endElement.getAttribute('aswi-tag-id')) ?? -1; + + const startPosition = + annotation.tagStartPositions[startId] + + annotation.tagLengths[startId] + + selection.anchorOffset; + const endPosition = + annotation.tagStartPositions[endId] + + annotation.tagLengths[endId] + + selection.focusOffset - + 1; + }} + > + Test + </Button> <div dangerouslySetInnerHTML={{ __html: annotation.documentToRender ?? '' }} /> -- GitLab From be4deff84566ef77ef9eed0649c91de0cde01d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com> Date: Sun, 17 Apr 2022 14:38:23 +0200 Subject: [PATCH 02/12] Minor fixes, added annotation instance endpoint - untested --- .../Controllers/AnnotationController.cs | 29 ++ .../Migrations/20220417114429_V3.Designer.cs | 395 ++++++++++++++++++ .../Backend/Migrations/20220417114429_V3.cs | 258 ++++++++++++ .../DatabaseContextModelSnapshot.cs | 24 +- Backend/Backend/Program.cs | 2 + Backend/Core/Entities/AnnotationTag.cs | 2 +- Backend/Core/Seeding/Seeder.cs | 2 +- .../AnnotationService/AnnotationServiceEF.cs | 48 +++ .../AnnotationService/IAnnotationService.cs | 2 + .../AnnotationInstanceAddRequest.cs | 20 + Backend/Models/Enums/ETagType.cs | 14 + Backend/Models/Tags/TagInstanceInfo.cs | 2 +- 12 files changed, 777 insertions(+), 21 deletions(-) create mode 100644 Backend/Backend/Migrations/20220417114429_V3.Designer.cs create mode 100644 Backend/Backend/Migrations/20220417114429_V3.cs create mode 100644 Backend/Models/Annotations/AnnotationInstanceAddRequest.cs create mode 100644 Backend/Models/Enums/ETagType.cs diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs index b5cb02a..ccfe949 100644 --- a/Backend/Backend/Controllers/AnnotationController.cs +++ b/Backend/Backend/Controllers/AnnotationController.cs @@ -73,5 +73,34 @@ namespace RestAPI.Controllers } } + + [HttpPost("/annotation/{annotationId}")] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.Forbidden)] + public ActionResult PostAnnotation([FromServices] ClientInfo clientInfo, [FromQuery] Guid annotationId, [FromBody] AnnotationInstanceAddRequest request) + { + if (clientInfo.LoggedUser == null) + { + logger.Warning("ClientInfo has null LoggerUser in [Authorized] controller /annotations"); + return Problem(); + } + + // Take care of - non-admin user requesting not-assigned annotation + // non-existent annotation + try + { + annotationService.AddAnnotationInstance(annotationId, clientInfo.LoggedUser.Id, clientInfo.LoggedUser.Role, request); + return Ok(); + } + catch (InvalidOperationException e) + { + throw new BadRequestException("Could not find specified annotation"); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } + + } } } diff --git a/Backend/Backend/Migrations/20220417114429_V3.Designer.cs b/Backend/Backend/Migrations/20220417114429_V3.Designer.cs new file mode 100644 index 0000000..8601f5d --- /dev/null +++ b/Backend/Backend/Migrations/20220417114429_V3.Designer.cs @@ -0,0 +1,395 @@ +// <auto-generated /> +using System; +using Core.Contexts; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace RestAPI.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20220417114429_V3")] + partial class V3 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("AnnotationClass", b => + { + b.Property<Guid>("AnnotationsId") + .HasColumnType("uuid"); + + b.Property<Guid>("ClassesId") + .HasColumnType("uuid"); + + b.HasKey("AnnotationsId", "ClassesId"); + + b.HasIndex("ClassesId"); + + b.ToTable("AnnotationClass"); + }); + + modelBuilder.Entity("Core.Entities.Annotation", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<DateTime>("DateAssigned") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTime>("DateLastChanged") + .HasColumnType("timestamp with time zone"); + + b.Property<Guid>("DocumentId") + .HasColumnType("uuid"); + + b.Property<string>("Note") + .IsRequired() + .HasColumnType("text"); + + b.Property<int>("State") + .HasColumnType("integer"); + + b.Property<Guid>("UserAssignedId") + .HasColumnType("uuid"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("UserAssignedId"); + + b.HasIndex("UserId"); + + b.ToTable("Annotations"); + }); + + modelBuilder.Entity("Core.Entities.AnnotationTag", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<Guid>("AnnotationId") + .HasColumnType("uuid"); + + b.Property<Guid>("Instance") + .HasColumnType("uuid"); + + b.Property<int>("Length") + .HasColumnType("integer"); + + b.Property<string>("Note") + .IsRequired() + .HasColumnType("text"); + + b.Property<int>("Position") + .HasColumnType("integer"); + + b.Property<Guid?>("SubTagId") + .HasColumnType("uuid"); + + b.Property<Guid>("TagId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AnnotationId"); + + b.HasIndex("SubTagId"); + + b.HasIndex("TagId"); + + b.ToTable("AnnotationTags"); + }); + + modelBuilder.Entity("Core.Entities.Class", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("Color") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Classes"); + }); + + modelBuilder.Entity("Core.Entities.Document", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<Guid?>("ContentId") + .HasColumnType("uuid"); + + b.Property<DateTime>("DateAdded") + .HasColumnType("timestamp with time zone"); + + b.Property<int>("Length") + .HasColumnType("integer"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<int>("RequiredAnnotations") + .HasColumnType("integer"); + + b.Property<Guid?>("UserAddedId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentId"); + + b.HasIndex("UserAddedId"); + + b.ToTable("Documents"); + }); + + modelBuilder.Entity("Core.Entities.DocumentContent", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("Content") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DocumentContents"); + }); + + modelBuilder.Entity("Core.Entities.SubTag", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property<Guid>("TagId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TagId"); + + b.ToTable("SubTags"); + }); + + modelBuilder.Entity("Core.Entities.Tag", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<Guid>("CategoryId") + .HasColumnType("uuid"); + + b.Property<string>("Color") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("Core.Entities.TagCategory", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("Color") + .HasColumnType("text"); + + b.Property<string>("Description") + .HasColumnType("text"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TagCategories"); + }); + + modelBuilder.Entity("Core.Entities.User", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<string>("Password") + .HasColumnType("text"); + + b.Property<int>("Role") + .HasColumnType("integer"); + + b.Property<string>("Surname") + .HasColumnType("text"); + + b.Property<string>("Username") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("AnnotationClass", b => + { + b.HasOne("Core.Entities.Annotation", null) + .WithMany() + .HasForeignKey("AnnotationsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Core.Entities.Class", null) + .WithMany() + .HasForeignKey("ClassesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Core.Entities.Annotation", b => + { + b.HasOne("Core.Entities.Document", "Document") + .WithMany() + .HasForeignKey("DocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Core.Entities.User", "UserAssigned") + .WithMany() + .HasForeignKey("UserAssignedId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Core.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Document"); + + b.Navigation("User"); + + b.Navigation("UserAssigned"); + }); + + modelBuilder.Entity("Core.Entities.AnnotationTag", b => + { + b.HasOne("Core.Entities.Annotation", "Annotation") + .WithMany() + .HasForeignKey("AnnotationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Core.Entities.SubTag", "SubTag") + .WithMany() + .HasForeignKey("SubTagId"); + + b.HasOne("Core.Entities.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Annotation"); + + b.Navigation("SubTag"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Core.Entities.Document", b => + { + b.HasOne("Core.Entities.DocumentContent", "Content") + .WithMany() + .HasForeignKey("ContentId"); + + b.HasOne("Core.Entities.User", "UserAdded") + .WithMany() + .HasForeignKey("UserAddedId"); + + b.Navigation("Content"); + + b.Navigation("UserAdded"); + }); + + modelBuilder.Entity("Core.Entities.SubTag", b => + { + b.HasOne("Core.Entities.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Core.Entities.Tag", b => + { + b.HasOne("Core.Entities.TagCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Backend/Backend/Migrations/20220417114429_V3.cs b/Backend/Backend/Migrations/20220417114429_V3.cs new file mode 100644 index 0000000..aecbffb --- /dev/null +++ b/Backend/Backend/Migrations/20220417114429_V3.cs @@ -0,0 +1,258 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RestAPI.Migrations +{ + public partial class V3 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Documents_DocumentContents_ContentId", + table: "Documents"); + + migrationBuilder.DropForeignKey( + name: "FK_Documents_Users_UserAddedId", + table: "Documents"); + + migrationBuilder.AlterColumn<string>( + name: "Username", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn<string>( + name: "Surname", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn<string>( + name: "Password", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn<string>( + name: "Name", + table: "Users", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn<string>( + name: "Name", + table: "TagCategories", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn<string>( + name: "Description", + table: "TagCategories", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn<string>( + name: "Color", + table: "TagCategories", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn<Guid>( + name: "UserAddedId", + table: "Documents", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn<string>( + name: "Name", + table: "Documents", + type: "text", + nullable: true, + oldClrType: typeof(string), + oldType: "text"); + + migrationBuilder.AlterColumn<Guid>( + name: "ContentId", + table: "Documents", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn<Guid>( + name: "Instance", + table: "AnnotationTags", + type: "uuid", + nullable: false, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AddForeignKey( + name: "FK_Documents_DocumentContents_ContentId", + table: "Documents", + column: "ContentId", + principalTable: "DocumentContents", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_Documents_Users_UserAddedId", + table: "Documents", + column: "UserAddedId", + principalTable: "Users", + principalColumn: "Id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Documents_DocumentContents_ContentId", + table: "Documents"); + + migrationBuilder.DropForeignKey( + name: "FK_Documents_Users_UserAddedId", + table: "Documents"); + + migrationBuilder.AlterColumn<string>( + name: "Username", + table: "Users", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn<string>( + name: "Surname", + table: "Users", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn<string>( + name: "Password", + table: "Users", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn<string>( + name: "Name", + table: "Users", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn<string>( + name: "Name", + table: "TagCategories", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn<string>( + name: "Description", + table: "TagCategories", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn<string>( + name: "Color", + table: "TagCategories", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn<Guid>( + name: "UserAddedId", + table: "Documents", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn<string>( + name: "Name", + table: "Documents", + type: "text", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "text", + oldNullable: true); + + migrationBuilder.AlterColumn<Guid>( + name: "ContentId", + table: "Documents", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn<int>( + name: "Instance", + table: "AnnotationTags", + type: "integer", + nullable: false, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddForeignKey( + name: "FK_Documents_DocumentContents_ContentId", + table: "Documents", + column: "ContentId", + principalTable: "DocumentContents", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Documents_Users_UserAddedId", + table: "Documents", + column: "UserAddedId", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/Backend/Backend/Migrations/DatabaseContextModelSnapshot.cs b/Backend/Backend/Migrations/DatabaseContextModelSnapshot.cs index 489b9ee..5f9ffb3 100644 --- a/Backend/Backend/Migrations/DatabaseContextModelSnapshot.cs +++ b/Backend/Backend/Migrations/DatabaseContextModelSnapshot.cs @@ -85,8 +85,8 @@ namespace RestAPI.Migrations b.Property<Guid>("AnnotationId") .HasColumnType("uuid"); - b.Property<int>("Instance") - .HasColumnType("integer"); + b.Property<Guid>("Instance") + .HasColumnType("uuid"); b.Property<int>("Length") .HasColumnType("integer"); @@ -144,7 +144,7 @@ namespace RestAPI.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); - b.Property<Guid>("ContentId") + b.Property<Guid?>("ContentId") .HasColumnType("uuid"); b.Property<DateTime>("DateAdded") @@ -154,13 +154,12 @@ namespace RestAPI.Migrations .HasColumnType("integer"); b.Property<string>("Name") - .IsRequired() .HasColumnType("text"); b.Property<int>("RequiredAnnotations") .HasColumnType("integer"); - b.Property<Guid>("UserAddedId") + b.Property<Guid?>("UserAddedId") .HasColumnType("uuid"); b.HasKey("Id"); @@ -246,15 +245,12 @@ namespace RestAPI.Migrations .HasColumnType("uuid"); b.Property<string>("Color") - .IsRequired() .HasColumnType("text"); b.Property<string>("Description") - .IsRequired() .HasColumnType("text"); b.Property<string>("Name") - .IsRequired() .HasColumnType("text"); b.HasKey("Id"); @@ -269,22 +265,18 @@ namespace RestAPI.Migrations .HasColumnType("uuid"); b.Property<string>("Name") - .IsRequired() .HasColumnType("text"); b.Property<string>("Password") - .IsRequired() .HasColumnType("text"); b.Property<int>("Role") .HasColumnType("integer"); b.Property<string>("Surname") - .IsRequired() .HasColumnType("text"); b.Property<string>("Username") - .IsRequired() .HasColumnType("text"); b.HasKey("Id"); @@ -363,15 +355,11 @@ namespace RestAPI.Migrations { b.HasOne("Core.Entities.DocumentContent", "Content") .WithMany() - .HasForeignKey("ContentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("ContentId"); b.HasOne("Core.Entities.User", "UserAdded") .WithMany() - .HasForeignKey("UserAddedId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("UserAddedId"); b.Navigation("Content"); diff --git a/Backend/Backend/Program.cs b/Backend/Backend/Program.cs index 89b3219..4380358 100644 --- a/Backend/Backend/Program.cs +++ b/Backend/Backend/Program.cs @@ -113,6 +113,8 @@ using (var scope = app.Services.CreateScope()) // In development we seed dummy data if (app.Environment.IsDevelopment()) { + //context.Database.EnsureDeleted(); + //context.Database.EnsureCreated(); context.Database.Migrate(); Seeder.SeedDevelopment(context); } diff --git a/Backend/Core/Entities/AnnotationTag.cs b/Backend/Core/Entities/AnnotationTag.cs index 9241c44..798b090 100644 --- a/Backend/Core/Entities/AnnotationTag.cs +++ b/Backend/Core/Entities/AnnotationTag.cs @@ -17,7 +17,7 @@ namespace Core.Entities /** Nullable for optional */ public SubTag? SubTag { get; set; } - public int Instance { get; set; } + public Guid Instance { get; set; } public string Note { get; set; } public int Position { get; set; } public int Length { get; set; } diff --git a/Backend/Core/Seeding/Seeder.cs b/Backend/Core/Seeding/Seeder.cs index 3e1a7df..7fe2795 100644 --- a/Backend/Core/Seeding/Seeder.cs +++ b/Backend/Core/Seeding/Seeder.cs @@ -36,7 +36,7 @@ namespace Core.Seeding AnnotationTag at = new AnnotationTag() { Annotation = annotation, - Instance = 1, + Instance = Guid.NewGuid(), Length = 10, Position = 0, SubTag = subtag, diff --git a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs index 3cde9ae..e71f215 100644 --- a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs +++ b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs @@ -184,6 +184,10 @@ namespace Core.Services.AnnotationService HtmlSanitizer sanitizer = new HtmlSanitizer(); sanitizer.AllowedAttributes.Clear(); sanitizer.AllowedAttributes.Add(idAttributeName); + if (sanitizer.AllowedTags.Contains("script")) + { + sanitizer.AllowedTags.Remove("script"); + } docToRender = sanitizer.Sanitize(docToRender); @@ -195,5 +199,49 @@ namespace Core.Services.AnnotationService { return text.Contains("<!DOCTYPE html>"); } + + public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request) + { + var annotation = context.Annotations + .Where(a => a.Id == annotationId) + .Include(a => a.User) + .Include(a => a.Document).ThenInclude(d => d.Content) + .First(); + + if (userRole < ERole.ADMINISTRATOR) + { + if (annotation.User.Id != userId) + { + throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}"); + } + } + + AnnotationTag annotationTag = new() + { + Annotation = annotation, + Instance = request.InstanceId == null ? Guid.NewGuid() : request.InstanceId.Value, + Length = request.Length, + Position = request.Position, + Note = "" + }; + + if (request.Type == ETagType.TAG) + { + annotationTag.Tag = context.Tags.Where(t => t.Id == request.Id).Single(); + annotationTag.SubTag = null; + } + else if (request.Type == ETagType.SUBTAG) + { + var subTag = context.SubTags.Where(st => st.Id == request.Id).Include(st => st.Tag).Single(); + annotationTag.SubTag = subTag; + annotationTag.Tag = subTag.Tag; + } + else + { + throw new ArgumentException($"Unknown tag type {request.Type}"); + } + + context.SaveChanges(); + } } } diff --git a/Backend/Core/Services/AnnotationService/IAnnotationService.cs b/Backend/Core/Services/AnnotationService/IAnnotationService.cs index afb584d..5946e9c 100644 --- a/Backend/Core/Services/AnnotationService/IAnnotationService.cs +++ b/Backend/Core/Services/AnnotationService/IAnnotationService.cs @@ -13,5 +13,7 @@ namespace Core.Services.AnnotationService public void CreateDocumentAnnotations(AnnotationsAddRequest request, Guid userId); public AnnotationListResponse GetUserAnnotations(Guid userId); public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole); + public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request); + } } diff --git a/Backend/Models/Annotations/AnnotationInstanceAddRequest.cs b/Backend/Models/Annotations/AnnotationInstanceAddRequest.cs new file mode 100644 index 0000000..a56ff8f --- /dev/null +++ b/Backend/Models/Annotations/AnnotationInstanceAddRequest.cs @@ -0,0 +1,20 @@ +using Models.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Models.Annotations +{ + public class AnnotationInstanceAddRequest + { + public int Position { get; set; } + public int Length { get; set; } + public ETagType Type { get; set; } + public Guid Id { get; set; } + + // If se to null, the instance is a new one + public Guid? InstanceId { get; set; } + } +} diff --git a/Backend/Models/Enums/ETagType.cs b/Backend/Models/Enums/ETagType.cs new file mode 100644 index 0000000..18686de --- /dev/null +++ b/Backend/Models/Enums/ETagType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Models.Enums +{ + public enum ETagType + { + TAG, + SUBTAG + } +} diff --git a/Backend/Models/Tags/TagInstanceInfo.cs b/Backend/Models/Tags/TagInstanceInfo.cs index 204a690..3c5cbcc 100644 --- a/Backend/Models/Tags/TagInstanceInfo.cs +++ b/Backend/Models/Tags/TagInstanceInfo.cs @@ -15,7 +15,7 @@ namespace Models.Tags public string? SubTagName { get; set; } public Guid? SubTagId { get; set; } - public int Instance { get; set; } + public Guid Instance { get; set; } public int Position { get; set; } public int Length { get; set; } -- GitLab From d08c4fb0cdf4588a7bcf1773ff5f8401bb8c8d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com> Date: Sun, 17 Apr 2022 14:45:46 +0200 Subject: [PATCH 03/12] Fixed error in AnnotationController --- Backend/Backend/Controllers/AnnotationController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs index ccfe949..a187467 100644 --- a/Backend/Backend/Controllers/AnnotationController.cs +++ b/Backend/Backend/Controllers/AnnotationController.cs @@ -77,7 +77,7 @@ namespace RestAPI.Controllers [HttpPost("/annotation/{annotationId}")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.Forbidden)] - public ActionResult PostAnnotation([FromServices] ClientInfo clientInfo, [FromQuery] Guid annotationId, [FromBody] AnnotationInstanceAddRequest request) + public ActionResult PostAnnotation([FromServices] ClientInfo clientInfo, Guid annotationId, [FromBody] AnnotationInstanceAddRequest request) { if (clientInfo.LoggedUser == null) { -- GitLab From abf94f214dc29530a3d07cba63e5673ff6094a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz> Date: Sun, 17 Apr 2022 15:23:41 +0200 Subject: [PATCH 04/12] Backend - saving data fixed --- Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs index e71f215..c97e009 100644 --- a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs +++ b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs @@ -241,6 +241,7 @@ namespace Core.Services.AnnotationService throw new ArgumentException($"Unknown tag type {request.Type}"); } + context.AnnotationTags.Add(annotationTag); context.SaveChanges(); } } -- GitLab From 4bc995912c81ebe8ddf01a440fb51bb09d4f816e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz> Date: Sun, 17 Apr 2022 15:24:42 +0200 Subject: [PATCH 05/12] Adding tag/subtag to selected text --- webapp/api/api.ts | 125 +++++++++++++++++- .../components/annotation/AnnotationItem.tsx | 7 +- .../annotation/DocumentAnnotationView.tsx | 60 --------- webapp/components/types/tag.tsx | 10 +- webapp/contexts/AnnotationContext.tsx | 69 ++++++++-- webapp/contexts/TagCategoryContext.tsx | 14 +- webapp/pages/annotation/[id].tsx | 28 ++-- webapp/utils/selectionUtils.ts | 88 ++++++++++++ 8 files changed, 315 insertions(+), 86 deletions(-) create mode 100644 webapp/utils/selectionUtils.ts diff --git a/webapp/api/api.ts b/webapp/api/api.ts index 3478280..b26d5f3 100644 --- a/webapp/api/api.ts +++ b/webapp/api/api.ts @@ -76,6 +76,43 @@ export interface AnnotationInfo { */ 'tagInstances'?: Array<TagInstanceInfo> | null; } +/** + * + * @export + * @interface AnnotationInstanceAddRequest + */ +export interface AnnotationInstanceAddRequest { + /** + * + * @type {number} + * @memberof AnnotationInstanceAddRequest + */ + 'position'?: number; + /** + * + * @type {number} + * @memberof AnnotationInstanceAddRequest + */ + 'length'?: number; + /** + * + * @type {ETagType} + * @memberof AnnotationInstanceAddRequest + */ + 'type'?: ETagType; + /** + * + * @type {string} + * @memberof AnnotationInstanceAddRequest + */ + 'id'?: string; + /** + * + * @type {string} + * @memberof AnnotationInstanceAddRequest + */ + 'instanceId'?: string | null; +} /** * * @export @@ -321,6 +358,20 @@ export const EState = { export type EState = typeof EState[keyof typeof EState]; +/** + * + * @export + * @enum {string} + */ + +export const ETagType = { + Tag: 'TAG', + Subtag: 'SUBTAG' +} as const; + +export type ETagType = typeof ETagType[keyof typeof ETagType]; + + /** * * @export @@ -553,10 +604,10 @@ export interface TagInstanceInfo { 'subTagId'?: string | null; /** * - * @type {number} + * @type {string} * @memberof TagInstanceInfo */ - 'instance'?: number; + 'instance'?: string; /** * * @type {number} @@ -710,6 +761,43 @@ export const AnnotationApiAxiosParamCreator = function (configuration?: Configur options: localVarRequestOptions, }; }, + /** + * + * @param {string} annotationId + * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + annotationAnnotationIdPost: async (annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { + // verify required parameter 'annotationId' is not null or undefined + assertParamExists('annotationAnnotationIdPost', 'annotationId', annotationId) + const localVarPath = `/annotation/{annotationId}` + .replace(`{${"annotationId"}}`, encodeURIComponent(String(annotationId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(annotationInstanceAddRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {AnnotationsAddRequest} [annotationsAddRequest] @@ -763,6 +851,17 @@ export const AnnotationApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdGet(annotationId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} annotationId + * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async annotationAnnotationIdPost(annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdPost(annotationId, annotationInstanceAddRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {AnnotationsAddRequest} [annotationsAddRequest] @@ -792,6 +891,16 @@ export const AnnotationApiFactory = function (configuration?: Configuration, bas annotationAnnotationIdGet(annotationId: string, options?: any): AxiosPromise<AnnotationInfo> { return localVarFp.annotationAnnotationIdGet(annotationId, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {string} annotationId + * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + annotationAnnotationIdPost(annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options?: any): AxiosPromise<void> { + return localVarFp.annotationAnnotationIdPost(annotationId, annotationInstanceAddRequest, options).then((request) => request(axios, basePath)); + }, /** * * @param {AnnotationsAddRequest} [annotationsAddRequest] @@ -822,6 +931,18 @@ export class AnnotationApi extends BaseAPI { return AnnotationApiFp(this.configuration).annotationAnnotationIdGet(annotationId, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {string} annotationId + * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AnnotationApi + */ + public annotationAnnotationIdPost(annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options?: AxiosRequestConfig) { + return AnnotationApiFp(this.configuration).annotationAnnotationIdPost(annotationId, annotationInstanceAddRequest, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {AnnotationsAddRequest} [annotationsAddRequest] diff --git a/webapp/components/annotation/AnnotationItem.tsx b/webapp/components/annotation/AnnotationItem.tsx index 7dfef34..d67a4bc 100644 --- a/webapp/components/annotation/AnnotationItem.tsx +++ b/webapp/components/annotation/AnnotationItem.tsx @@ -19,6 +19,8 @@ import { TagInstanceInfo } from '../../api'; * @returns The item that represents an annotation. */ export function AnnotationItem(props: { tag: Tag }) { + const { markSelectedText } = useContext(AnnotationContext); + /** * Should properties of this annotation be visible? */ @@ -88,7 +90,10 @@ export function AnnotationItem(props: { tag: Tag }) { <Col sm="auto" className="d-flex align-items-center"> <TagOutlined /> </Col> - <Col className="d-flex align-items-center">{props.tag.name}</Col> + <Col className="d-flex align-items-center"> + {props.tag.tagName} + {props.tag.subtagName ? ' (' + props.tag.subtagName + ')' : ''} + </Col> <Col sm="auto"> <Button type="text" diff --git a/webapp/components/annotation/DocumentAnnotationView.tsx b/webapp/components/annotation/DocumentAnnotationView.tsx index 8c2090e..f55fe05 100644 --- a/webapp/components/annotation/DocumentAnnotationView.tsx +++ b/webapp/components/annotation/DocumentAnnotationView.tsx @@ -15,66 +15,6 @@ export function DocumentAnnotationView() { return ( <div> - <Button - onClick={() => { - const selection = window.getSelection(); - if (!selection) { - return; - } - if (!annotation?.tagStartPositions || !annotation.tagLengths) { - console.log('start or lengths not found'); - return; - } - - let startTag = selection.anchorNode; - let endTag = selection.focusNode; - - if (!startTag || !endTag) { - console.log('Selection not found'); - return; - } - - if (startTag.nodeName.includes('#')) { - startTag = startTag.parentNode; - } - if (endTag.nodeName.includes('#')) { - endTag = endTag.parentNode; - } - if (!startTag || !endTag) { - console.log('Selection element not found'); - return; - } - - if (!(startTag instanceof Element)) { - console.log('StartTag is not instance of Element'); - return; - } - if (!(endTag instanceof Element)) { - console.log('EndTag is not instance of Element'); - return; - } - - const startElement = startTag as Element; - const endElement = endTag as Element; - - const startId: number = - Number(startElement.getAttribute('aswi-tag-id')) ?? -1; - const endId: number = - Number(endElement.getAttribute('aswi-tag-id')) ?? -1; - - const startPosition = - annotation.tagStartPositions[startId] + - annotation.tagLengths[startId] + - selection.anchorOffset; - const endPosition = - annotation.tagStartPositions[endId] + - annotation.tagLengths[endId] + - selection.focusOffset - - 1; - }} - > - Test - </Button> <div dangerouslySetInnerHTML={{ __html: annotation.documentToRender ?? '' }} /> diff --git a/webapp/components/types/tag.tsx b/webapp/components/types/tag.tsx index 62c23b8..d0a04aa 100644 --- a/webapp/components/types/tag.tsx +++ b/webapp/components/types/tag.tsx @@ -1,11 +1,17 @@ -import { TagInstanceInfo } from '../../api'; +import { SubTagInfo, TagInfo, TagInstanceInfo } from '../../api'; /** * Special tag used in annotation panel. */ export type Tag = { - name: string; + tagName: string; + subtagName: string | null; + category: string; visible: boolean; occurrences: TagInstanceInfo[]; + + tagId: string; + subtagId: string | null; + instanceId: string; }; diff --git a/webapp/contexts/AnnotationContext.tsx b/webapp/contexts/AnnotationContext.tsx index 74f28e4..502f1b6 100644 --- a/webapp/contexts/AnnotationContext.tsx +++ b/webapp/contexts/AnnotationContext.tsx @@ -1,7 +1,8 @@ import React, { createContext, useEffect, useState } from 'react'; -import { AnnotationInfo, TagInstanceInfo } from '../api'; +import { AnnotationInfo, ETagType, SubTagInfo, TagInfo, TagInstanceInfo } from '../api'; import { Tag } from '../components/types/tag'; import { annotationController } from '../controllers'; +import { GetSelectionInfo } from '../utils/selectionUtils'; /** * Interface of an annotation context provider. @@ -53,6 +54,12 @@ interface IAnnotationContextProvider { annotation: AnnotationInfo | null; mappedTags: Tag[] | null; refreshAnnotation: () => void; + + markSelectedText: ( + tagId: string, + subtagId: string | null, + instanceID: string | null + ) => void; } /** @@ -119,6 +126,10 @@ export const AnnotationContext = createContext<IAnnotationContextProvider>({ refreshAnnotation: () => { return; }, + + markSelectedText: () => { + return; + }, }); /** @@ -139,12 +150,49 @@ const AnnotationProvider = (props: { const [mappedTags, setMappedTags] = useState<Tag[] | null>(null); + async function markSelectedText( + tagId: string, + subtagId: string | null, + instanceId: string | null + ) { + if (!annotation) { + console.log('annotation not found'); + return; + } + + const selectionInfo = GetSelectionInfo(annotation); + if (!selectionInfo) { + console.log( + 'not able to continue, selection processing not completed successfully' + ); + return; + } + + const id = subtagId ?? tagId; + const type: ETagType = subtagId == null ? ETagType.Tag : ETagType.Subtag; + + const res = await annotationController.annotationAnnotationIdPost( + props.annotationId, + { + id: id, + instanceId, + type, + position: selectionInfo.startPositionOriginalDocument, + length: selectionInfo.selectionLengthOriginalDocument, + } + ); + console.log('res'); + console.log(res); + + await refreshAnnotation(); + } + /** * Default implementation of addOccurrence method. * @param tag The tag with new occurrence. */ - const addOccurrence = (tag: Tag) => { - //TODO: Implement method (should use objects from server API) + const addOccurrence = async (tag: Tag) => { + await markSelectedText(tag.tagId, tag.subtagId ?? null, tag.instanceId); }; /** @@ -182,17 +230,21 @@ const AnnotationProvider = (props: { }; const remapAnnotations = (data: AnnotationInfo) => { - let map = new Map<number, Tag>(); + let map = new Map<string, Tag>(); data.tagInstances?.forEach((tagInstance) => { - if (map.has(tagInstance.instance ?? 0)) { - let tag = map.get(tagInstance.instance ?? 0); + if (map.has(tagInstance.instance ?? '-')) { + let tag = map.get(tagInstance.instance ?? '-'); tag!.occurrences = [...tag!.occurrences, tagInstance]; } else { - map.set(tagInstance.position ?? 0, { - name: tagInstance.tagName ?? '', + map.set(tagInstance.instance ?? '-', { + tagName: tagInstance.tagName ?? '', + subtagName: tagInstance.subTagName ?? null, category: tagInstance.tagCategoryName ?? '', visible: true, occurrences: [tagInstance], + tagId: tagInstance.tagId ?? '', + instanceId: tagInstance.instance ?? '', + subtagId: tagInstance.subTagId ?? null, }); } }); @@ -229,6 +281,7 @@ const AnnotationProvider = (props: { refreshAnnotation, annotation, mappedTags, + markSelectedText, }} > {props.children} diff --git a/webapp/contexts/TagCategoryContext.tsx b/webapp/contexts/TagCategoryContext.tsx index 315149b..f2b4ab3 100644 --- a/webapp/contexts/TagCategoryContext.tsx +++ b/webapp/contexts/TagCategoryContext.tsx @@ -1,6 +1,9 @@ -import React, { createContext, useEffect, useState } from 'react'; +import React, { createContext, useContext, useEffect, useState } from 'react'; import { SubTagInfo, TagCategoryInfo, TagInfo } from '../api'; import { tagController } from '../controllers'; +import { LoggedUserContext } from './LoggedUserContext'; +import Annotation from '../pages/annotation/[id]'; +import { AnnotationContext } from './AnnotationContext'; /** * Interface of a tag context provider. @@ -78,6 +81,8 @@ export const TagCategoryContext = createContext<ITagCategoryContextProvider>({ * @returns Prepared html of the provider. */ const TagCategoryProvider = (props: { children: React.ReactNode }) => { + const { markSelectedText } = useContext(AnnotationContext); + /** * Tags managed by the context. */ @@ -99,6 +104,13 @@ const TagCategoryProvider = (props: { children: React.ReactNode }) => { const selectTag = (tag: TagInfo, subTag: SubTagInfo | null) => { setSelectedTag(tag); setSelectedSubTag(subTag); + + if (!tag.id) { + console.log('invalid selection'); + return; + } + + markSelectedText(tag.id, subTag?.id ?? null, null); }; /** diff --git a/webapp/pages/annotation/[id].tsx b/webapp/pages/annotation/[id].tsx index dec1dfe..80afdcf 100644 --- a/webapp/pages/annotation/[id].tsx +++ b/webapp/pages/annotation/[id].tsx @@ -5,6 +5,8 @@ import { MainLayout } from '../../layouts/MainLayout'; import 'antd/dist/antd.css'; import styles from '/styles/Annotation.module.scss'; import AnnotationProvider from '../../contexts/AnnotationContext'; +import TagCategoryProvider from '../../contexts/TagCategoryContext'; + import { useRouter } from 'next/router'; /** @@ -27,19 +29,21 @@ function Annotation() { return ( <AnnotationProvider annotationId={annotationId}> - <MainLayout> - <div className={styles.layoutWrapper}> - <div className={styles.tags}> - <TagPanel /> - </div> - <div className={styles.document}> - <DocumentAnnotationView /> - </div> - <div className={styles.annotations}> - <AnnotationPanel /> + <TagCategoryProvider> + <MainLayout> + <div className={styles.layoutWrapper}> + <div className={styles.tags}> + <TagPanel /> + </div> + <div className={styles.document}> + <DocumentAnnotationView /> + </div> + <div className={styles.annotations}> + <AnnotationPanel /> + </div> </div> - </div> - </MainLayout> + </MainLayout> + </TagCategoryProvider> </AnnotationProvider> ); } diff --git a/webapp/utils/selectionUtils.ts b/webapp/utils/selectionUtils.ts new file mode 100644 index 0000000..8f7e990 --- /dev/null +++ b/webapp/utils/selectionUtils.ts @@ -0,0 +1,88 @@ +import { AnnotationInfo } from '../api'; + +export interface SelectionInfo { + startElementId: number; + endElementId: number; + startPositionOriginalDocument: number; + endPositionOriginalDocument: number; + selectionLengthOriginalDocument: number; +} +export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | null { + const selection = window.getSelection(); + if (!selection) { + return null; + } + if (!annotation?.tagStartPositions || !annotation.tagLengths) { + console.log('start or lengths not found'); + return null; + } + + let startTag = selection.anchorNode; + let endTag = selection.focusNode; + + if (!startTag || !endTag) { + console.log('Selection not found'); + return null; + } + + if (startTag.nodeName.includes('#')) { + startTag = startTag.parentNode; + } + if (endTag.nodeName.includes('#')) { + endTag = endTag.parentNode; + } + if (!startTag || !endTag) { + console.log('Selection element not found'); + return null; + } + + if (!(startTag instanceof Element)) { + console.log('StartTag is not instance of Element'); + return null; + } + if (!(endTag instanceof Element)) { + console.log('EndTag is not instance of Element'); + return null; + } + + let startElement = startTag as Element; + let endElement = endTag as Element; + + let startId: number = Number(startElement.getAttribute('aswi-tag-id')) ?? -1; + let endId: number = Number(endElement.getAttribute('aswi-tag-id')) ?? -1; + + let startPosition = + annotation.tagStartPositions[startId] + + annotation.tagLengths[startId] + + selection.anchorOffset; + let endPosition = + annotation.tagStartPositions[endId] + + annotation.tagLengths[endId] + + selection.focusOffset - + 1; + + // need to switch start and end elements (selection was in the opposite way then expected) + if (startPosition > endPosition) { + let temp = startPosition; + startPosition = endPosition; + endPosition = temp; + + temp = startId; + startId = endId; + endId = temp; + + const tempElement = startElement; + startElement = endElement; + endElement = tempElement; + } + + const length = endPosition - startPosition + 1; + + return { + endElementId: endId, + startElementId: startId, + startPositionOriginalDocument: startPosition, + endPositionOriginalDocument: endPosition, + selectionLengthOriginalDocument: length, + }; +} -- GitLab From 0f8d6304152d20511d43d066a93a1e8b29537e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com> Date: Sun, 17 Apr 2022 15:41:37 +0200 Subject: [PATCH 06/12] Endpoint for annotation instance deletion - untested --- .../Controllers/AnnotationController.cs | 29 ++++++++++++++ .../AnnotationService/AnnotationServiceEF.cs | 38 +++++++++++++++++++ .../AnnotationService/IAnnotationService.cs | 1 + Backend/Models/Tags/TagInstanceInfo.cs | 2 + 4 files changed, 70 insertions(+) diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs index a187467..201468b 100644 --- a/Backend/Backend/Controllers/AnnotationController.cs +++ b/Backend/Backend/Controllers/AnnotationController.cs @@ -102,5 +102,34 @@ namespace RestAPI.Controllers } } + + [HttpDelete("/annotation/{annotationId}/{tagInstanceId}")] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.Forbidden)] + public ActionResult DeleteAnnotationInstance([FromServices] ClientInfo clientInfo, Guid annotationId, Guid tagInstanceId) + { + if (clientInfo.LoggedUser == null) + { + logger.Warning("ClientInfo has null LoggerUser in [Authorized] controller /annotations"); + return Problem(); + } + + // Take care of - non-admin user requesting not-assigned annotation + // non-existent annotation + try + { + annotationService.DeleteAnnotationInstance(annotationId, tagInstanceId, clientInfo.LoggedUser.Id, clientInfo.LoggedUser.Role); + return Ok(); + } + catch (InvalidOperationException e) + { + throw new BadRequestException("Could not find specified annotation"); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } + + } } } diff --git a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs index c97e009..e3d929a 100644 --- a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs +++ b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs @@ -244,5 +244,43 @@ namespace Core.Services.AnnotationService context.AnnotationTags.Add(annotationTag); context.SaveChanges(); } + + public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole) + { + Annotation annotation = null; + try + { + annotation = context.Annotations + .Where(a => a.Id == annotationId) + .Include(a => a.User) + .Include(a => a.Document).ThenInclude(d => d.Content) + .First(); + + } + catch (Exception ex) + { + throw new InvalidOperationException("Could not find annotation"); + } + + + if (userRole < ERole.ADMINISTRATOR) + { + if (annotation.User.Id != loggedUserId) + { + throw new UnauthorizedAccessException($"User {loggedUserId} does not have assigned annotation {annotationId}"); + } + } + + if (!context.AnnotationTags.Any(at => at.Id == tagInstanceId)) + { + throw new InvalidOperationException("Could not find tag instance"); + } + + context.AnnotationTags + .Where(at => at.Id == tagInstanceId).ToList() + .ForEach(a => context.AnnotationTags.Remove(a)); + + context.SaveChanges(); + } } } diff --git a/Backend/Core/Services/AnnotationService/IAnnotationService.cs b/Backend/Core/Services/AnnotationService/IAnnotationService.cs index 5946e9c..07dddb1 100644 --- a/Backend/Core/Services/AnnotationService/IAnnotationService.cs +++ b/Backend/Core/Services/AnnotationService/IAnnotationService.cs @@ -14,6 +14,7 @@ namespace Core.Services.AnnotationService public AnnotationListResponse GetUserAnnotations(Guid userId); public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole); public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request); + public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole); } } diff --git a/Backend/Models/Tags/TagInstanceInfo.cs b/Backend/Models/Tags/TagInstanceInfo.cs index 3c5cbcc..cbbc4e6 100644 --- a/Backend/Models/Tags/TagInstanceInfo.cs +++ b/Backend/Models/Tags/TagInstanceInfo.cs @@ -8,6 +8,8 @@ namespace Models.Tags { public class TagInstanceInfo { + /** For database */ + public Guid Id { get; set; } public string TagName { get; set; } public Guid TagId { get; set; } public string TagCategoryName { get; set; } -- GitLab From 2ebf32d0c9142b907dc53a55a3de62ecf8ed8675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com> Date: Sun, 17 Apr 2022 15:49:20 +0200 Subject: [PATCH 07/12] Changed Id to OccurenceId in TagInstanceInfo --- Backend/Core/MapperProfiles/TagProfileEF.cs | 3 ++- Backend/Models/Tags/TagInstanceInfo.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Backend/Core/MapperProfiles/TagProfileEF.cs b/Backend/Core/MapperProfiles/TagProfileEF.cs index dcc4e0e..4095ddd 100644 --- a/Backend/Core/MapperProfiles/TagProfileEF.cs +++ b/Backend/Core/MapperProfiles/TagProfileEF.cs @@ -16,7 +16,8 @@ namespace Core.MapperProfiles CreateMap<TagCategory, TagCategoryInfo>(); CreateMap<Tag, TagInfo>(); CreateMap<SubTag, SubTagInfo>(); - CreateMap<AnnotationTag, TagInstanceInfo>(); + CreateMap<AnnotationTag, TagInstanceInfo>() + .ForMember(ti => ti.OccurenceId, opt => opt.MapFrom(at => at.Id)); } } } diff --git a/Backend/Models/Tags/TagInstanceInfo.cs b/Backend/Models/Tags/TagInstanceInfo.cs index cbbc4e6..788893d 100644 --- a/Backend/Models/Tags/TagInstanceInfo.cs +++ b/Backend/Models/Tags/TagInstanceInfo.cs @@ -9,7 +9,7 @@ namespace Models.Tags public class TagInstanceInfo { /** For database */ - public Guid Id { get; set; } + public Guid OccurenceId { get; set; } public string TagName { get; set; } public Guid TagId { get; set; } public string TagCategoryName { get; set; } -- GitLab From 77667f3fd5f7248264977a3aed8e40f26f88f546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com> Date: Sun, 17 Apr 2022 15:51:28 +0200 Subject: [PATCH 08/12] Minor rename --- Backend/Backend/Controllers/AnnotationController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs index 201468b..d4b9728 100644 --- a/Backend/Backend/Controllers/AnnotationController.cs +++ b/Backend/Backend/Controllers/AnnotationController.cs @@ -106,7 +106,7 @@ namespace RestAPI.Controllers [HttpDelete("/annotation/{annotationId}/{tagInstanceId}")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.Forbidden)] - public ActionResult DeleteAnnotationInstance([FromServices] ClientInfo clientInfo, Guid annotationId, Guid tagInstanceId) + public ActionResult DeleteAnnotationInstance([FromServices] ClientInfo clientInfo, Guid annotationId, Guid occurenceId) { if (clientInfo.LoggedUser == null) { @@ -118,7 +118,7 @@ namespace RestAPI.Controllers // non-existent annotation try { - annotationService.DeleteAnnotationInstance(annotationId, tagInstanceId, clientInfo.LoggedUser.Id, clientInfo.LoggedUser.Role); + annotationService.DeleteAnnotationInstance(annotationId, occurenceId, clientInfo.LoggedUser.Id, clientInfo.LoggedUser.Role); return Ok(); } catch (InvalidOperationException e) -- GitLab From 8316c10270b5cd14b98b769770c2c5d65a4da46c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com> Date: Sun, 17 Apr 2022 15:52:40 +0200 Subject: [PATCH 09/12] Last fix --- Backend/Backend/Controllers/AnnotationController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs index d4b9728..e6e1f98 100644 --- a/Backend/Backend/Controllers/AnnotationController.cs +++ b/Backend/Backend/Controllers/AnnotationController.cs @@ -103,7 +103,7 @@ namespace RestAPI.Controllers } - [HttpDelete("/annotation/{annotationId}/{tagInstanceId}")] + [HttpDelete("/annotation/{annotationId}/{occurenceId}")] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.Forbidden)] public ActionResult DeleteAnnotationInstance([FromServices] ClientInfo clientInfo, Guid annotationId, Guid occurenceId) -- GitLab From 3f66b62447a126f02af249db079b144a2313d9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz> Date: Sun, 17 Apr 2022 15:47:59 +0200 Subject: [PATCH 10/12] Comments added to selection utils --- webapp/utils/selectionUtils.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/webapp/utils/selectionUtils.ts b/webapp/utils/selectionUtils.ts index 8f7e990..ce0737a 100644 --- a/webapp/utils/selectionUtils.ts +++ b/webapp/utils/selectionUtils.ts @@ -7,18 +7,22 @@ export interface SelectionInfo { endPositionOriginalDocument: number; selectionLengthOriginalDocument: number; } +const idAttributeName = 'aswi-tag-id'; + export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | null { const selection = window.getSelection(); if (!selection) { return null; } + if (!annotation?.tagStartPositions || !annotation.tagLengths) { + // invalid data provided from API console.log('start or lengths not found'); return null; } - let startTag = selection.anchorNode; - let endTag = selection.focusNode; + let startTag = selection.anchorNode; // anchor node = node where selection started + let endTag = selection.focusNode; // focus node = node where selection ended if (!startTag || !endTag) { console.log('Selection not found'); @@ -26,11 +30,14 @@ export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | nu } if (startTag.nodeName.includes('#')) { + // it is not an element (usually #text) startTag = startTag.parentNode; } if (endTag.nodeName.includes('#')) { + // it is not an element (usually #text) endTag = endTag.parentNode; } + if (!startTag || !endTag) { console.log('Selection element not found'); return null; @@ -48,8 +55,8 @@ export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | nu let startElement = startTag as Element; let endElement = endTag as Element; - let startId: number = Number(startElement.getAttribute('aswi-tag-id')) ?? -1; - let endId: number = Number(endElement.getAttribute('aswi-tag-id')) ?? -1; + let startId: number = Number(startElement.getAttribute(idAttributeName)) ?? -1; + let endId: number = Number(endElement.getAttribute(idAttributeName)) ?? -1; let startPosition = annotation.tagStartPositions[startId] + -- GitLab From 9fdb7d556f0aeb823465803942d00d212085ce70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz> Date: Sun, 17 Apr 2022 15:56:45 +0200 Subject: [PATCH 11/12] Deleting occurences --- webapp/api/api.ts | 76 +++++++++++++++++++++++++++ webapp/contexts/AnnotationContext.tsx | 17 ++++-- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/webapp/api/api.ts b/webapp/api/api.ts index b26d5f3..89c8e56 100644 --- a/webapp/api/api.ts +++ b/webapp/api/api.ts @@ -566,6 +566,12 @@ export interface TagInfo { * @interface TagInstanceInfo */ export interface TagInstanceInfo { + /** + * + * @type {string} + * @memberof TagInstanceInfo + */ + 'occurenceId'?: string; /** * * @type {string} @@ -752,6 +758,43 @@ export const AnnotationApiAxiosParamCreator = function (configuration?: Configur + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} annotationId + * @param {string} occurenceId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + annotationAnnotationIdOccurenceIdDelete: async (annotationId: string, occurenceId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => { + // verify required parameter 'annotationId' is not null or undefined + assertParamExists('annotationAnnotationIdOccurenceIdDelete', 'annotationId', annotationId) + // verify required parameter 'occurenceId' is not null or undefined + assertParamExists('annotationAnnotationIdOccurenceIdDelete', 'occurenceId', occurenceId) + const localVarPath = `/annotation/{annotationId}/{occurenceId}` + .replace(`{${"annotationId"}}`, encodeURIComponent(String(annotationId))) + .replace(`{${"occurenceId"}}`, encodeURIComponent(String(occurenceId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -851,6 +894,17 @@ export const AnnotationApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdGet(annotationId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} annotationId + * @param {string} occurenceId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async annotationAnnotationIdOccurenceIdDelete(annotationId: string, occurenceId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdOccurenceIdDelete(annotationId, occurenceId, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} annotationId @@ -891,6 +945,16 @@ export const AnnotationApiFactory = function (configuration?: Configuration, bas annotationAnnotationIdGet(annotationId: string, options?: any): AxiosPromise<AnnotationInfo> { return localVarFp.annotationAnnotationIdGet(annotationId, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {string} annotationId + * @param {string} occurenceId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + annotationAnnotationIdOccurenceIdDelete(annotationId: string, occurenceId: string, options?: any): AxiosPromise<void> { + return localVarFp.annotationAnnotationIdOccurenceIdDelete(annotationId, occurenceId, options).then((request) => request(axios, basePath)); + }, /** * * @param {string} annotationId @@ -931,6 +995,18 @@ export class AnnotationApi extends BaseAPI { return AnnotationApiFp(this.configuration).annotationAnnotationIdGet(annotationId, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {string} annotationId + * @param {string} occurenceId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AnnotationApi + */ + public annotationAnnotationIdOccurenceIdDelete(annotationId: string, occurenceId: string, options?: AxiosRequestConfig) { + return AnnotationApiFp(this.configuration).annotationAnnotationIdOccurenceIdDelete(annotationId, occurenceId, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {string} annotationId diff --git a/webapp/contexts/AnnotationContext.tsx b/webapp/contexts/AnnotationContext.tsx index 502f1b6..b3be87f 100644 --- a/webapp/contexts/AnnotationContext.tsx +++ b/webapp/contexts/AnnotationContext.tsx @@ -181,8 +181,6 @@ const AnnotationProvider = (props: { length: selectionInfo.selectionLengthOriginalDocument, } ); - console.log('res'); - console.log(res); await refreshAnnotation(); } @@ -207,8 +205,19 @@ const AnnotationProvider = (props: { * Deletes an occurrence of an annotation. * @param occurrence Occurrence that should be deleted. */ - const deleteOccurrence = (occurrence: TagInstanceInfo) => { - //TODO: Implement method (should use objects from server API) + const deleteOccurrence = async (occurrence: TagInstanceInfo) => { + if (!occurrence.occurenceId) { + console.log('invalid occurrence'); + return; + } + + const deleteRes = + await annotationController.annotationAnnotationIdOccurenceIdDelete( + props.annotationId, + occurrence.occurenceId + ); + + await refreshAnnotation(); }; /** -- GitLab From 51f70e003068d8db4ce5a952ec9fa3329e39de19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz> Date: Sun, 17 Apr 2022 15:56:49 +0200 Subject: [PATCH 12/12] Build fixed --- webapp/pages/classes/index.tsx | 2 +- webapp/pages/documents/admin/index.tsx | 2 +- webapp/pages/documents/annotator/index.tsx | 2 +- webapp/pages/export/index.tsx | 2 +- webapp/pages/login/index.tsx | 2 +- webapp/pages/tags/index.tsx | 2 +- webapp/pages/users/index.tsx | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/webapp/pages/classes/index.tsx b/webapp/pages/classes/index.tsx index 8465291..2ef901f 100644 --- a/webapp/pages/classes/index.tsx +++ b/webapp/pages/classes/index.tsx @@ -7,7 +7,7 @@ import { Typography } from 'antd'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBookmark } from '@fortawesome/free-solid-svg-icons'; import { LoggedUserContext } from '../../contexts/LoggedUserContext'; -import { MainLayout } from '../../layouts/mainLayout'; +import { MainLayout } from '../../layouts/MainLayout'; function ClassesPage() { const redirecting = useUnauthRedirect('/login'); diff --git a/webapp/pages/documents/admin/index.tsx b/webapp/pages/documents/admin/index.tsx index ac575ab..73547ac 100644 --- a/webapp/pages/documents/admin/index.tsx +++ b/webapp/pages/documents/admin/index.tsx @@ -7,7 +7,7 @@ import { Button, Typography } from 'antd'; import { faFileLines } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { LoggedUserContext } from '../../../contexts/LoggedUserContext'; -import { MainLayout } from '../../../layouts/mainLayout'; +import { MainLayout } from '../../../layouts/MainLayout'; import { documentController } from '../../../controllers'; function AdminDocumentPage() { diff --git a/webapp/pages/documents/annotator/index.tsx b/webapp/pages/documents/annotator/index.tsx index 469b76a..5b30256 100644 --- a/webapp/pages/documents/annotator/index.tsx +++ b/webapp/pages/documents/annotator/index.tsx @@ -8,7 +8,7 @@ import UserNavBar from '../../../components/navigation/UserNavBar'; import { faFileLines } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { LoggedUserContext } from '../../../contexts/LoggedUserContext'; -import { MainLayout } from '../../../layouts/mainLayout'; +import { MainLayout } from '../../../layouts/MainLayout'; function UserDocumentPage() { const redirecting = useUnauthRedirect('/login'); diff --git a/webapp/pages/export/index.tsx b/webapp/pages/export/index.tsx index 1f533b6..6ce97f7 100644 --- a/webapp/pages/export/index.tsx +++ b/webapp/pages/export/index.tsx @@ -7,7 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faFileExport } from '@fortawesome/free-solid-svg-icons'; import { Typography } from 'antd'; import { LoggedUserContext } from '../../contexts/LoggedUserContext'; -import { MainLayout } from '../../layouts/mainLayout'; +import { MainLayout } from '../../layouts/MainLayout'; function ExportPage() { const redirecting = useUnauthRedirect('/login'); diff --git a/webapp/pages/login/index.tsx b/webapp/pages/login/index.tsx index 7d492e9..d115be9 100644 --- a/webapp/pages/login/index.tsx +++ b/webapp/pages/login/index.tsx @@ -2,7 +2,7 @@ import 'antd/dist/antd.css'; import { Form, Input, Button } from 'antd'; import { UserOutlined, LockOutlined } from '@ant-design/icons'; import { LoggedUserContext } from '../../contexts/LoggedUserContext'; -import { LoginLayout } from '../../layouts/loginLayout'; +import { LoginLayout } from '../../layouts/LoginLayout'; import { useContext } from 'react'; import { useRouter } from 'next/router'; import { ShowToast } from '../../utils/alerts'; diff --git a/webapp/pages/tags/index.tsx b/webapp/pages/tags/index.tsx index c304f1f..b9a5f7c 100644 --- a/webapp/pages/tags/index.tsx +++ b/webapp/pages/tags/index.tsx @@ -7,7 +7,7 @@ import { Typography } from 'antd'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTags } from '@fortawesome/free-solid-svg-icons'; import { LoggedUserContext } from '../../contexts/LoggedUserContext'; -import { MainLayout } from '../../layouts/mainLayout'; +import { MainLayout } from '../../layouts/MainLayout'; function TagsPage() { const redirecting = useUnauthRedirect('/login'); diff --git a/webapp/pages/users/index.tsx b/webapp/pages/users/index.tsx index 2190587..765f8bc 100644 --- a/webapp/pages/users/index.tsx +++ b/webapp/pages/users/index.tsx @@ -7,7 +7,7 @@ import { Typography } from 'antd'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUsers } from '@fortawesome/free-solid-svg-icons'; import { LoggedUserContext } from '../../contexts/LoggedUserContext'; -import { MainLayout } from '../../layouts/mainLayout'; +import { MainLayout } from '../../layouts/MainLayout'; function UsersPage() { const redirecting = useUnauthRedirect('/login'); -- GitLab