diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-thumb/pdf-viewer-thumb.component.stories.ts b/lib/core/src/lib/viewer/components/pdf-viewer-thumb/pdf-viewer-thumb.component.stories.ts new file mode 100644 index 0000000000..30d3e6ae7e --- /dev/null +++ b/lib/core/src/lib/viewer/components/pdf-viewer-thumb/pdf-viewer-thumb.component.stories.ts @@ -0,0 +1,119 @@ +/*! + * @license + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { applicationConfig, Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { PdfThumbComponent } from './pdf-viewer-thumb.component'; +import { provideStoryCore } from '../../../stories/core-story.providers'; + +const buildMockPage = (id: number, color: string) => ({ + id, + getWidth: () => 91, + getHeight: () => 114, + getPage: () => + Promise.resolve({ + getViewport: ({ scale }: { scale: number }) => ({ width: 91 * scale, height: 114 * scale }), + render: ({ canvasContext, viewport }: { canvasContext: CanvasRenderingContext2D; viewport: { width: number; height: number } }) => { + canvasContext.fillStyle = color; + canvasContext.fillRect(0, 0, viewport.width, viewport.height); + canvasContext.fillStyle = '#333'; + canvasContext.font = '14px sans-serif'; + canvasContext.fillText(`Page ${id}`, 16, 24); + return { promise: Promise.resolve() }; + } + }) +}); + +const EMPTY_PAGE = { + id: 0, + getWidth: () => 91, + getHeight: () => 114, + getPage: () => new Promise(() => undefined) +}; + +type PdfThumbStoryArgs = PdfThumbComponent & { + selected?: boolean; +}; + +const meta: Meta = { + component: PdfThumbComponent, + title: 'Core/Viewer/PDF Viewer Thumb', + decorators: [ + moduleMetadata({ + imports: [PdfThumbComponent] + }), + applicationConfig({ + providers: [...provideStoryCore()] + }) + ], + parameters: { + docs: { + description: { + component: `Single thumbnail tile rendered for a page in the PDF Viewer thumbnails panel. Renders a small canvas preview from the supplied \`page\` descriptor.` + } + } + }, + argTypes: { + page: { + control: false, + description: 'Page descriptor providing `getPage()`, `getWidth()` and `getHeight()` used to render the thumbnail.', + table: { type: { summary: 'PdfThumbnailPage' } } + }, + selected: { + control: 'boolean', + description: 'Visual flag toggled by the parent thumbnails list when the page is the active one.', + table: { category: 'Storybook Helpers' } + } + } +}; + +export default meta; +type Story = StoryObj; + +export const Empty: Story = { + args: { + page: EMPTY_PAGE, + selected: false + }, + render: (args) => ({ + props: args, + template: `` + }) +}; + +export const Populated: Story = { + args: { + page: buildMockPage(1, '#e3f2fd'), + selected: false + }, + render: (args) => ({ + props: args, + template: `` + }) +}; + +export const Selected: Story = { + args: { + page: buildMockPage(2, '#bbdefb'), + selected: true + }, + render: (args) => ({ + props: args, + template: `
+ +
` + }) +}; diff --git a/lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.stories.ts b/lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.stories.ts new file mode 100644 index 0000000000..63e0157e65 --- /dev/null +++ b/lib/core/src/lib/viewer/components/pdf-viewer-thumbnails/pdf-viewer-thumbnails.component.stories.ts @@ -0,0 +1,127 @@ +/*! + * @license + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { applicationConfig, Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { PdfThumbListComponent } from './pdf-viewer-thumbnails.component'; +import { provideStoryCore } from '../../../stories/core-story.providers'; + +const buildPdfViewerMock = (totalPages: number) => { + const pageHandlers: Array<(event: { pageNumber: number }) => void> = []; + + const buildMockPage = (id: number) => ({ + id, + width: 91, + height: 114 + }); + + const buildPageProxy = (id: number) => ({ + getViewport: ({ scale }: { scale: number }) => ({ width: 91 * scale, height: 114 * scale }), + render: ({ canvasContext, viewport }: { canvasContext: CanvasRenderingContext2D; viewport: { width: number; height: number } }) => { + canvasContext.fillStyle = id % 2 ? '#e3f2fd' : '#f3e5f5'; + canvasContext.fillRect(0, 0, viewport.width, viewport.height); + canvasContext.fillStyle = '#333'; + canvasContext.font = '12px sans-serif'; + canvasContext.fillText(`Page ${id}`, 12, 20); + return { promise: Promise.resolve() }; + } + }); + + return { + currentPageNumber: 1, + pagesCount: totalPages, + _pages: Array.from({ length: totalPages }, (_, i) => buildMockPage(i + 1)), + pdfDocument: { + getPage: (id: number) => Promise.resolve(buildPageProxy(id)) + }, + eventBus: { + on: (event: string, handler: (e: { pageNumber: number }) => void) => { + if (event === 'pagechanging') { + pageHandlers.push(handler); + } + }, + off: () => undefined + } + } as any; +}; + +type PdfThumbnailsStoryArgs = PdfThumbListComponent & { + pageCount?: number; +}; + +const meta: Meta = { + component: PdfThumbListComponent, + title: 'Core/Viewer/PDF Viewer Thumbnails', + decorators: [ + moduleMetadata({ + imports: [PdfThumbListComponent] + }), + applicationConfig({ + providers: [...provideStoryCore()] + }) + ], + parameters: { + docs: { + description: { + component: `Side panel listing thumbnails for every page of the PDF currently rendered by the \`adf-pdf-viewer\`. Stories use a lightweight mock of the underlying \`PDFViewer\`.` + } + } + }, + argTypes: { + pdfViewer: { + control: false, + description: 'Reference to the underlying pdf.js `PDFViewer` instance. Required input.', + table: { type: { summary: 'PDFViewer' } } + }, + pageCount: { + control: { type: 'number', min: 0, max: 50 }, + description: 'Number of pages used to build the mock `PDFViewer`.', + table: { category: 'Storybook Helpers' } + }, + close: { + action: 'close', + description: 'Emitted when the user requests to close the thumbnails panel (e.g. via Escape).', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + } + }, + args: { + pageCount: 5 + }, + render: (args) => ({ + props: { + ...args, + pdfViewer: buildPdfViewerMock(args.pageCount ?? 0) + }, + template: `
+ +
` + }) +}; + +export default meta; +type Story = StoryObj; + +export const Empty: Story = { + args: { pageCount: 0 } +}; + +export const Populated: Story = { + args: { pageCount: 8 } +}; + +export const Selected: Story = { + args: { pageCount: 4 } +}; diff --git a/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.stories.ts b/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.stories.ts new file mode 100644 index 0000000000..9e97b9bd2b --- /dev/null +++ b/lib/core/src/lib/viewer/components/pdf-viewer/pdf-viewer.component.stories.ts @@ -0,0 +1,142 @@ +/*! + * @license + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { applicationConfig, Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { PdfViewerComponent } from './pdf-viewer.component'; +import { provideStoryCore } from '../../../stories/core-story.providers'; + +const SAMPLE_PDF_URL = 'https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf'; + +const meta: Meta = { + component: PdfViewerComponent, + title: 'Core/Viewer/PDF Viewer', + decorators: [ + moduleMetadata({ + imports: [PdfViewerComponent] + }), + applicationConfig({ + providers: [...provideStoryCore()] + }) + ], + parameters: { + docs: { + description: { + component: `Renders PDF documents using \`pdfjs-dist\`. Stories require the pdf.js worker assets to be served by Storybook; see the Viewer documentation for details.` + } + } + }, + argTypes: { + urlFile: { + control: 'text', + description: 'External URL of the PDF file to load.', + table: { type: { summary: 'string' } } + }, + blobFile: { + control: false, + description: 'In-memory Blob containing the PDF binary content.', + table: { type: { summary: 'Blob' } } + }, + fileName: { + control: 'text', + description: 'Optional file name displayed by the toolbar.', + table: { type: { summary: 'string' } } + }, + showToolbar: { + control: 'boolean', + description: 'Whether to show the PDF viewer toolbar (page navigation, zoom, ...).', + table: { type: { summary: 'boolean' }, defaultValue: { summary: 'true' } } + }, + allowThumbnails: { + control: 'boolean', + description: 'Whether the thumbnails side panel can be opened.', + table: { type: { summary: 'boolean' }, defaultValue: { summary: 'false' } } + }, + cacheType: { + control: 'text', + description: 'Optional `Cache-Control` header value used when loading via URL.', + table: { type: { summary: 'string' } } + }, + rendered: { + action: 'rendered', + description: 'Emitted after a page is rendered.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + }, + error: { + action: 'error', + description: 'Emitted when the PDF fails to load.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + }, + close: { + action: 'close', + description: 'Emitted when the password dialog is dismissed without supplying a password.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + }, + pagesLoaded: { + action: 'pagesLoaded', + description: 'Emitted when all pages of the document finish loading.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + } + }, + args: { + urlFile: SAMPLE_PDF_URL, + fileName: 'sample.pdf', + showToolbar: true, + allowThumbnails: false + }, + render: (args) => ({ + props: args, + template: `
+ +
` + }) +}; + +export default meta; +type Story = StoryObj; + +export const WithSamplePdf: Story = { + args: { + urlFile: SAMPLE_PDF_URL, + showToolbar: true, + allowThumbnails: false + } +}; + +export const WithThumbnailsEnabled: Story = { + args: { + urlFile: SAMPLE_PDF_URL, + showToolbar: true, + allowThumbnails: true + } +}; + +export const ToolbarHidden: Story = { + args: { + urlFile: SAMPLE_PDF_URL, + showToolbar: false, + allowThumbnails: false + } +};