# @yuuvis/media-viewer

![npm](https://img.shields.io/npm/v/@yuuvis/media-viewer.svg)
![npm](https://img.shields.io/npm/l/@yuuvis/media-viewer.svg)
![npm](https://img.shields.io/npm/dm/@yuuvis/media-viewer.svg)

## Table of Contents

- [Installation](#installation)
- [Usage](#Usage)
  - [General use](#general-use)
  - [AUDIO](#audio)
    - [Supported Types](#supported-types)
    - [How to use](#how-to-use-audio-viewer)
  - [EMAIL](#email)
    - [Supported Types](#supported-types-1)
    - [How to use Email Viewer](#how-to-use-email-viewer)
  - [IMAGE](#image)
    - [Supported Types](#supported-types-2)
    - [How to use Image Viewer](#how-to-use-image-viewer)
  - [OFFICE](#office)
    - [Supported Types](#supported-types-3)
    - [How to use Office Viewer](#how-to-use-office-viewer)
  - [PDF](#pdf)
    - [Supported Types](#supported-types-4)
    - [How to use PDF Viewer](#how-to-use-pdf-viewer)
  - [TEXT](#text)
    - [Supported Types](#supported-types-5)
    - [How to use Text Viewer](#how-to-use-text-viewer)
  - [VIDEO](#video)
    - [Supported Types](#supported-types-6)
    - [How to use Video Viewer](#how-to-use-video-viewer)
- [Extend / Customize Viewer](#extend--customize-viewer)
  - [Types](#types)
  - [Override Default Viewer](#override-default-viewer)
  - [Reset to Default Viewer](#reset-to-default-viewer)
  - [Override Supported Types](#override-supported-types)
  - [Extend Supported Types](#extend-supported-types)
  - [MediaViewerService](#mediaviewerservice)
- [Schematics](schematics/README.md)
- [Styling](#styling)
- [Development](#development)
  - [Extend config via provider](#extend-config-via-provider)
- [License](#license)

## Installation

Install the package using npm:

```sh
npm install @yuuvis/media-viewer --save
```

## Usage

This library provides multiple viewers for displaying various media types based on their MIME types.

### General Use

This is a convenience wrapper for all types that selects the appropriate viewer based on the provided type.

```typescript
{
  type: string | unknown; // Required
  id: string; // Required
  metadata: T;
  src: string | null;
}
```

```typescript
import { YuvMediaViewerComponent } from '@yuuvis/media-viewer';

@Component({
  ...
  imports: [YuvMediaViewerComponent],
  template: `<yuv-media-viewer [id]="id()" [src]="src()" [type]="type()" [metadata]="metadata()"></yuv-media-viewer>`
})
...
id = signal(UUID);
src = signal('https://path.to.the.document.com');
type = signal('application/vnd.openxmlformats-officedocument.wordprocessingml.document');
metadata = signal({});
```

### AUDIO

Audio playback is handled using the native [HTMLAudioElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement), enhanced with a visualizer.

#### Supported Types

```json
{
  "mimeType": ["audio/mp3", "audio/webm", "audio/ogg", "audio/mpeg"],
  "type": "AUDIO"
}
```

#### How to Use Audio Viewer

```typescript
AudioMetadata {
  type: 'audio';
  mode?: 'oscilloscope' | 'frequency' | 'off';
} // Default: 'frequency'
```

```typescript
{
  type: string;
  id: string; // Required
  metadata: AudioMetadata;
  src: string; // Required
}
```

```typescript
import { AudioComponent } from '@yuuvis/media-viewer';

@Component({
  ...
  imports: [AudioComponent],
  template: `<yuv-audio [id]="id()" [src]="src()" [type]="type()"></yuv-audio>`
})
...
id = signal(UUID);
src = signal('source');
type = signal('audio/mp3');
```

### EMAIL

The UI renders the parsed email and allows users to select and view attachments (beta).

#### Supported Types

```json
{
  "mimeType": ["message/rfc822", "application/vnd.ms-outlook"],
  "type": "EMAIL"
}
```

#### How to use Email Viewer

```typescript
EmailMetadata {
  type: 'EMAIL';
  header: Email;
  attachments: string[];
  theme: string;
}
```

```typescript
{
  type: string;
  id: string; // Required
  metadata: EmailMetadata;
  src: string; // Required
}
```

```typescript
import { MailComponent } from '@yuuvis/media-viewer';

@Component({
  ...
  imports: [MailComponent],
  template: `<yuv-mail [id]="id()" [src]="src()" [type]="type()" [metadata]="metadata()"></yuv-mail>`
})
...
id = signal(UUID);
src = signal('source');
type = signal('message/rfc822');
metadata = signal(this.emailMetadata);
```

### IMAGE

The image leverages the native [HTMLImageElement](https://developer.mozilla.org/de/docs/Web/API/HTMLImageElement) and extends it with zoom and rotate capabilities, while also incorporating accessibility features.

#### Supported Types

```json
{
  "mimeType": ["image/tiff", "image/jpeg", "image/png", "image/apng", "image/gif", "image/svg+xml", "image/webp"],
  "type": "IMAGE"
}
```

#### How to use Image Viewer

```typescript
{
  type: string;
  id: string; // Required
  metadata: T;
  src: string; // Required
}
```

```typescript
import { ImageComponent } from '@yuuvis/media-viewer';

@Component({
  ...
  imports: [ImageComponent],
  template: `<yuv-image [id]="id()" [src]="src()" [type]="type()" [metadata]="metadata()"></yuv-image>`
})
...
id = signal(UUID);
src = signal('source');
type = signal('image/jpeg');
metadata = signal({ disableMenu: false })
```

### OFFICE

To properly use the Office viewer, a backend integrated with an MS Office 365 solution is required.

#### Supported Types

```json
{
  "mimeType": [
    "application/msword",
    "application/vnd.ms-excel",
    "application/vnd.ms-powerpoint",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
    "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    "application/vnd.openxmlformats-officedocument.presentationml.template",
    "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
    "application/vnd.ms-word.document.macroEnabled.12",
    "application/vnd.ms-word.template.macroEnabled.12",
    "application/vnd.ms-excel.sheet.macroEnabled.12",
    "application/vnd.ms-excel.template.macroEnabled.12",
    "application/vnd.ms-excel.addin.macroEnabled.12",
    "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
    "application/vnd.ms-powerpoint.addin.macroEnabled.12",
    "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
    "application/vnd.ms-powerpoint.template.macroEnabled.12",
    "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"
  ],
  "type": "OFFICE"
}
```

#### How to use Office Viewer

```typescript
OfficeMetadata {
  type: 'office';
  id: string;
  dmsObject: Record<string, unknown>;
  editable?: boolean;
  sendEvent?: boolean;
  version?: number;
  user: MetadatUser;
}
```

```typescript
{
  type: string;
  id: string(required);
  metadata: OfficeMetadata;
  src: string(required);
}
```

```typescript
import { OfficeComponent } from '@yuuvis/media-viewer';

@Component({
  ...
  imports: [OfficeComponent],
  template: `<yuv-office [id]="id()" [src]="src()" [type]="type()" [metadata]="metadata()"></yuv-office>`
})
...
id = signal(UUID)
src = signal('source')
type = signal('application/vnd.openxmlformats-officedocument.wordprocessingml.document')
metadata = signal({id:UUID,dmsObject:dmsObject, user:currentUser})
```

### PDF

The PDF viewer is powered by [ngx-extended-pdf-viewer](https://pdfviewer.net/extended-pdf-viewer/simple).

#### Supported Types

```json
{
  "mimeType": ["application/pdf"],
  "type": "PDF"
}
```

#### How to use PDF Viewer

```typescript
PdfMetadata {
  theme?: string;
  showToolbar?: boolean; // Default: true
}
```

```typescript
{
  type: string;
  id: string(required);
  metadata: PdfMetadata;
  src: string(required);
}
```

```typescript
import { PdfComponent } from '@yuuvis/media-viewer';

@Component({
  ...
  imports: [PdfComponent],
  template: `<yuv-pdf [id]="id()" [src]="src()" [type]="type()" [metadata]="metadata()"></yuv-pdf>`,
})
...
id = signal(UUID)
src = signal('source')
type = signal('application/pdf')
metadata = signal({theme:'dark', showToolbar: true})
```

```json
// You might need to add
{
  "glob": "**/*",
  "input": "node_modules/ngx-extended-pdf-viewer/bleeding-edge/",
  "output": "/bleeding-edge/"
}
```

### TEXT

The Text viewer is powered by [ngx-monaco-editor-v2](https://github.com/miki995/ngx-monaco-editor-v2?tab=readme-ov-file)

#### Supported Types

```json
{
  "mimeType": [
    "application/json",
    "text/plain",
    "text/xml",
    "text/java",
    "text/javascript",
    "application/javascript",
    "text/html",
    "text/markdown",
    "text/x-web-markdown",
    "text/x-markdown"
  ],
  "type": "TEXT"
}
```

#### How to use Text Viewer

```typescript
{
  type: string;
  id: string(required);
  metadata: {
    theme: 'light';
  }
  src: string(required);
}
```

```typescript
import { TextComponent } from '@yuuvis/media-viewer';

@Component({
  ...
  imports: [TextComponent],
  template: `<yuv-text [id]="id()" [src]="src()" [type]="type()" [metadata]="metadata()"></yuv-text>`,
})
...
id = signal(UUID)
src = signal('source')
type = signal('text/xml')
metadata = signal({theme:'dark'})

```

### VIDEO

Video utilizes the native [HTMLVideoElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement).

#### Supported Types

```json
{
  "mimeType": ["video/mp4", "video/webm", "video/ogg", "application/ogg"],
  "type": "VIDEO"
}
```

#### How to use Video Viewer

```typescript
{
  id: string(required);
  src: string(required);
  type: string;
}
```

```typescript
import { VideoComponent } from '@yuuvis/media-viewer';

@Component({
  ...
  imports: [VideoComponent],
  template: `<yuv-video [id]="id()" [src]="src()" [type]="type()"></yuv-video>`,
})
...
id = signal(UUID)
src = signal('source')
type = signal('video/mp4')
```

## Extend / Customize Viewer

### Types

```typescript
Array<{ mimeType: Array<string>; type: string }>;
```

### Override default viewer

If you prefer to use your own viewer for a specific type, you can override the default configuration as shown below:

```typescript
import { MediaViewerService } from '@yuuvis/media-viewer';
...
readonly #mediaViewerService = inject(MediaViewerService);

constructor() {
  this.#mediaViewerService.setCustomViewer([{'AUDIO': MyCustomImageViewerComponent}]);
}
```

#### Reset to Default Viewer

```typescript
import { MediaViewerService } from '@yuuvis/media-viewer';
...
readonly #mediaViewerService = inject(MediaViewerService);

constructor() {
  this.#mediaViewerService.resetViewers();
}
```

#### Override Supported Types

To customize the supported MIME types for a specific type, you can override the default configuration.

```typescript
import { MediaViewerService } from '@yuuvis/media-viewer';
...
readonly #mediaViewerService = inject(MediaViewerService);

constructor() {
  this.#mediaViewerService.setViewers({
    mimeType: ['image/jpeg', 'image/png'],
    type: 'IMAGE'
  });
}
```

#### Extend Supported Types

If you have a custom media type, you can easily add it to the configuration.

```typescript
import { MediaViewerService } from '@yuuvis/media-viewer';
...
readonly #mediaViewerService = inject(MediaViewerService);

constructor() {
  this.#mediaViewerService.setViewers({
    mimeType: ['fancy/stuff'],
    type: 'FANCY'
  });
  this.#mediaViewerService.setCustomViewer([{'FANCY': MyFancyViewerComponent}]);
}
```

or

```typescript
import { MediaViewerService } from '@yuuvis/media-viewer';
...
readonly #mediaViewerService = inject(MediaViewerService);

constructor() {
  this.#mediaViewerService.extendViewer<MyFancyViewerComponent>({ mimeType: ['fancy/stuff'], type: 'FANCY' }, MyFancyViewerComponent );
}
```

### MediaViewerService

The central service for managing viewer type mappings, component registry, and utility methods.

**Methods**:

- `getComponentType(type: string): KeyTypes` — Maps a MIME type to a viewer type
- `setViewers(customViewer: Viewer | Viewer[])` — Add custom MIME type mappings
- `setCustomViewer(components: TypedComponents | TypedComponents[])` — Register custom viewer components
- `extendViewer<T>(customViewer: Viewer, component: Type<T>)` — Convenience method to add both mapping and component
- `resetViewers()` — Reset to default viewers
- `setViewerSeize(width: number, height: number)` — Set the viewer dimensions
- `fileExtension(fileName: string | null): string` — Extracts the lowercase file extension from a filename (e.g. `"report.docx"` returns `"docx"`)
- `srcPath(id: string, officeEnv: { path: string; reloadOffset: number }, version?: number): string` — Builds the full content URL for a DMS object, resolving the host from the Office environment config or `window.location`

## Styling

If You want to customise some aspects of the viewer you can so by importing the media-viewer styles.

```CSS
  @use '@yuuvis/media-viewer/scss';
```

### Style Tokens

```CSS
  --ymv-text-color-subtle: light-dark(#5b5e65, #a8abb2);

  --ymv-surface: light-dark(#f8f9ff, #101419);
  --ymv-outline-variant: light-dark(#e0e2ea, #2d3136);
  --ymv-surface-frame:light-dark(#ebeef5, #1c2025)
  --ymv-primary: light-dark(#13629d, #9ccaff);
  --ymv-current-background: light-dark(#ebeef5, #1c2025);
  --ymv-hover-background: light-dark(#f1f3fb, #181c21);
  --ymv-text-color: light-dark(#181c21, #e0e2ea);
  --ymv-success: light-dark(#006b54, #9ee3b5);
  --ymv-progress-color: light-dark(hsl(290, 88%, 67%), #ad2c98);
  --ymv-progress-blend-color: light-dark(hsl(290, 88%, 67%), #ad2c98);


  --ymv-spacing-xs: 0.5rem;
  --ymv-sizing-s: 1.25rem;
  --ymv-sizing-l: 1.75rem;

  --ymv-border-radius: 50%;

  --ymv-font-subhead: 700 0.875rem / 1.4 sans-serif;
  --ymv-icon-color: light-dark(#181c21, #e0e2ea);
  --ymv-icon-color-disabled: light-dark(#7e868f, #a8adb4);
```

## Development

### Extend config via Provider

For development, you can set the configuration globally. For the Office viewer, you must specify the path to the Office 365 instance.

```typescript
export const environment = {
  production: false,
  mediaviewer: { office: { path: 'https://path.to.office-365.org' } }
};
```

```typescript
provideMediaViewer(environment.mediaviewer);
```

## License

MIT
