Compare commits

..

1 Commits

Author SHA1 Message Date
8053a68bf2 feat: migrate to new tsclass schema 2025-07-28 09:42:43 +00:00
27 changed files with 4941 additions and 6421 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
registry=https://registry.npmjs.org/

View File

@@ -1,72 +1,5 @@
# Changelog # Changelog
## 2025-12-11 - 2.2.3 - fix(contentinvoice)
Use shared translation.TranslationKey type in DeContentInvoice.translateKey and remove local import
- Replaced local TranslationKey import with plugins.shared.translation.TranslationKey to ensure type consistency
- Updated DeContentInvoice.translateKey parameter type to reference the shared translation type
- Removed an unused type import from ts_web/elements/contentinvoice.ts
## 2025-12-11 - 2.2.0 - feat(viewer)
Add pagination metadata and thumbnail offset rendering for viewer thumbnails
- Expose per-page pagination metadata (IPagePaginationInfo) from DeDocument during rendering (pageNumber, startOffset, contentLength).
- DeDocument now collects pagination info while paginating and dispatches a 'pagination-complete' CustomEvent with pageCount and paginationInfo (bubbled & composed).
- DeContentInvoice: add renderStartOffset and renderContentLength properties and applyOffsetTrimming to support rendering trimmed content (used for thumbnails).
- DeDocumentViewer: listen for 'pagination-complete', store paginationInfo, and render thumbnail previews by mounting DeContentInvoice instances with precomputed offsets to produce accurate thumbnails.
- Viewer: wire thumbnail generation to use per-page offsets so thumbnails show exactly the content slice for each page.
- Demo data: extended demo invoice items in ts_shared/demoletter.ts to better exercise pagination and thumbnail rendering.
- Remove registry override from .npmrc (previously pointed to registry.npmjs.org).
## 2025-12-11 - 2.1.1 - fix(viewer)
Improve sidebar resizing UX and update deps
- Add smooth width transition for the thumbnail sidebar to improve resize visuals
- Disable viewport CSS transitions while user is actively resizing the sidebar to avoid janky animations
- Mark viewport as resizing in classes so layout updates skip transitions during drag
- Make sidebar resize mousemove listener passive to improve scrolling/interaction performance
- Bump @design.estate/dees-wcctools to ^2.0.0 and @git.zone/tswatch to ^2.3.11
## 2025-12-11 - 2.1.0 - feat(viewer)
Add smooth zoom and scroll animations to viewer
- Introduce smooth zoom animation: new private animateZoomTo(targetZoom, duration) method and zoomAnimationId to track/cancel running animations
- Animate transitions for numeric zoom presets and zoom step changes (handleZoomStep now uses animateZoomTo)
- Cancel ongoing zoom animation when user manipulates the zoom slider
- Make fit-to-width and fit-to-page calculations optionally animate (calculateFitWidth/ calculateFitPage accept animate flag)
- Add animated scrolling helper animateScrollTo for smooth page scrolling when requested (used when smooth=true)
## 2025-12-11 - 2.0.6 - fix(DeDocumentViewer)
Account for toolbar and padding when calculating Fit Page zoom in viewer
- calculateFitPage now subtracts explicit top (toolbar + padding), bottom, and side paddings from viewport dimensions before computing scale.
- This produces a more accurate zoom level for the Fit Page preset by considering toolbar height and page margins.
## 2025-12-11 - 2.0.2 - fix(page)
Use theme variables for page styling, make pagecontainer background theme-aware, and use accent background for footer separator
- Extract documentSettings.theme to a local variable in dedocument-page and use safe optional checks when emitting CSS custom properties
- Use theme pageBackground / coverPageBackground values consistently for page background-image fallback logic
- Make pagecontainer background follow the --text-bg-color CSS variable instead of a hard #ffffff color
- Switch footer separator color variable to --color-accent-bg so the footer stripe uses the theme accent background
## 2025-12-10 - 2.0.1 - fix(core)
Migrate file I/O to @push.rocks/smartfs, adopt TC39 decorators v3 accessor in web components, and update docs/tests
- Added dependency @push.rocks/smartfs and replaced usages of @push.rocks/smartfile.fs with the SmartFs class-based API
- Updated test helpers and test code to use SmartFs (SmartFsProviderNode) and new file read/write methods
- Replaced legacy property declarations with TC39 decorators v3 accessor keyword across web components (ts_web/elements/*)
- Replaced synchronous smartfile directory operations with async SmartFs directory.exists/create flow for .nogit
- Updated README and added readme.hints.md with SmartFs usage examples, decorator guidance, and other developer hints
## 2025-12-10 - 2.0.0 - BREAKING CHANGE(build)
Upgrade dependencies and dev tooling; adjust TypeScript config
- Bumped runtime dependencies: @design.estate/*, @push.rocks/*, @git.zone/tsrun (to ^2.0.0), @tsclass/tsclass, puppeteer, @types/node, qrcode types, and others.
- Updated devDependencies: @git.zone/tsbuild, tsbundle, tstest, tswatch to newer releases.
- Removed experimentalDecorators and useDefineForClassFields from tsconfig.json — this may change decorator handling and affect compilation.
- These dependency and config changes may introduce breaking behavior; recommend a major version bump.
## 2025-01-01 - 1.6.11 - fix(license) ## 2025-01-01 - 1.6.11 - fix(license)
Update copyright notice in license to reflect new ownership Update copyright notice in license to reflect new ownership

View File

@@ -1,6 +1,6 @@
{ {
"name": "@design.estate/dees-document", "name": "@design.estate/dees-document",
"version": "2.2.3", "version": "1.6.11",
"private": false, "private": false,
"description": "A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.", "description": "A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.",
"main": "dist_ts_web/index.js", "main": "dist_ts_web/index.js",
@@ -21,29 +21,29 @@
"author": "Lossless GmbH", "author": "Lossless GmbH",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@design.estate/dees-catalog": "^3.3.0", "@design.estate/dees-catalog": "^1.4.1",
"@design.estate/dees-domtools": "^2.3.6", "@design.estate/dees-domtools": "^2.3.2",
"@design.estate/dees-element": "^2.1.3", "@design.estate/dees-element": "^2.0.39",
"@design.estate/dees-wcctools": "^2.0.0", "@design.estate/dees-wcctools": "^1.0.90",
"@git.zone/tsrun": "^2.0.0", "@git.zone/tsrun": "^1.3.3",
"@push.rocks/smartfile": "^13.1.0", "@push.rocks/smartfile": "^11.2.0",
"@push.rocks/smartfs": "^1.2.0", "@push.rocks/smartjson": "^5.0.20",
"@push.rocks/smartjson": "^6.0.0", "@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpdf": "^3.2.2",
"@push.rocks/smartpdf": "^4.1.1",
"@push.rocks/smarttime": "^4.1.1", "@push.rocks/smarttime": "^4.1.1",
"@tsclass/tsclass": "^9.3.0", "@tsclass/tsclass": "^8.0.3",
"@types/node": "^25.0.0", "@types/node": "^22.13.13",
"@types/qrcode": "^1.5.6", "@types/qrcode": "^1.5.5",
"puppeteer": "^24.32.1", "puppeteer": "^24.4.0",
"qrcode": "^1.5.4" "qrcode": "^1.5.4"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^3.1.2", "@git.zone/tsbuild": "^2.3.2",
"@git.zone/tsbundle": "^2.6.3", "@git.zone/tsbundle": "^2.2.5",
"@git.zone/tstest": "^3.1.3", "@git.zone/tstest": "^1.0.96",
"@git.zone/tswatch": "^2.3.11", "@git.zone/tswatch": "^2.1.0",
"@push.rocks/projectinfo": "^5.0.2" "@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/tapbundle": "^5.6.0"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",

9050
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +1 @@
# Development Hints
## Dependencies (v2.0.0)
- Uses `@push.rocks/smartfs` (v1.2.0) for filesystem operations - SmartFs is class-based with fluent API
- Uses `@push.rocks/smartfile` (v13.1.0) - note: v13 removed the `.fs` namespace, use smartfs instead
- Uses `@git.zone/tstest/tapbundle` for tests (NOT @push.rocks/tapbundle)
- Test files must end with `export default tap.start()`
## TC39 Decorators v3
All decorated properties in web components use the `accessor` keyword:
```typescript
@property({ type: Object })
accessor letterData: TInvoice;
```
## SmartFs Usage
```typescript
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
const smartfs = new SmartFs(new SmartFsProviderNode());
// Reading files
await smartfs.file('/path/to/file').encoding('utf8').read();
// Writing files
await smartfs.file('/path/to/file').write(buffer);
// Directory operations
await smartfs.directory('/path').exists();
await smartfs.directory('/path').create();
```
## Project Structure
- `ts/` - Node.js server-side code (PdfService)
- `ts_web/` - Browser web components (Lit-based)
- `ts_shared/` - Shared code (translations, interfaces)
- `test/` - Test files

337
readme.md
View File

@@ -1,276 +1,199 @@
# @design.estate/dees-document # @design.estate/dees-document
A powerful TypeScript framework for dynamically generating professional business documents like invoices with web components and PDF export capabilities. 🧾 A comprehensive tool for dynamically generating and rendering business documents like invoices using modern web technologies.
## Issue Reporting and Security ## Install
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly. To incorporate `@design.estate/dees-document` into your project, execute the following command in your terminal:
## Installation
```shell ```shell
pnpm install @design.estate/dees-document npm install @design.estate/dees-document --save
``` ```
## Features This command will install the package and add it to your project's dependencies, thus making all the necessary modules available within your `node_modules` directory.
- 📄 **PDF Generation** - Server-side PDF creation from structured data using Puppeteer
- 🎨 **Web Components** - Lit-based custom elements for document rendering
- 🌍 **Multi-language Support** - Built-in translation system (EN, DE, ES)
- 📱 **Automatic Pagination** - Smart content overflow handling across pages
- 💳 **QR Payment Codes** - EPC QR codes for SEPA payments
- 🖨️ **Print Mode** - Optimized rendering for both screen and print
- 📐 **A4 Format** - Precise DIN A4 document dimensions
-**Theming** - Customizable colors, backgrounds, and branding
## Usage ## Usage
### Server-Side PDF Generation The `@design.estate/dees-document` package serves as a robust framework to facilitate the generation of business documents, such as invoices, contracts, and reports. Leveraging modern web technologies, this package integrates seamlessly with TypeScript and ES modules, offering a type-safe environment conducive to efficient, dynamic document creation.
Below, we provide a detailed guide for utilizing this package, from initializing your environment to generating complete PDF documents.
### Setting Up the Environment
Before diving into document creation, ensure your environment is properly configured. Make sure to import necessary dependencies and setup configurations:
```typescript ```typescript
import { PdfService } from '@design.estate/dees-document'; import { PdfService, IPdfServiceConstructorOptions } from '@design.estate/dees-document';
import type { TInvoice } from '@tsclass/tsclass/finance';
// Initialize the PDF service async function setupPdfService() {
const pdfService = new PdfService({}); const options: IPdfServiceConstructorOptions = {
await pdfService.start(); // Configure your options here
};
// Create invoice data const pdfService = await PdfService.createAndStart(options);
const invoice: TInvoice = { console.log('PDF Service started successfully.');
type: 'invoice', return pdfService;
invoiceType: 'debitnote', }
id: 'INV-2024-001', ```
invoiceId: 'INV-2024-001',
date: Date.now(), In this setup snippet, you set up the `PdfService` which is the core component for PDF generation. The configuration options should be adjusted to fit your needs, especially considering options such as file paths, languages, and template directories.
currency: 'EUR',
dueInDays: 30, ### Creating a Document Template
reverseCharge: false,
To create documents, particularly invoices, you'll first need to define a template. This template describes the structure of your document:
```typescript
import { ILetter } from '@design.estate/dees-document';
const invoiceTemplate: ILetter = {
from: { from: {
name: 'Your Company GmbH', name: 'Your Company Name',
type: 'company',
status: 'active',
address: { address: {
streetName: 'Business Street', streetName: 'Your Street',
houseNumber: '123', houseNumber: '123',
city: 'Berlin', city: 'Your City',
country: 'Germany', country: 'Your Country',
postalCode: '10115', postalCode: '12345',
},
sepaConnection: {
bic: 'DEUTDEFF',
iban: 'DE89370400440532013000',
},
registrationDetails: {
vatId: 'DE123456789',
registrationName: 'Amtsgericht Berlin',
registrationId: 'HRB 12345',
}, },
email: 'your-email@example.com',
phone: '123-456-7890',
}, },
to: { to: {
name: 'Customer Inc.', name: 'Recipient Company Name',
type: 'company',
status: 'active',
address: { address: {
streetName: 'Client Avenue', streetName: 'Recipient Street',
houseNumber: '456', houseNumber: '456',
city: 'Munich', city: 'Recipient City',
country: 'Germany', country: 'Recipient Country',
postalCode: '80331', postalCode: '67890',
}, },
registrationDetails: { email: 'recipient-email@example.com',
vatId: 'DE987654321', phone: '098-765-4321',
},
content: {
invoiceData: {
items: [
{
name: 'Service or Product Name',
unitQuantity: 2,
unitNetPrice: 100.0,
unitType: 'service',
vatPercentage: 19,
currency: 'EUR',
},
],
}, },
}, },
items: [ subject: 'Invoice for Services Rendered',
{ date: new Date().getTime(),
name: 'Web Development Services',
unitQuantity: 40,
unitNetPrice: 95,
unitType: 'hours',
vatPercentage: 19,
position: 1,
},
{
name: 'Hosting (Annual)',
unitQuantity: 1,
unitNetPrice: 299,
unitType: 'item',
vatPercentage: 19,
position: 2,
},
],
subject: 'Invoice INV-2024-001',
versionInfo: { versionInfo: {
type: 'final', type: 'final',
version: '1.0.0', version: '1.0.0',
}, },
}; };
// Generate PDF
const pdfResult = await pdfService.createPdfFromLetterObject({
letterData: invoice,
documentSettings: {
languageCode: 'EN',
enableDefaultHeader: true,
enableDefaultFooter: true,
enableFoldMarks: true,
dateStyle: 'long',
},
});
// Save to file
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
const fs = new SmartFs(new SmartFsProviderNode());
await fs.file('./invoice.pdf').write(Buffer.from(pdfResult.buffer));
// Don't forget to stop the service when done
await pdfService.stop();
``` ```
### Document Settings The `invoiceTemplate` object utilizes the `ILetter` interface which specifies the structure for invoice data, including sender and recipient details, itemization, and pricing.
Customize document appearance with `IDocumentSettings`: ### Generating the Document
Once a template is established, the next step is generating a PDF:
```typescript ```typescript
const documentSettings = { async function generateInvoice(pdfService: PdfService, invoiceData: ILetter) {
// Language for translations const pdfBuffer = await pdfService.createPdfFromLetterObject(invoiceData);
languageCode: 'DE', // 'EN' | 'DE' | 'ES' console.log('Invoice PDF generated successfully.');
// Layout options // Here you could save the PDF to your filesystem, send it via email, etc.
enableDefaultHeader: true, }
enableDefaultFooter: true,
enableFoldMarks: true,
enableTopDraftText: true,
enableInvoiceContractRefSection: true,
// Display options
vatGroupPositions: true,
dateStyle: 'long', // 'short' | 'medium' | 'long' | 'full'
// Theming
theme: {
colorPrimaryForeground: '#ffffff',
colorPrimaryBackground: '#e4002b',
colorAccentForeground: '#333333',
colorAccentBackground: '#f5f5f5',
pageBackground: 'url(your-watermark.png)',
coverPageBackground: 'url(your-cover.png)',
},
};
``` ```
### Web Component Usage In this function, the `createPdfFromLetterObject` method converts your invoice data into a `Buffer` containing the PDF. This `Buffer` can subsequently be saved as a file or sent over HTTP.
For browser-based document viewing: ### Comprehensive Example
Below is an example integrating all previous steps into a single coherent script:
```typescript ```typescript
import '@design.estate/dees-document/web'; async function main() {
const pdfService = await setupPdfService();
const invoiceData: ILetter = {
// Populate your invoice object
};
await generateInvoice(pdfService, invoiceData);
}
// In your HTML/Lit template main().then(() => console.log('Invoice generation process completed.'));
html`
<dedocument-viewer
.letterData=${invoiceData}
.documentSettings=${documentSettings}
></dedocument-viewer>
`;
// Or render the document directly
html`
<dedocument-dedocument
.letterData=${invoiceData}
.documentSettings=${documentSettings}
></dedocument-dedocument>
`;
``` ```
### Translation System This script encompasses initializing services and generating a PDF in a streamlined, efficient workflow.
The package includes a translation system for multi-language document generation: ### Advanced Features
`@design.estate/dees-document` provides several advanced functionalities, enabling rich document creation:
1. **Custom Templates & Styling**
- Customize the styling through CSS or using inline styles in TypeScript.
- Templates can be adjusted to present different document types (e.g., contracts, reports).
2. **Modular Components and Reuse**
- Utilize modular components to create reusable parts across different documents, enhancing maintainability and reducing redundancy.
3. **Interactive Documents**
- Integrate interactivities like forms, buttons, and interactive charts within your documents.
4. **Localization Support**
- Documents can be localized to support multiple languages, enhancing accessibility and usability.
5. **Responsive and Adaptive Designs**
- Create documents that adjust layout dynamically depending on print or digital medium, maintaining consistency across platforms.
6. **Security Features**
- Apply digital signatures and encrypt sensitive documents to ensure secure and authentic document distribution.
7. **Complex Business Logic**
- Implement complex logic for feature-rich documents such as inventory systems, automatic tax calculations, or real-time pricing adjustments.
### Utilize Translation for Localization
The package offers a translation mechanism to adapt your documents for international clients:
```typescript ```typescript
import { translate } from '@design.estate/dees-document/shared'; import { translate } from '@design.estate/dees-document/shared';
// Translate document labels const languageCode: 'DE' | 'EN' | 'ES' = 'EN'; // Set required language code
translate('DE', 'invoice@@totalGross'); // "Gesamtbetrag (Brutto)" console.log(translate(languageCode, 'invoice', 'Invoice')); // Translated output
translate('EN', 'invoice@@totalGross'); // "Total (Gross)"
translate('ES', 'invoice@@totalGross'); // "Total (Bruto)"
``` ```
## Module Exports Different language settings can be applied, enabling the seamless presentation of document contents tailored to regional and linguistic preferences.
The package provides multiple entry points: ### Conclusion
```typescript The `@design.estate/dees-document` package is a versatile and powerful solution for businesses seeking to automate and streamline their document generation processes using modern web technologies. By integrating seamlessly with TypeScript, it offers robust capabilities for creating complex, customizable, and interactive documents that can meet a variety of business needs.
// Node.js server-side (PDF generation)
import { PdfService } from '@design.estate/dees-document';
import { PdfService } from '@design.estate/dees-document/node';
// Browser web components From custom templates to localization and advanced security features, this module provides the flexibility and power to create polished and professional documents efficiently. Whether you're generating invoices, contracts, or detailed reports, `@design.estate/dees-document` has the tools to support your enterprise workflows with ease and precision.
import '@design.estate/dees-document/web';
// Shared utilities and types
import { translate, IDocumentSettings } from '@design.estate/dees-document/shared';
// TypeScript interfaces
import type { IDocumentSettings } from '@design.estate/dees-document/interfaces';
```
## Document Components
The framework includes these web components:
| Component | Description |
|-----------|-------------|
| `<dedocument-dedocument>` | Main document container with pagination |
| `<dedocument-viewer>` | Document viewer with controls |
| `<dedocument-page>` | Single A4 page with scaling support |
| `<dedocument-pageheader>` | Company logo and branding header |
| `<dedocument-letterheader>` | Sender/recipient addresses |
| `<dedocument-pagecontent>` | Main content area |
| `<dedocument-pagefooter>` | Company info and page numbers |
| `<dedocument-contentinvoice>` | Invoice-specific content layout |
| `<dedocument-paymentcode>` | EPC QR code for SEPA payments |
## Draft vs Final Documents
Control document version display:
```typescript
const invoice = {
// ... other fields
versionInfo: {
type: 'draft', // Shows watermark "DRAFT" across pages
version: '0.1.0',
},
};
// For final documents
const invoice = {
// ... other fields
versionInfo: {
type: 'final', // Clean document without watermark
version: '1.0.0',
},
};
```
## License and Legal Information ## License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file. This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file. **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks ### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein. This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
### Company Information ### Company Information
Task Venture Capital GmbH Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc. For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works. By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

View File

@@ -1,7 +1,5 @@
import * as tsclass from '@tsclass/tsclass'; import * as tsclass from '@tsclass/tsclass';
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs'; import * as smartfile from '@push.rocks/smartfile';
import * as path from 'path'; import * as path from 'path';
export const smartfs = new SmartFs(new SmartFsProviderNode()); export { tsclass, smartfile, path };
export { tsclass, path };

View File

@@ -1,7 +1,7 @@
import * as plugins from "./plugins.js"; import * as plugins from "./plugins.js";
import * as paths from "./paths.js"; import * as paths from "./paths.js";
import * as interfaces from "../ts_shared/interfaces/index.js"; import * as interfaces from "../ts_shared/interfaces/index.js";
import { expect, tap } from "@git.zone/tstest/tapbundle"; import { expect, tap } from "@push.rocks/tapbundle";
import * as deesDocumentServer from "../ts/index.js"; import * as deesDocumentServer from "../ts/index.js";
let testPdfServiceInstance: deesDocumentServer.PdfService; let testPdfServiceInstance: deesDocumentServer.PdfService;
@@ -99,8 +99,10 @@ tap.test("should create an invoice", async () => {
const pdfResult = await testPdfServiceInstance.createPdfFromLetterObject( const pdfResult = await testPdfServiceInstance.createPdfFromLetterObject(
optionsArg optionsArg
); );
await plugins.smartfs.file(plugins.path.join(paths.nogitDir, `test-${counter++}.pdf`)) await plugins.smartfile.memory.toFs(
.write(Buffer.from(pdfResult.buffer)); Buffer.from(pdfResult.buffer),
plugins.path.join(paths.nogitDir, `test-${counter++}.pdf`)
);
}; };
await saveResult({ await saveResult({
letterData: testLetterData, letterData: testLetterData,
@@ -224,4 +226,4 @@ tap.test("should stop the service", async () => {
await testPdfServiceInstance.stop(); await testPdfServiceInstance.stop();
}); });
export default tap.start(); tap.start();

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-document', name: '@design.estate/dees-document',
version: '2.2.3', version: '1.6.11',
description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.' description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.'
} }

View File

@@ -2,5 +2,5 @@ import * as plugins from './plugins.js';
import * as paths from './paths.js'; import * as paths from './paths.js';
export const getBundleAsString = async () => { export const getBundleAsString = async () => {
return plugins.smartfs.file(paths.bundleFile).encoding('utf8').read(); return plugins.smartfile.fs.toStringSync(paths.bundleFile);
}; };

View File

@@ -6,9 +6,7 @@ export const packageDir = plugins.path.join(
); );
export const nogitDir = plugins.path.join(packageDir, '.nogit/'); export const nogitDir = plugins.path.join(packageDir, '.nogit/');
if (!(await plugins.smartfs.directory(nogitDir).exists())) { plugins.smartfile.fs.ensureDirSync(nogitDir);
await plugins.smartfs.directory(nogitDir).create();
}
export const bundleDir = plugins.path.join(packageDir, './dist_bundle'); export const bundleDir = plugins.path.join(packageDir, './dist_bundle');
export const bundleFile = plugins.path.join(bundleDir, './bundle.js'); export const bundleFile = plugins.path.join(bundleDir, './bundle.js');

View File

@@ -10,13 +10,10 @@ export { shared };
// @push.rocks/scope // @push.rocks/scope
import * as smartfile from '@push.rocks/smartfile'; import * as smartfile from '@push.rocks/smartfile';
import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
import * as smartjson from '@push.rocks/smartjson'; import * as smartjson from '@push.rocks/smartjson';
import * as smartpath from '@push.rocks/smartpath'; import * as smartpath from '@push.rocks/smartpath';
import * as smartpdf from '@push.rocks/smartpdf'; import * as smartpdf from '@push.rocks/smartpdf';
export const smartfs = new SmartFs(new SmartFsProviderNode());
export { smartfile, smartpath, smartjson, smartpdf }; export { smartfile, smartpath, smartjson, smartpdf };
// @tsclass scope // @tsclass scope

View File

@@ -50,10 +50,7 @@ const toContact: plugins.tsclass.business.TContact = {
}; };
export const demoLetter: plugins.tsclass.finance.TInvoice = { export const demoLetter: plugins.tsclass.finance.TInvoice = {
type: "accounting-doc", type: "invoice",
accountingDocType: "invoice",
accountingDocId: "LL-INV-48765",
accountingDocStatus: "draft",
id: "LL-INV-48765", id: "LL-INV-48765",
versionInfo: { versionInfo: {
version: "1.0.0", version: "1.0.0",
@@ -62,6 +59,7 @@ export const demoLetter: plugins.tsclass.finance.TInvoice = {
language: "de", language: "de",
date: Date.now(), date: Date.now(),
incidenceId: "LL-INV-48765", incidenceId: "LL-INV-48765",
invoiceId: "LL-INV-48765",
subject: "LL-INV-48765", subject: "LL-INV-48765",
reverseCharge: true, reverseCharge: true,
dueInDays: 30, dueInDays: 30,
@@ -76,6 +74,7 @@ export const demoLetter: plugins.tsclass.finance.TInvoice = {
printResult: null, printResult: null,
currency: "EUR", currency: "EUR",
notes: [], notes: [],
invoiceType: "debitnote",
items: [ items: [
{ {
name: "Item with 19% VAT", name: "Item with 19% VAT",
@@ -237,86 +236,6 @@ export const demoLetter: plugins.tsclass.finance.TInvoice = {
vatPercentage: 0, vatPercentage: 0,
position: 20, position: 20,
}, },
{
name: "Consulting Services",
unitQuantity: 5,
unitNetPrice: 150,
unitType: "hours",
vatPercentage: 19,
position: 21,
},
{
name: "Development Work",
unitQuantity: 12,
unitNetPrice: 120,
unitType: "hours",
vatPercentage: 19,
position: 22,
},
{
name: "Project Management",
unitQuantity: 3,
unitNetPrice: 180,
unitType: "hours",
vatPercentage: 19,
position: 23,
},
{
name: "Technical Support",
unitQuantity: 8,
unitNetPrice: 90,
unitType: "hours",
vatPercentage: 7,
position: 24,
},
{
name: "Documentation",
unitQuantity: 4,
unitNetPrice: 75,
unitType: "hours",
vatPercentage: 7,
position: 25,
},
{
name: "Code Review",
unitQuantity: 6,
unitNetPrice: 110,
unitType: "hours",
vatPercentage: 19,
position: 26,
},
{
name: "Testing & QA",
unitQuantity: 10,
unitNetPrice: 95,
unitType: "hours",
vatPercentage: 19,
position: 27,
},
{
name: "Infrastructure Setup",
unitQuantity: 2,
unitNetPrice: 250,
unitType: "hours",
vatPercentage: 19,
position: 28,
},
{
name: "Training Session",
unitQuantity: 4,
unitNetPrice: 200,
unitType: "hours",
vatPercentage: 7,
position: 29,
},
{
name: "Maintenance Package",
unitQuantity: 1,
unitNetPrice: 500,
unitType: "units",
vatPercentage: 19,
position: 30,
},
], ],
}; };

View File

@@ -148,10 +148,82 @@ export const DE_translations: Dictionary = {
vat: "Umsatzsteuer", vat: "Umsatzsteuer",
}; };
// Define Spanish translations
// export const ES_translations: TTranslationImplementation = {
// address: "Dirección",
// bankConnection: "Conexión bancaria",
// contactInfo: "Información de contacto",
// description: "Descripción",
// invoice: "Factura",
// itemPos: "Pos.",
// quantity: "Cantidad",
// registrationInfo: "Información de registro",
// reverseVatNote:
// "El IVA se aplica por inversión del sujeto pasivo y debe ser pagado por el cliente.",
// totalNetPrice: "Precio total neto",
// unitNetPrice: "Precio unitario neto",
// unitType: "Tipo de unidad",
// yourCustomerId: "Su número de cliente:",
// yourVatId: "Su ID de IVA:",
// continuesOnPage: "Continúa en la página",
// finalPageStatement: "Esta es la última página de este documento.",
// page: "Página",
// vatShort: "IVA",
// };
// Define French translations
// export const FR_translations: TTranslationImplementation = {
// address: "Adresse",
// bankConnection: "Coordonnées bancaires",
// contactInfo: "Informations de contact",
// description: "Description",
// invoice: "Facture",
// itemPos: "Position",
// quantity: "Quantité",
// registrationInfo: "Informations d'enregistrement",
// reverseVatNote:
// "La TVA s'applique selon le mécanisme d'autoliquidation et est à payer par le client.",
// totalNetPrice: "Prix net total",
// unitNetPrice: "Prix unitaire net",
// unitType: "Type d'unité",
// yourCustomerId: "Votre numéro de client :",
// yourVatId: "Votre numéro de TVA :",
// continuesOnPage: "Continue sur la page",
// finalPageStatement: "Ceci est la dernière page de ce document.",
// page: "Page",
// vatShort: "TVA",
// };
// Define Italian translations
// export const IT_translations: TTranslationImplementation = {
// address: "Indirizzo",
// bankConnection: "Coordinate bancarie",
// contactInfo: "Informazioni di contatto",
// description: "Descrizione",
// invoice: "Fattura",
// itemPos: "Pos.",
// quantity: "Quantità",
// registrationInfo: "Informazioni di registrazione",
// reverseVatNote:
// "L'IVA è applicata con inversione contabile ed è a carico del cliente.",
// totalNetPrice: "Prezzo netto totale",
// unitNetPrice: "Prezzo netto unitario",
// unitType: "Tipo di unità",
// yourCustomerId: "Il tuo numero cliente:",
// yourVatId: "Il tuo numero di partita IVA:",
// continuesOnPage: "Continua alla pagina",
// finalPageStatement: "Questa è l'ultima pagina di questo documento.",
// page: "Pagina",
// vatShort: "IVA",
// };
// Language Code Map // Language Code Map
export const languageCodeMap: Record<string, Dictionary> = { export const languageCodeMap: Record<string, Dictionary> = {
EN: EN_translations, EN: EN_translations,
DE: DE_translations, DE: DE_translations,
// ES: ES_translations,
// FR: FR_translations,
// IT: IT_translations,
}; };
// Language Code Type // Language Code Type

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-document', name: '@design.estate/dees-document',
version: '2.2.3', version: '1.6.11',
description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.' description: 'A sophisticated framework for dynamically generating and rendering business documents like invoices with modern web technologies, featuring PDF creation, templating, and automation.'
} }

View File

@@ -15,6 +15,7 @@ import {
import * as plugins from "../plugins.js"; import * as plugins from "../plugins.js";
import { dedocumentSharedStyle } from "../style.js"; import { dedocumentSharedStyle } from "../style.js";
import type { TranslationKey } from "ts_shared/translation.js";
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@@ -40,20 +41,13 @@ export class DeContentInvoice extends DeesElement {
type: Object, type: Object,
reflect: true, reflect: true,
}) })
accessor letterData: plugins.tsclass.finance.TInvoice; public letterData: plugins.tsclass.finance.TInvoice;
@property({ @property({
type: Object, type: Object,
reflect: true, reflect: true,
}) })
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings; public documentSettings: plugins.shared.interfaces.IDocumentSettings;
// Offset-based rendering properties for thumbnails
@property({ type: Number })
accessor renderStartOffset: number = null;
@property({ type: Number })
accessor renderContentLength: number = null;
constructor() { constructor() {
super(); super();
@@ -302,7 +296,7 @@ export class DeContentInvoice extends DeesElement {
} }
} }
public translateKey(key: plugins.shared.translation.TranslationKey): string { public translateKey(key: TranslationKey): string {
return plugins.shared.translation.translate( return plugins.shared.translation.translate(
this.documentSettings.languageCode, this.documentSettings.languageCode,
key key
@@ -313,29 +307,7 @@ export class DeContentInvoice extends DeesElement {
_changedProperties: Map<string | number | symbol, unknown> _changedProperties: Map<string | number | symbol, unknown>
) { ) {
super.firstUpdated(_changedProperties); super.firstUpdated(_changedProperties);
await this.attachInvoiceDom(); this.attachInvoiceDom();
// Apply offset-based trimming if specified (used for thumbnails)
if (this.renderStartOffset !== null && this.renderContentLength !== null) {
await this.applyOffsetTrimming();
}
}
/**
* Apply pre-computed pagination offsets for thumbnail rendering
*/
private async applyOffsetTrimming(): Promise<void> {
// Trim from start
if (this.renderStartOffset > 0) {
await this.trimStartToOffset(this.renderStartOffset);
}
// Trim from end to match content length
let currentLength = await this.getContentLength();
while (currentLength > this.renderContentLength) {
await this.trimEndByOne();
currentLength = await this.getContentLength();
}
} }
private renderPaymentTerms(): TemplateResult { private renderPaymentTerms(): TemplateResult {

View File

@@ -25,12 +25,8 @@ import { DePage } from "./page.js";
import { DeContentInvoice } from "./contentinvoice.js"; import { DeContentInvoice } from "./contentinvoice.js";
import { demoFunc } from "./document.demo.js"; import { demoFunc } from "./document.demo.js";
import { dedocumentSharedStyle } from "../style.js";
export interface IPagePaginationInfo { import type { TInvoice } from "@tsclass/tsclass/dist_ts/finance/invoice.js";
pageNumber: number;
startOffset: number;
contentLength: number;
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@@ -46,25 +42,25 @@ export class DeDocument extends DeesElement {
type: String, type: String,
reflect: true, reflect: true,
}) })
accessor format: "a4" = "a4"; public format: "a4" = "a4";
@property({ @property({
type: Number, type: Number,
reflect: true, reflect: true,
}) })
accessor viewWidth: number = null; public viewWidth: number = null;
@property({ @property({
type: Number, type: Number,
reflect: true, reflect: true,
}) })
accessor viewHeight: number = null; public viewHeight: number = null;
@property({ @property({
type: Boolean, type: Boolean,
reflect: true, reflect: true,
}) })
accessor printMode = false; printMode = false;
@property({ @property({
type: Object, type: Object,
@@ -77,7 +73,7 @@ export class DeDocument extends DeesElement {
} }
}, },
}) })
accessor letterData: plugins.tsclass.business.TLetter; public letterData: plugins.tsclass.business.TLetter;
@property({ @property({
type: Object, type: Object,
@@ -90,15 +86,9 @@ export class DeDocument extends DeesElement {
} }
}, },
}) })
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings = public documentSettings: plugins.shared.interfaces.IDocumentSettings =
defaultDocumentSettings; defaultDocumentSettings;
@property({ type: Number })
accessor zoomLevel: number = null; // null = auto-fit, otherwise percentage (e.g., 100 = 100%)
@property({ type: Number })
accessor pageGap: number = 16; // pixels between pages
constructor() { constructor() {
super(); super();
domtools.DomTools.setupDomTools(); domtools.DomTools.setupDomTools();
@@ -106,6 +96,7 @@ export class DeDocument extends DeesElement {
public static styles = [ public static styles = [
domtools.elementBasic.staticStyles, domtools.elementBasic.staticStyles,
dedocumentSharedStyle,
css` css`
:host { :host {
display: block; display: block;
@@ -148,12 +139,10 @@ export class DeDocument extends DeesElement {
null; null;
public latestRenderedLetterData: plugins.tsclass.business.TLetter = null; public latestRenderedLetterData: plugins.tsclass.business.TLetter = null;
public cleanupStore: any[] = []; public cleanupStore: any[] = [];
public paginationInfo: IPagePaginationInfo[] = [];
public async renderDocument() { public async renderDocument() {
this.latestDocumentSettings = this.documentSettings; this.latestDocumentSettings = this.documentSettings;
this.latestRenderedLetterData = this.letterData; this.latestRenderedLetterData = this.letterData;
this.paginationInfo = [];
const cleanUpStoreCurrentRender = []; const cleanUpStoreCurrentRender = [];
const cleanUpStoreNextRender = []; const cleanUpStoreNextRender = [];
@@ -170,8 +159,7 @@ export class DeDocument extends DeesElement {
// lets append the content // lets append the content
const content: DeContentInvoice = new DeContentInvoice(); const content: DeContentInvoice = new DeContentInvoice();
cleanUpStoreCurrentRender.push(content); cleanUpStoreCurrentRender.push(content);
content.letterData = this content.letterData = this.letterData as unknown as TInvoice;
.letterData as unknown as plugins.tsclass.finance.TInvoice;
content.documentSettings = this.documentSettings; content.documentSettings = this.documentSettings;
document.body.appendChild(content); document.body.appendChild(content);
@@ -206,16 +194,7 @@ export class DeDocument extends DeesElement {
newPageOverflows = await newPage.checkOverflow(); newPageOverflows = await newPage.checkOverflow();
} }
currentContentOffset = await currentContent.getContentLength(); currentContentOffset = await currentContent.getContentLength();
const pageStartOffset = overallContentOffset;
overallContentOffset = overallContentOffset + currentContentOffset; overallContentOffset = overallContentOffset + currentContentOffset;
// Track pagination info for this page
this.paginationInfo.push({
pageNumber: pageCounter,
startOffset: pageStartOffset,
contentLength: currentContentOffset,
});
if (trimmed === 0) { if (trimmed === 0) {
complete = true; complete = true;
} }
@@ -246,21 +225,10 @@ export class DeDocument extends DeesElement {
if (!this.printMode) { if (!this.printMode) {
const betweenPagesSpacerDiv = document.createElement("div"); const betweenPagesSpacerDiv = document.createElement("div");
betweenPagesSpacerDiv.classList.add("betweenPagesSpacer"); betweenPagesSpacerDiv.classList.add("betweenPagesSpacer");
betweenPagesSpacerDiv.style.height = `${this.pageGap}px`;
documentContainer.appendChild(betweenPagesSpacerDiv); documentContainer.appendChild(betweenPagesSpacerDiv);
} }
} }
this.adjustDePageScaling(); this.adjustDePageScaling();
// Emit event with pagination info for thumbnails
this.dispatchEvent(new CustomEvent('pagination-complete', {
detail: {
pageCount: pageCounter,
paginationInfo: this.paginationInfo,
},
bubbles: true,
composed: true,
}));
} }
async updated( async updated(
@@ -283,25 +251,10 @@ export class DeDocument extends DeesElement {
if ( if (
changedProperties.has("viewHeight") || changedProperties.has("viewHeight") ||
changedProperties.has("viewWidth") || changedProperties.has("viewWidth")
changedProperties.has("zoomLevel")
) { ) {
this.adjustDePageScaling(); this.adjustDePageScaling();
} }
if (changedProperties.has("pageGap")) {
this.updatePageGaps();
}
}
/**
* Update page gap spacing without re-rendering document
*/
private updatePageGaps(): void {
const spacers = this.shadowRoot.querySelectorAll(".betweenPagesSpacer");
spacers.forEach((spacer: HTMLElement) => {
spacer.style.height = `${this.pageGap}px`;
});
} }
private adjustDePageScaling() { private adjustDePageScaling() {
@@ -312,11 +265,8 @@ export class DeDocument extends DeesElement {
// Find all DePage instances within this DeDocument // Find all DePage instances within this DeDocument
const pages = this.shadowRoot.querySelectorAll("dedocument-page"); const pages = this.shadowRoot.querySelectorAll("dedocument-page");
// Update each DePage instance's viewHeight, viewWidth, and zoomLevel // Update each DePage instance's viewHeight and viewWidth
pages.forEach((page: DePage) => { pages.forEach((page: DePage) => {
// Pass manual zoom level if set
page.manualZoomLevel = this.zoomLevel;
if (this.viewHeight) { if (this.viewHeight) {
page.viewHeight = this.viewHeight; page.viewHeight = this.viewHeight;
} }
@@ -325,25 +275,4 @@ export class DeDocument extends DeesElement {
} }
}); });
} }
/**
* Set zoom level manually. Pass null to return to auto-fit mode.
* @param level - Zoom percentage (e.g., 100 for 100%) or null for auto-fit
*/
public setZoomLevel(level: number | null): void {
this.zoomLevel = level;
this.adjustDePageScaling();
}
/**
* Get the current effective zoom percentage
*/
public getEffectiveZoom(): number {
if (this.zoomLevel !== null) {
return this.zoomLevel;
}
// Calculate auto-fit zoom percentage
const scale = this.viewWidth / plugins.shared.A4_WIDTH;
return Math.round(scale * 100);
}
} }

View File

@@ -31,25 +31,25 @@ export class DeLetterHeader extends DeesElement {
type: Object, type: Object,
reflect: true, reflect: true,
}) })
accessor letterData: plugins.tsclass.finance.TInvoice; public letterData: plugins.tsclass.finance.TInvoice;
@property({ @property({
type: Number, type: Number,
reflect: true, reflect: true,
}) })
accessor pageNumber: number = 1; public pageNumber: number = 1;
@property({ @property({
type: Number, type: Number,
reflect: true, reflect: true,
}) })
accessor pageTotalNumber: number = 1; public pageTotalNumber: number = 1;
@property({ @property({
type: Object, type: Object,
reflect: true, reflect: true,
}) })
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings; public documentSettings: plugins.shared.interfaces.IDocumentSettings;
constructor() { constructor() {
super(); super();
@@ -101,7 +101,7 @@ export class DeLetterHeader extends DeesElement {
]; ];
private renderDeliveryDate(from: Date, to: Date): TemplateResult { private renderDeliveryDate(from: Date, to: Date): TemplateResult {
if (this.letterData.accountingDocType !== "invoice") return null; if (this.letterData.type !== "invoice") return null;
const dateFormat = new Intl.DateTimeFormat( const dateFormat = new Intl.DateTimeFormat(
this.documentSettings.languageCode, this.documentSettings.languageCode,
{ dateStyle: this.documentSettings.dateStyle } { dateStyle: this.documentSettings.dateStyle }
@@ -174,7 +174,7 @@ export class DeLetterHeader extends DeesElement {
${this.letterData.to.registrationDetails.vatId || "not provided"} ${this.letterData.to.registrationDetails.vatId || "not provided"}
<!-- TODO: Make use of components --> <!-- TODO: Make use of components -->
${this.letterData.accountingDocType === "invoice" ${this.letterData.type === "invoice"
? html` <div class="label"> ? html` <div class="label">
${plugins.shared.translation.translate( ${plugins.shared.translation.translate(
this.documentSettings.languageCode, this.documentSettings.languageCode,

View File

@@ -12,6 +12,7 @@ import {
import * as plugins from "../plugins.js"; import * as plugins from "../plugins.js";
import { defaultDocumentSettings } from "./document.js"; import { defaultDocumentSettings } from "./document.js";
import { dedocumentSharedStyle } from "../style.js";
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
@@ -27,49 +28,46 @@ export class DePage extends DeesElement {
@property({ @property({
type: Number, type: Number,
}) })
accessor viewWidth: number = null; viewWidth: number = null;
@property({ @property({
type: Number, type: Number,
}) })
accessor viewHeight: number = null; viewHeight: number = null;
@property({ @property({
type: String, type: String,
}) })
accessor format: "a4" = "a4"; public format: "a4" = "a4";
@property({ @property({
type: Number, type: Number,
}) })
accessor pageNumber: number = 1; public pageNumber: number = 1;
@property({ @property({
type: Number, type: Number,
}) })
accessor pageTotalNumber: number = 1; public pageTotalNumber: number = 1;
@property({ @property({
type: Object, type: Object,
}) })
accessor letterData: tsclass.business.TLetter = null; public letterData: tsclass.business.TLetter = null;
@property({ @property({
type: Boolean, type: Boolean,
reflect: true, reflect: true,
}) })
accessor printMode = false; printMode = false;
@property({ @property({
type: Object, type: Object,
reflect: true, reflect: true,
}) })
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings = public documentSettings: plugins.shared.interfaces.IDocumentSettings =
defaultDocumentSettings; defaultDocumentSettings;
@property({ type: Number })
accessor manualZoomLevel: number = null; // null = auto-fit, otherwise percentage
constructor() { constructor() {
super(); super();
domtools.DomTools.setupDomTools(); domtools.DomTools.setupDomTools();
@@ -77,6 +75,7 @@ export class DePage extends DeesElement {
public static styles = [ public static styles = [
domtools.elementBasic.staticStyles, domtools.elementBasic.staticStyles,
dedocumentSharedStyle,
css` css`
:host { :host {
display: block; display: block;
@@ -135,14 +134,17 @@ export class DePage extends DeesElement {
]; ];
public render(): TemplateResult { public render(): TemplateResult {
const theme = this.documentSettings.theme;
return html` return html`
<style> <style>
:host { :host {
${theme?.colorPrimaryForeground ? `--theme-color-primary-fg: ${theme.colorPrimaryForeground};` : ''} --theme-color-primary-fg: ${this.documentSettings.theme
${theme?.colorPrimaryBackground ? `--theme-color-primary-bg: ${theme.colorPrimaryBackground};` : ''} ?.colorPrimaryForeground};
${theme?.colorAccentForeground ? `--theme-color-accent-fg: ${theme.colorAccentForeground};` : ''} --theme-color-primary-bg: ${this.documentSettings.theme
${theme?.colorAccentBackground ? `--theme-color-accent-bg: ${theme.colorAccentBackground};` : ''} ?.colorPrimaryBackground};
--theme-color-accent-fg: ${this.documentSettings.theme
?.colorAccentForeground};
--theme-color-accent-bg: ${this.documentSettings.theme
?.colorAccentBackground};
} }
.page { .page {
@@ -151,17 +153,21 @@ export class DePage extends DeesElement {
} }
.page:not(.page--first) { .page:not(.page--first) {
background-image: ${theme?.pageBackground ?? "none"}; background-image: ${this.documentSettings.theme?.pageBackground ??
"none"};
} }
.page.page--first { .page.page--first {
background-image: ${theme?.coverPageBackground ?? theme?.pageBackground ?? "none"}; background-image: ${this.documentSettings.theme
?.coverPageBackground ??
this.documentSettings.theme?.pageBackground ??
"none"};
} }
</style> </style>
<div id="scaleWrapper"> <div id="scaleWrapper">
<dedocument-pagecontainer .printMode=${this.printMode}> <dedocument-pagecontainer .printMode=${this.printMode}>
<div <div
class="page ${this.pageNumber === 1 class="page page__background ${this.pageNumber === 1
? "page--first" ? "page--first"
: ""}" : ""}"
></div> ></div>
@@ -249,8 +255,7 @@ export class DePage extends DeesElement {
super.updated(changedProperties); super.updated(changedProperties);
if ( if (
changedProperties.has("viewHeight") || changedProperties.has("viewHeight") ||
changedProperties.has("viewWidth") || changedProperties.has("viewWidth")
changedProperties.has("manualZoomLevel")
) { ) {
this.adjustScaling(); this.adjustScaling();
} }
@@ -263,21 +268,15 @@ export class DePage extends DeesElement {
if (!scaleWrapper) return; if (!scaleWrapper) return;
let scale = 1; let scale = 1;
if (this.viewHeight) {
// If manual zoom is set, use it directly
if (this.manualZoomLevel !== null) {
scale = this.manualZoomLevel / 100;
} else if (this.viewHeight) {
// Auto-fit to height
scale = this.viewHeight / plugins.shared.A4_HEIGHT; scale = this.viewHeight / plugins.shared.A4_HEIGHT;
} else if (this.viewWidth) { } else if (this.viewWidth) {
// Auto-fit to width
scale = this.viewWidth / plugins.shared.A4_WIDTH; scale = this.viewWidth / plugins.shared.A4_WIDTH;
} }
scaleWrapper.style.transform = `scale(${scale})`; scaleWrapper.style.transform = `scale(${scale})`;
// Adjust the outer dimensions so they match the scaled content // Adjust the outer dimensions so they match the scaled content
this.style.width = `${plugins.shared.A4_WIDTH * scale}px`; this.style.width = `${plugins.shared.A4_WIDTH * scale}px`;
this.style.height = `${plugins.shared.A4_HEIGHT * scale}px`; this.style.height = `${plugins.shared.A4_HEIGHT * scale}px`;
} }

View File

@@ -27,12 +27,12 @@ export class DePageContainer extends DeesElement {
@property({ @property({
type: String, type: String,
}) })
accessor format: "a4" = "a4"; public format: "a4" = "a4";
@property({ @property({
type: Boolean, type: Boolean,
}) })
accessor printMode = false; public printMode = false;
constructor() { constructor() {
super(); super();
@@ -50,7 +50,6 @@ export class DePageContainer extends DeesElement {
position: relative; position: relative;
border-radius: 3px; border-radius: 3px;
overflow: hidden; overflow: hidden;
background: var(--text-bg-color, #ffffff);
} }
`, `,
]; ];

View File

@@ -28,23 +28,23 @@ export class DePageContent extends DeesElement {
@property({ @property({
type: Number, type: Number,
}) })
accessor letterData: plugins.tsclass.business.TLetter; public letterData: plugins.tsclass.business.TLetter;
@property({ @property({
type: Number, type: Number,
}) })
accessor pageNumber: number = 1; public pageNumber: number = 1;
@property({ @property({
type: Number, type: Number,
}) })
accessor pageTotalNumber: number = 1; public pageTotalNumber: number = 1;
@property({ @property({
type: Object, type: Object,
reflect: true, reflect: true,
}) })
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings; public documentSettings: plugins.shared.interfaces.IDocumentSettings;
constructor() { constructor() {
super(); super();

View File

@@ -27,23 +27,23 @@ export class DePageFooter extends DeesElement {
@property({ @property({
type: Object, type: Object,
}) })
accessor letterData: plugins.tsclass.business.TLetter; letterData: plugins.tsclass.business.TLetter;
@property({ @property({
type: Object, type: Object,
reflect: true, reflect: true,
}) })
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings; documentSettings: plugins.shared.interfaces.IDocumentSettings;
@property({ @property({
type: Number, type: Number,
}) })
accessor pageNumber: number = 1; public pageNumber: number = 1;
@property({ @property({
type: Number, type: Number,
}) })
accessor pageTotalNumber: number = 1; public pageTotalNumber: number = 1;
constructor() { constructor() {
super(); super();

View File

@@ -28,23 +28,23 @@ export class DePageHeader extends DeesElement {
@property({ @property({
type: Object, type: Object,
}) })
accessor letterData: plugins.tsclass.business.TLetter = null; public letterData: plugins.tsclass.business.TLetter = null;
@property({ @property({
type: Object, type: Object,
reflect: true, reflect: true,
}) })
accessor documentSettings: plugins.shared.interfaces.IDocumentSettings; documentSettings: plugins.shared.interfaces.IDocumentSettings;
@property({ @property({
type: Number, type: Number,
}) })
accessor pageNumber: number = 1; public pageNumber: number = 1;
@property({ @property({
type: Number, type: Number,
}) })
accessor pageTotalNumber: number = 1; public pageTotalNumber: number = 1;
constructor() { constructor() {
super(); super();

View File

@@ -33,25 +33,25 @@ export class DedocumentPaymentCode extends DeesElement {
]; ];
@query("canvas") @query("canvas")
accessor canvasEl!: HTMLCanvasElement; private canvasEl!: HTMLCanvasElement;
@property() @property()
accessor bic: string; public bic: string;
@property() @property()
accessor name: string; public name: string;
@property() @property()
accessor iban: string; public iban: string;
@property() @property()
accessor currency: string; public currency: string;
@property({ type: Number }) @property({ type: Number })
accessor totalGross: number; public totalGross: number;
@property() @property()
accessor reference: string; public reference: string;
private updateQRCode(): void { private updateQRCode(): void {
if (!this.canvasEl) return; if (!this.canvasEl) return;

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@ export const dedocumentSharedStyle = css`
--text-bg-color: var(--color-primary-bg); --text-bg-color: var(--color-primary-bg);
--label-fg: var(--color-dark); --label-fg: var(--color-dark);
--label-bg: var(--color-grey); --label-bg: var(--color-grey);
--footer-separator-bg-color: var(--color-accent-bg); --footer-separator-bg-color: var(--color-accent);
--footer-separator-fg-color: var(--color-light); --footer-separator-fg-color: var(--color-light);
/* Functional variables */ /* Functional variables */
@@ -32,6 +32,7 @@ export const dedocumentSharedStyle = css`
--text-font-size: var(--theme-text-font-size, 12px); --text-font-size: var(--theme-text-font-size, 12px);
color: var(--text-fg-color); color: var(--text-fg-color);
background: var(--text-bg-color);
font-family: var(--text-font-family); font-family: var(--text-font-family);
font-size: var(--text-font-size); font-size: var(--text-font-size);
} }

View File

@@ -1,10 +1,13 @@
{ {
"compilerOptions": { "compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
"target": "ES2022", "target": "ES2022",
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"esModuleInterop": true, "esModuleInterop": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"baseUrl": ".",
"paths": { "paths": {
"undefined": [ "undefined": [
"./ts_web/index.js" "./ts_web/index.js"