diff --git a/lib/core/src/lib/viewer/components/img-viewer/img-viewer.component.stories.ts b/lib/core/src/lib/viewer/components/img-viewer/img-viewer.component.stories.ts new file mode 100644 index 0000000000..3fbdfbe9d8 --- /dev/null +++ b/lib/core/src/lib/viewer/components/img-viewer/img-viewer.component.stories.ts @@ -0,0 +1,151 @@ +/*! + * @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 { ImgViewerComponent } from './img-viewer.component'; +import { provideStoryCore } from '../../../stories/core-story.providers'; + +const SAMPLE_IMAGE_URL = + 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/640px-PNG_transparency_demonstration_1.png'; +const LARGE_IMAGE_URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/2560px-Cat03.jpg'; + +const meta: Meta = { + component: ImgViewerComponent, + title: 'Core/Viewer/Image Viewer', + decorators: [ + moduleMetadata({ + imports: [ImgViewerComponent] + }), + applicationConfig({ + providers: [...provideStoryCore()] + }) + ], + parameters: { + docs: { + description: { + component: `Renders an image with zoom, rotation and (optional) crop / save editing actions powered by Cropper.js.` + } + } + }, + argTypes: { + urlFile: { + control: 'text', + description: 'External URL of the image to display.', + table: { type: { summary: 'string' } } + }, + blobFile: { + control: false, + description: 'In-memory Blob containing the image content.', + table: { type: { summary: 'Blob' } } + }, + fileName: { + control: 'text', + description: 'Optional file name used by the toolbar/aria labels.', + table: { type: { summary: 'string' } } + }, + showToolbar: { + control: 'boolean', + description: 'Whether to show the image viewer toolbar (zoom, rotate, ...).', + table: { type: { summary: 'boolean' }, defaultValue: { summary: 'true' } } + }, + readOnly: { + control: 'boolean', + description: 'When `true`, editing actions (rotate, crop, save) are hidden.', + table: { type: { summary: 'boolean' }, defaultValue: { summary: 'true' } } + }, + allowedEditActions: { + control: 'object', + description: 'Map controlling which editing actions are enabled. Example: `{ rotate: true, crop: false }`.', + table: { type: { summary: '{ [key: string]: boolean }' } } + }, + error: { + action: 'error', + description: 'Emitted when the image fails to load.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + }, + submit: { + action: 'submit', + description: 'Emitted with the edited Blob when the user saves the cropped/rotated image.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + }, + isSaving: { + action: 'isSaving', + description: 'Emits `true` while the edited image is being saved.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + }, + imageLoaded: { + action: 'imageLoaded', + description: 'Emitted once the underlying `` element finishes loading.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + } + }, + args: { + urlFile: SAMPLE_IMAGE_URL, + fileName: 'sample-image.png', + showToolbar: true, + readOnly: true, + allowedEditActions: { rotate: true, crop: true } + }, + render: (args) => ({ + props: args, + template: `
+ +
` + }) +}; + +export default meta; +type Story = StoryObj; + +export const SimpleImage: Story = { + args: { + urlFile: SAMPLE_IMAGE_URL, + readOnly: true + } +}; + +export const LargeImage: Story = { + args: { + urlFile: LARGE_IMAGE_URL, + readOnly: true + } +}; + +export const EditableWithRotateOnly: Story = { + args: { + urlFile: SAMPLE_IMAGE_URL, + readOnly: false, + allowedEditActions: { rotate: true, crop: false } + } +}; + +export const FullyEditable: Story = { + args: { + urlFile: SAMPLE_IMAGE_URL, + readOnly: false, + allowedEditActions: { rotate: true, crop: true } + } +}; diff --git a/lib/core/src/lib/viewer/components/media-player/media-player.component.stories.ts b/lib/core/src/lib/viewer/components/media-player/media-player.component.stories.ts new file mode 100644 index 0000000000..41f67b4c69 --- /dev/null +++ b/lib/core/src/lib/viewer/components/media-player/media-player.component.stories.ts @@ -0,0 +1,135 @@ +/*! + * @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 { MediaPlayerComponent } from './media-player.component'; +import { Track } from '../../models/viewer.model'; +import { provideStoryCore } from '../../../stories/core-story.providers'; + +const SAMPLE_VIDEO_URL = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'; +const SAMPLE_AUDIO_URL = 'https://upload.wikimedia.org/wikipedia/commons/4/4f/Mozart_Eine_Kleine_Nachtmusik_K525_1st_movement.ogg'; + +const SAMPLE_TRACK: Track = { + src: 'data:text/vtt;base64,V0VCVlRUCgowMDowMDowMC4wMDAgLS0+IDAwOjAwOjAyLjAwMApIZWxsbyBmcm9tIHN0b3J5Ym9vayB0cmFja3M=', + label: 'English', + kind: 'subtitles', + srclang: 'en' +}; + +const meta: Meta = { + component: MediaPlayerComponent, + title: 'Core/Viewer/Media Player', + decorators: [ + moduleMetadata({ + imports: [MediaPlayerComponent] + }), + applicationConfig({ + providers: [...provideStoryCore()] + }) + ], + parameters: { + docs: { + description: { + component: `Plays audio and video files using native browser controls. Accepts URLs, blobs and an optional list of subtitle tracks.` + } + } + }, + argTypes: { + urlFile: { + control: 'text', + description: 'External URL of the audio/video file to play.', + table: { type: { summary: 'string' } } + }, + blobFile: { + control: false, + description: 'In-memory Blob containing media data.', + table: { type: { summary: 'Blob' } } + }, + mimeType: { + control: 'text', + description: 'MIME type used to determine whether to render an audio or video player.', + table: { type: { summary: 'string' } } + }, + fileName: { + control: 'text', + description: 'Optional file name (used as a fallback when the MIME type cannot be derived).', + table: { type: { summary: 'string' } } + }, + tracks: { + control: 'object', + description: 'Array of subtitle / caption tracks attached to the player.', + table: { type: { summary: 'Track[]' }, defaultValue: { summary: '[]' } } + }, + error: { + action: 'error', + description: 'Emitted when the underlying media element raises an error.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + }, + canPlay: { + action: 'canPlay', + description: 'Emitted when the media element reports that it can start playback.', + table: { type: { summary: 'EventEmitter ' }, category: 'Actions' } + } + }, + args: { + urlFile: SAMPLE_VIDEO_URL, + mimeType: 'video/mp4', + fileName: 'sample.mp4', + tracks: [] + }, + render: (args) => ({ + props: args, + template: `
+ +
` + }) +}; + +export default meta; +type Story = StoryObj; + +export const Video: Story = { + args: { + urlFile: SAMPLE_VIDEO_URL, + mimeType: 'video/mp4', + fileName: 'sample.mp4' + } +}; + +export const VideoWithSubtitleTrack: Story = { + args: { + urlFile: SAMPLE_VIDEO_URL, + mimeType: 'video/mp4', + fileName: 'sample.mp4', + tracks: [SAMPLE_TRACK] + } +}; + +export const Audio: Story = { + args: { + urlFile: SAMPLE_AUDIO_URL, + mimeType: 'audio/ogg', + fileName: 'sample.ogg', + tracks: [] + } +}; diff --git a/lib/core/src/lib/viewer/components/txt-viewer/txt-viewer.component.stories.ts b/lib/core/src/lib/viewer/components/txt-viewer/txt-viewer.component.stories.ts new file mode 100644 index 0000000000..a67748d1d6 --- /dev/null +++ b/lib/core/src/lib/viewer/components/txt-viewer/txt-viewer.component.stories.ts @@ -0,0 +1,120 @@ +/*! + * @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 { provideHttpClient } from '@angular/common/http'; +import { TxtViewerComponent } from './txt-viewer.component'; +import { provideStoryCore } from '../../../stories/core-story.providers'; + +type TxtViewerStoryArgs = TxtViewerComponent & { + sampleText?: string; +}; + +const SHORT_TEXT = `Hello from the ADF Text Viewer! + +Use this story to preview short snippets of plain-text content.`; + +const LONG_TEXT = Array.from( + { length: 60 }, + (_, i) => `${i + 1}. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.` +).join('\n'); + +const buildBlob = (content: string) => new Blob([content], { type: 'text/plain' }); + +const meta: Meta = { + component: TxtViewerComponent, + title: 'Core/Viewer/Txt Viewer', + decorators: [ + moduleMetadata({ + imports: [TxtViewerComponent] + }), + applicationConfig({ + providers: [provideHttpClient(), ...provideStoryCore()] + }) + ], + parameters: { + docs: { + description: { + component: `Renders the textual content of a plain-text file from either an external URL or an in-memory \`Blob\`.` + } + } + }, + argTypes: { + urlFile: { + control: 'text', + description: 'External URL pointing to the text file to load.', + table: { + type: { summary: 'string' }, + defaultValue: { summary: 'undefined' } + } + }, + blobFile: { + control: false, + description: 'In-memory Blob containing the text content.', + table: { + type: { summary: 'Blob' }, + defaultValue: { summary: 'undefined' } + } + }, + sampleText: { + control: 'text', + description: 'Sample text content used to build the Blob input for the story.', + table: { category: 'Storybook Helpers' } + }, + contentLoaded: { + action: 'contentLoaded', + description: 'Emitted when the text content finishes loading.', + table: { + type: { summary: 'EventEmitter ' }, + category: 'Actions' + } + } + }, + args: { + sampleText: SHORT_TEXT + }, + render: (args) => ({ + props: { + ...args, + blobFile: args.sampleText ? buildBlob(args.sampleText) : undefined + }, + template: `
+ +
` + }) +}; + +export default meta; +type Story = StoryObj; + +export const ShortContent: Story = { + args: { + sampleText: SHORT_TEXT + } +}; + +export const LongScrollingContent: Story = { + args: { + sampleText: LONG_TEXT + } +}; + +export const Empty: Story = { + args: { + sampleText: '' + } +};