Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/FacturXDotNet.WebEditor/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import { ImportFileComponent } from './core/import-file/import-file.component';
import { NgbTooltipConfigComponent } from './core/ng-bootstrap/ngb-tooltip-config.component';
import * as pdf from 'pdfjs-dist';
import { PlatformLocation } from '@angular/common';
import { GlobalOverlayComponent } from './core/global-overlay/global-overlay.component';

@Component({
selector: 'app-root',
imports: [RouterOutlet, ToasterComponent, ImportFileComponent, NgbTooltipConfigComponent],
imports: [RouterOutlet, ToasterComponent, ImportFileComponent, NgbTooltipConfigComponent, GlobalOverlayComponent],
template: `
<router-outlet />
<app-toaster></app-toaster>
<app-import-file></app-import-file>
<app-toaster />
<app-import-file />
<app-global-overlay />

<app-ngb-tooltip-config></app-ngb-tooltip-config>
<app-ngb-tooltip-config />
`,
styles: [],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
import { map, Observable } from 'rxjs';
import { API_BASE_URL } from '../../app.config';
import { CrossIndustryInvoice, ICrossIndustryInvoice, IXmpMetadata, StandardPdfGeneratorLanguagePackDto } from './api.models';
import { EditorStateAttachment } from '../../features/editor/editor-state.service';
import { EditorStateAttachment } from '../../features/editor/services/editor-state.service';

@Injectable({
providedIn: 'root',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component, inject, signal } from '@angular/core';
import { GlobalOverlayService } from './global-overlay.service';

@Component({
selector: 'app-global-overlay',
imports: [],
template: `
<div
class="backdrop position-absolute start-0 end-0 top-0 bottom-0 d-flex flex-column justify-content-center align-items-center pe-none"
[class.d-none]="!enabled()"
style="z-index: 9999"
>
<div class="card">
<div class="card-body d-flex flex-column align-items-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
@if (message(); as message) {
{{ message }}
}
</div>
</div>
</div>
`,
styles: `
.backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
`,
})
export class GlobalOverlayComponent {
private globalOverlayService = inject(GlobalOverlayService);
protected enabled = this.globalOverlayService.enabled;
protected message = this.globalOverlayService.message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Injectable, signal } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class GlobalOverlayService {
public enabled = signal(false);
public message = signal<string | undefined>(undefined);

public enable(message?: string) {
this.enabled.set(true);
this.message.set(message);

console.log('Overlay enabled:', message);
}

public disable() {
this.enabled.set(false);

console.log('Overlay disabled');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { NgStyle } from '@angular/common';
imports: [NgStyle],
template: `
<div class="h-100 d-flex overflow-hidden">
<div class="h-100" [ngStyle]="{ 'width.px': leftColumnWidth() }">
<div class="h-100 overflow-hidden" [ngStyle]="{ 'width.px': leftColumnWidth() }">
<ng-content select="[left]" />
</div>
<div
Expand All @@ -20,7 +20,7 @@ import { NgStyle } from '@angular/common';
<i class="bi bi-grip-vertical text-body-secondary"></i>
}
</div>
<div class="h-100" [ngStyle]="{ 'width.px': rightColumnWidth() }">
<div class="h-100 overflow-hidden" [ngStyle]="{ 'width.px': rightColumnWidth() }">
<ng-content select="[right]" />
</div>
</div>
Expand All @@ -33,6 +33,8 @@ import { NgStyle } from '@angular/common';
})
export class TwoColumnsComponent {
rightColumnWidth = model.required<number>();
leftColumnMinWidth = input(undefined, { transform: numberAttribute });
rightColumnMinWidth = input(undefined, { transform: numberAttribute });
resizeHandleWidth = input(16, { transform: numberAttribute });
draggable = input(false, { transform: booleanAttribute });
dragging = output<boolean>();
Expand Down Expand Up @@ -100,10 +102,10 @@ export class TwoColumnsComponent {
}

if (this.resizing) {
const width = this.totalWidth();
const totalWidth = this.totalWidth();
const x = event.type === 'mousemove' ? (event as MouseEvent).clientX : (event as TouchEvent).touches[0].clientX;
const newWidth = width - x - this.resizeHandleWidth() / 2;
this.rightColumnWidth.set(newWidth);
const newWidth = totalWidth - x - this.resizeHandleWidth() / 2;
this.setRightColumnWidth(newWidth);
}
}

Expand All @@ -112,4 +114,24 @@ export class TwoColumnsComponent {
this.resizing = false;
this.dragging.emit(false);
}

private setRightColumnWidth(width: number) {
let finalWidth = width;

const rightColumnMinWidth = this.rightColumnMinWidth();
if (rightColumnMinWidth !== undefined && finalWidth < rightColumnMinWidth) {
finalWidth = rightColumnMinWidth;
}

const leftColumnMinWidth = this.leftColumnMinWidth();
if (leftColumnMinWidth !== undefined) {
const totalWidth = this.totalWidth();
const leftColumnWidth = totalWidth - finalWidth - this.resizeHandleWidth();
if (leftColumnWidth < leftColumnMinWidth) {
finalWidth = totalWidth - leftColumnMinWidth - this.resizeHandleWidth();
}
}

this.rightColumnWidth.set(finalWidth);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, inject, input, signal } from '@angular/core';
import { EditorStateService } from '../../editor-state.service';
import { EditorStateService } from '../../services/editor-state.service';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { NgStyle } from '@angular/common';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,59 @@
import { Component, computed, inject, input } from '@angular/core';
import { EditorSavedState } from '../../editor-state.service';
import { EditorSettings } from '../../editor-settings.service';
import { EditorSavedState } from '../../services/editor-state.service';
import { EditorSettings } from '../../services/editor-settings.service';
import { NavigationEnd, Router, RouterLink } from '@angular/router';
import { NgbNav, NgbNavItem, NgbNavLink, NgbNavLinkBase } from '@ng-bootstrap/ng-bootstrap';
import { FormsModule } from '@angular/forms';
import { EditorResponsivenessService } from '../../editor-responsiveness.service';
import { EditorResponsivenessService } from '../../services/editor-responsiveness.service';
import { toSignal } from '@angular/core/rxjs-interop';
import { distinct, distinctUntilChanged, filter, map } from 'rxjs';
import { EditorHeaderNameComponent } from './editor-header-name.component';

@Component({
selector: 'app-editor-left-pane-header',
imports: [NgbNav, NgbNavItem, NgbNavLink, RouterLink, FormsModule, NgbNavLinkBase],
imports: [NgbNav, NgbNavItem, NgbNavLink, RouterLink, FormsModule, NgbNavLinkBase, EditorHeaderNameComponent],
template: `
<ul ngbNav [activeId]="tab()" class="nav-tabs px-4">
<li ngbNavItem="xmp">
<a ngbNavLink role="button" routerLink="/xmp">
<i class="bi bi-info-lg pe-1"></i>
@if (!folded()) {
<span [class.small]="small()">XMP Metadata</span>
}
</a>
</li>
<li ngbNavItem="cii">
<a ngbNavLink role="button" routerLink="/cii">
<i class="bi bi-code pe-1"></i>
@if (!folded()) {
<span [class.small]="small()">Cross-Industry Invoice</span>
}
</a>
</li>
<li ngbNavItem="attachments">
<a ngbNavLink role="button" routerLink="/attachments">
<i class="bi bi-paperclip pe-1"></i>
@if (!folded()) {
<span [class.small]="small()">Attachments</span>
}
({{ state().attachments.length }})
</a>
</li>
<div class="flex-grow-1"><!--spacer--></div>
<li ngbNavItem="settings">
<a ngbNavLink role="button" routerLink="/settings">
<i class="bi bi-gear pe-1"></i>
@if (!folded()) {
<span [class.small]="small()">Settings</span>
}
</a>
</li>
</ul>
<div class="d-flex flex-column">
<div class="px-3">
<app-editor-header-name [name]="state().name" />
</div>
<ul ngbNav [activeId]="tab()" class="nav-tabs px-4">
<li ngbNavItem="xmp">
<a ngbNavLink role="button" routerLink="/xmp">
<i class="bi bi-info-lg pe-1"></i>
@if (!folded()) {
<span [class.small]="small()">XMP Metadata</span>
}
</a>
</li>
<li ngbNavItem="cii">
<a ngbNavLink role="button" routerLink="/cii">
<i class="bi bi-code pe-1"></i>
@if (!folded()) {
<span [class.small]="small()">Cross-Industry Invoice</span>
}
</a>
</li>
<li ngbNavItem="attachments">
<a ngbNavLink role="button" routerLink="/attachments">
<i class="bi bi-paperclip pe-1"></i>
@if (!folded()) {
<span [class.small]="small()">Attachments</span>
}
({{ state().attachments.length }})
</a>
</li>
<div class="flex-grow-1"><!--spacer--></div>
<li ngbNavItem="settings">
<a ngbNavLink role="button" routerLink="/settings">
<i class="bi bi-gear pe-1"></i>
@if (!folded()) {
<span [class.small]="small()">Settings</span>
}
</a>
</li>
</ul>
</div>
`,
})
export class EditorLeftPaneHeaderComponent {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,82 @@
import { Component, inject, model } from '@angular/core';
import { Component, computed, inject, model } from '@angular/core';
import { NgbNav, NgbNavItem, NgbNavLinkButton } from '@ng-bootstrap/ng-bootstrap';
import { PdfModel } from '../../editor-settings.service';
import { EditorPdfViewerService } from '../editor-pdf-viewer/editor-pdf-viewer.service';
import { EditorPdfGenerationProfilesService } from '../../editor-pdf-generation-profiles.service';
import { RouterLink } from '@angular/router';
import { PdfModel } from '../../services/editor-settings.service';
import { EditorPdfViewerService } from '../../editor-tabs/editor-pdf-viewer/editor-pdf-viewer.service';
import { EditorPdfGenerationProfile, EditorPdfGenerationProfilesService } from '../../services/editor-pdf-generation-profiles.service';
import { Router, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';

@Component({
selector: 'app-editor-right-pane-header',
imports: [NgbNav, NgbNavItem, NgbNavLinkButton, RouterLink],
imports: [NgbNav, NgbNavItem, NgbNavLinkButton, FormsModule, RouterLink],
template: `
<div class="d-flex align-items-center">
<span class="fw-semibold"><i class="bi bi-file-pdf"></i> PDF</span>
<ul ngbNav [(activeId)]="tab" class="nav-underline small ps-4">
<li ngbNavItem="imported">
<button ngbNavLink>Imported</button>
</li>
<li ngbNavItem="generated">
<button ngbNavLink>Generated</button>
</li>
</ul>

@if (tab() === 'generated') {
@if (selectedProfile(); as selectedProfile) {
<div class="small text-body-secondary px-4">
Current profile: <span class="fw-semibold"> {{ selectedProfile.name }} </span>
<a class="btn btn-sm btn-light border mx-2" routerLink="/settings/profiles">Change profile</a>
</div>
} @else {
<a class="btn btn-sm btn-light border mx-4" routerLink="/settings/profiles">Customize generation</a>
}

<div class="flex-grow-1"><!--spacer--></div>
<div class="d-flex flex-column gap-2">
<div class="ps-1 d-flex align-items-center">
<span class="fs-5 fw-semibold"><i class="bi bi-file-pdf"></i> PDF</span>
<ul ngbNav [(activeId)]="tab" class="nav-underline small ps-4">
<li ngbNavItem="imported">
<button ngbNavLink>Imported</button>
</li>
<li ngbNavItem="generated">
<button ngbNavLink>Generated</button>
</li>
</ul>
</div>

<div class="px-2">
@if (pdf.isLoading()) {
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
<div class="d-flex gap-2 align-items-center">
@if (tab() === 'generated') {
@if (profiles().length > 0) {
<div class="flex-grow-1 editor__control row">
<div class="col-3">
<label class="col-form-label col-form-label-sm">Profile:</label>
</div>
<div class="col">
<select class="form-select form-select-sm" [ngModel]="selectedProfile()" (ngModelChange)="changeProfile($event)">
<option class="text-body-secondary" [ngValue]="undefined">(none)</option>
@for (profile of profiles(); track profile.id) {
<option [ngValue]="profile">{{ profile.name }}</option>
}
</select>
</div>
</div>
} @else {
<button class="btn btn-sm btn-light border" (click)="regeneratePdf()"><i class="bi bi-arrow-repeat"></i> Regenerate</button>
<button class="btn btn-sm btn-light border text-nowrap" routerLink="/settings/profiles">Customize PDF generation</button>
}
</div>
}

<div class="flex-grow-1"><!--spacer--></div>

<div>
@if (pdf.isLoading()) {
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
} @else {
<button class="btn btn-sm btn-light border text-nowrap" (click)="regeneratePdf()"><i class="bi bi-arrow-repeat"></i> Regenerate</button>
}
</div>
}
</div>
</div>
`,
styles: ``,
})
export class EditorRightPaneHeaderComponent {
tab = model.required<PdfModel>();

private router: Router = inject(Router);
private editorPdfViewerService = inject(EditorPdfViewerService);
protected pdf = this.editorPdfViewerService.pdf;

private editorPdfGenerationProfilesService = inject(EditorPdfGenerationProfilesService);
protected profiles = computed(() => Object.values(this.editorPdfGenerationProfilesService.profiles()));
protected selectedProfile = this.editorPdfGenerationProfilesService.selectedProfile;

protected regeneratePdf() {
this.editorPdfViewerService.regenerateAndDisplayGeneratedPdf();
}

protected async changeProfile(newProfile: EditorPdfGenerationProfile | undefined) {
this.editorPdfGenerationProfilesService.selectProfile(newProfile?.id);
this.regeneratePdf();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export class EditorExportMenuComponent {

private editorMenuService = inject(EditorMenuService);
private toastService = inject(ToastService);
private destroyRef = inject(DestroyRef);

async exportFacturX() {
this.exporting.emit(true);
Expand Down
Loading