diff --git a/UI/Web/src/app/_service/annotation-card.service.ts b/UI/Web/src/app/_service/annotation-card.service.ts new file mode 100644 index 000000000..f15d43340 --- /dev/null +++ b/UI/Web/src/app/_service/annotation-card.service.ts @@ -0,0 +1,64 @@ +import {ApplicationRef, ComponentRef, createComponent, EmbeddedViewRef, inject, Injectable} from '@angular/core'; +import {AnnotationCardComponent} from '../book-reader/_components/annotation-card/annotation-card.component'; + +@Injectable({ + providedIn: 'root' +}) +export class AnnotationCardService { + + private readonly applicationRef = inject(ApplicationRef); + + private componentRef?: ComponentRef; + + show(config: { + position: any; + annotationText?: string; + createdDate?: Date; + onMouseEnter?: () => void; + onMouseLeave?: () => void; + }): ComponentRef { + // Remove existing card if present + this.hide(); + + // Create component using createComponent (Angular 13+ approach) + this.componentRef = createComponent(AnnotationCardComponent, { + environmentInjector: this.applicationRef.injector + }); + + // Set inputs using signals + this.componentRef.setInput('position', config.position); + this.componentRef.setInput('annotationText', config.annotationText || 'This is test text'); + this.componentRef.setInput('createdDate', config.createdDate || new Date()); + + // Set up event handlers + if (config.onMouseEnter) { + this.componentRef.instance.mouseEnter.subscribe(config.onMouseEnter); + } + if (config.onMouseLeave) { + this.componentRef.instance.mouseLeave.subscribe(config.onMouseLeave); + } + + // Attach to application + this.applicationRef.attachView(this.componentRef.hostView); + + // Append to body + const domElem = (this.componentRef.hostView as EmbeddedViewRef).rootNodes[0] as HTMLElement; + document.body.appendChild(domElem); + + return this.componentRef; + } + + hide(): void { + if (this.componentRef) { + this.applicationRef.detachView(this.componentRef.hostView); + this.componentRef.destroy(); + this.componentRef = undefined; + } + } + + updateHoverState(isHovered: boolean): void { + if (this.componentRef) { + this.componentRef.instance.isHovered.set(isHovered); + } + } +} diff --git a/UI/Web/src/app/book-reader/_components/annotation-card/annotation-card.component.scss b/UI/Web/src/app/book-reader/_components/annotation-card/annotation-card.component.scss index 1743220aa..8341701ee 100644 --- a/UI/Web/src/app/book-reader/_components/annotation-card/annotation-card.component.scss +++ b/UI/Web/src/app/book-reader/_components/annotation-card/annotation-card.component.scss @@ -3,7 +3,6 @@ position: absolute; z-index: 1000; width: 300px; - background: white; border: 1px solid #e5e7eb; border-radius: 8px; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); diff --git a/UI/Web/src/app/book-reader/_components/annotation-card/annotation-card.component.ts b/UI/Web/src/app/book-reader/_components/annotation-card/annotation-card.component.ts index 2493d98ad..52369778d 100644 --- a/UI/Web/src/app/book-reader/_components/annotation-card/annotation-card.component.ts +++ b/UI/Web/src/app/book-reader/_components/annotation-card/annotation-card.component.ts @@ -1,4 +1,4 @@ -import {Component} from '@angular/core'; +import {Component, input, model, output} from '@angular/core'; import {UtcToLocaleDatePipe} from "../../../_pipes/utc-to-locale-date.pipe"; import {DatePipe} from "@angular/common"; @@ -14,7 +14,7 @@ import {DatePipe} from "@angular/common"; export class AnnotationCardComponent { position = input.required(); annotationText = input('This is test text'); - createdDate = input(new Date()); + createdDate = input('01-01-0001'); isHovered = model(false); mouseEnter = output(); diff --git a/UI/Web/src/app/book-reader/_components/epub-highlight/epub-highlight.component.ts b/UI/Web/src/app/book-reader/_components/epub-highlight/epub-highlight.component.ts index 03aa5a6e2..c39be7dcb 100644 --- a/UI/Web/src/app/book-reader/_components/epub-highlight/epub-highlight.component.ts +++ b/UI/Web/src/app/book-reader/_components/epub-highlight/epub-highlight.component.ts @@ -1,8 +1,10 @@ import { AfterViewChecked, Component, + ComponentRef, computed, ElementRef, + inject, input, model, OnDestroy, @@ -11,6 +13,8 @@ import { ViewChild } from '@angular/core'; import {Annotation} from "../../_models/annotation"; +import {AnnotationCardComponent} from "../annotation-card/annotation-card.component"; +import {AnnotationCardService} from 'src/app/_service/annotation-card.service'; export type HighlightColor = 'blue' | 'green'; @@ -30,6 +34,9 @@ export class EpubHighlightComponent implements OnInit, AfterViewChecked, OnDestr private resizeObserver?: ResizeObserver; private annotationCardElement?: HTMLElement; + private annotationCardRef?: ComponentRef; + + private annotationCardService = inject(AnnotationCardService); showAnnotationCard = computed(() => { const annotation = this.annotation(); @@ -147,90 +154,99 @@ export class EpubHighlightComponent implements OnInit, AfterViewChecked, OnDestr } private createOrUpdateAnnotationCard() { + // const pos = this.cardPosition(); + // if (!pos) return; + // + // // Remove existing card if it exists + // this.removeAnnotationCard(); + // + // // Create new card element + // this.annotationCardElement = document.createElement('div'); + // this.annotationCardElement.className = `annotation-card ${this.isHovered() ? 'hovered' : ''}`; + // this.annotationCardElement.setAttribute('data-position', pos.isRight ? 'right' : 'left'); + // this.annotationCardElement.style.position = 'absolute'; + // this.annotationCardElement.style.top = `${pos.top}px`; + // this.annotationCardElement.style.left = `${pos.left}px`; + // this.annotationCardElement.style.zIndex = '1000'; + // + // // Add event listeners for hover + // this.annotationCardElement.addEventListener('mouseenter', () => this.onMouseEnter()); + // this.annotationCardElement.addEventListener('mouseleave', () => this.onMouseLeave()); + // + // // Create card content + // this.annotationCardElement.innerHTML = ` + //
+ //
This is test text
+ //
+ // 10/20/2025 + //
+ //
+ // `; + // + // // Create connection line + // const lineElement = document.createElement('div'); + // lineElement.className = `connection-line ${this.isHovered() ? 'hovered' : ''}`; + // lineElement.style.position = 'absolute'; + // lineElement.style.left = `${pos.connection.startX}px`; + // lineElement.style.top = `${pos.connection.startY}px`; + // lineElement.style.width = `${pos.connection.distance}px`; + // lineElement.style.height = '2px'; + // lineElement.style.backgroundColor = '#9ca3af'; + // lineElement.style.transformOrigin = '0 50%'; + // lineElement.style.transform = `rotate(${pos.connection.angle}deg)`; + // lineElement.style.opacity = this.isHovered() ? '1' : '0.3'; + // lineElement.style.transition = 'opacity 0.2s ease'; + // lineElement.style.zIndex = '999'; + // + // // Add dot at the end + // const dotElement = document.createElement('div'); + // dotElement.style.position = 'absolute'; + // dotElement.style.right = '-3px'; + // dotElement.style.top = '50%'; + // dotElement.style.width = '6px'; + // dotElement.style.height = '6px'; + // dotElement.style.backgroundColor = '#9ca3af'; + // dotElement.style.borderRadius = '50%'; + // dotElement.style.transform = 'translateY(-50%)'; + // + // lineElement.appendChild(dotElement); + // + // // Append to body + // document.body.appendChild(this.annotationCardElement); + // document.body.appendChild(lineElement); + // + // // Store reference to line for updates + // (this.annotationCardElement as any).lineElement = lineElement; + const pos = this.cardPosition(); if (!pos) return; - // Remove existing card if it exists - this.removeAnnotationCard(); - - // Create new card element - this.annotationCardElement = document.createElement('div'); - this.annotationCardElement.className = `annotation-card ${this.isHovered() ? 'hovered' : ''}`; - this.annotationCardElement.setAttribute('data-position', pos.isRight ? 'right' : 'left'); - this.annotationCardElement.style.position = 'absolute'; - this.annotationCardElement.style.top = `${pos.top}px`; - this.annotationCardElement.style.left = `${pos.left}px`; - this.annotationCardElement.style.zIndex = '1000'; - - // Add event listeners for hover - this.annotationCardElement.addEventListener('mouseenter', () => this.onMouseEnter()); - this.annotationCardElement.addEventListener('mouseleave', () => this.onMouseLeave()); - - // Create card content - this.annotationCardElement.innerHTML = ` -
-
This is test text
-
- 10/20/2025 -
-
- `; - - // Create connection line - const lineElement = document.createElement('div'); - lineElement.className = `connection-line ${this.isHovered() ? 'hovered' : ''}`; - lineElement.style.position = 'absolute'; - lineElement.style.left = `${pos.connection.startX}px`; - lineElement.style.top = `${pos.connection.startY}px`; - lineElement.style.width = `${pos.connection.distance}px`; - lineElement.style.height = '2px'; - lineElement.style.backgroundColor = '#9ca3af'; - lineElement.style.transformOrigin = '0 50%'; - lineElement.style.transform = `rotate(${pos.connection.angle}deg)`; - lineElement.style.opacity = this.isHovered() ? '1' : '0.3'; - lineElement.style.transition = 'opacity 0.2s ease'; - lineElement.style.zIndex = '999'; - - // Add dot at the end - const dotElement = document.createElement('div'); - dotElement.style.position = 'absolute'; - dotElement.style.right = '-3px'; - dotElement.style.top = '50%'; - dotElement.style.width = '6px'; - dotElement.style.height = '6px'; - dotElement.style.backgroundColor = '#9ca3af'; - dotElement.style.borderRadius = '50%'; - dotElement.style.transform = 'translateY(-50%)'; - - lineElement.appendChild(dotElement); - - // Append to body - document.body.appendChild(this.annotationCardElement); - document.body.appendChild(lineElement); - - // Store reference to line for updates - (this.annotationCardElement as any).lineElement = lineElement; + // Only create if not already created + if (!this.annotationCardRef) { + this.annotationCardRef = this.annotationCardService.show({ + position: pos, + annotationText: 'This is test text', + createdDate: new Date('10/20/2025'), + onMouseEnter: () => this.onMouseEnter(), + onMouseLeave: () => this.onMouseLeave() + }); + } } private removeAnnotationCard() { - if (this.annotationCardElement) { - // Remove associated line element - const lineElement = (this.annotationCardElement as any).lineElement; - if (lineElement) { - lineElement.remove(); - } - - this.annotationCardElement.remove(); - this.annotationCardElement = undefined; - } - } - - private updateLineOpacity() { - if (this.annotationCardElement) { - const lineElement = (this.annotationCardElement as any).lineElement; - if (lineElement) { - lineElement.style.opacity = this.isHovered() ? '1' : '0.3'; - } + // if (this.annotationCardElement) { + // // Remove associated line element + // const lineElement = (this.annotationCardElement as any).lineElement; + // if (lineElement) { + // lineElement.remove(); + // } + // + // this.annotationCardElement.remove(); + // this.annotationCardElement = undefined; + // } + if (this.annotationCardRef) { + this.annotationCardService.hide(); + this.annotationCardRef = undefined; } } }