mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
AAE-43707 Add storybook stories for txt img and media viewers
Adds Storybook coverage for the basic content sub-viewers used by the viewer-render renderer - txt-viewer, img-viewer and media-player - with sample blob and URL inputs and meaningful named variants.
This commit is contained in:
@@ -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<ImgViewerComponent> = {
|
||||||
|
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 <void>' }, category: 'Actions' }
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
action: 'submit',
|
||||||
|
description: 'Emitted with the edited Blob when the user saves the cropped/rotated image.',
|
||||||
|
table: { type: { summary: 'EventEmitter <Blob>' }, category: 'Actions' }
|
||||||
|
},
|
||||||
|
isSaving: {
|
||||||
|
action: 'isSaving',
|
||||||
|
description: 'Emits `true` while the edited image is being saved.',
|
||||||
|
table: { type: { summary: 'EventEmitter <boolean>' }, category: 'Actions' }
|
||||||
|
},
|
||||||
|
imageLoaded: {
|
||||||
|
action: 'imageLoaded',
|
||||||
|
description: 'Emitted once the underlying `<img>` element finishes loading.',
|
||||||
|
table: { type: { summary: 'EventEmitter <void>' }, 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: `<div style="height:480px; border:1px solid #ddd;">
|
||||||
|
<adf-img-viewer
|
||||||
|
[urlFile]="urlFile"
|
||||||
|
[fileName]="fileName"
|
||||||
|
[showToolbar]="showToolbar"
|
||||||
|
[readOnly]="readOnly"
|
||||||
|
[allowedEditActions]="allowedEditActions"
|
||||||
|
(error)="error($event)"
|
||||||
|
(submit)="submit($event)"
|
||||||
|
(isSaving)="isSaving($event)"
|
||||||
|
(imageLoaded)="imageLoaded($event)" />
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<ImgViewerComponent>;
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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<MediaPlayerComponent> = {
|
||||||
|
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 <Event>' }, category: 'Actions' }
|
||||||
|
},
|
||||||
|
canPlay: {
|
||||||
|
action: 'canPlay',
|
||||||
|
description: 'Emitted when the media element reports that it can start playback.',
|
||||||
|
table: { type: { summary: 'EventEmitter <void>' }, category: 'Actions' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
urlFile: SAMPLE_VIDEO_URL,
|
||||||
|
mimeType: 'video/mp4',
|
||||||
|
fileName: 'sample.mp4',
|
||||||
|
tracks: []
|
||||||
|
},
|
||||||
|
render: (args) => ({
|
||||||
|
props: args,
|
||||||
|
template: `<div style="max-width:720px; border:1px solid #ddd;">
|
||||||
|
<adf-media-player
|
||||||
|
[urlFile]="urlFile"
|
||||||
|
[mimeType]="mimeType"
|
||||||
|
[fileName]="fileName"
|
||||||
|
[tracks]="tracks"
|
||||||
|
(error)="error($event)"
|
||||||
|
(canPlay)="canPlay($event)" />
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<MediaPlayerComponent>;
|
||||||
|
|
||||||
|
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: []
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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<TxtViewerStoryArgs> = {
|
||||||
|
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 <void>' },
|
||||||
|
category: 'Actions'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
sampleText: SHORT_TEXT
|
||||||
|
},
|
||||||
|
render: (args) => ({
|
||||||
|
props: {
|
||||||
|
...args,
|
||||||
|
blobFile: args.sampleText ? buildBlob(args.sampleText) : undefined
|
||||||
|
},
|
||||||
|
template: `<div style="height:320px; border:1px solid #ddd; overflow:auto;">
|
||||||
|
<adf-txt-viewer [blobFile]="blobFile" (contentLoaded)="contentLoaded($event)" />
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<TxtViewerStoryArgs>;
|
||||||
|
|
||||||
|
export const ShortContent: Story = {
|
||||||
|
args: {
|
||||||
|
sampleText: SHORT_TEXT
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LongScrollingContent: Story = {
|
||||||
|
args: {
|
||||||
|
sampleText: LONG_TEXT
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Empty: Story = {
|
||||||
|
args: {
|
||||||
|
sampleText: ''
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user