Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ftr: component based stories #78

Merged
merged 4 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class AppComponent {
name: 'John Doe',
stories: [
{ id: 101, type: 'image', content: 'https://example.com/story1.jpg' },
{ id: 101, type: 'component', content: CustomComponent }, // pass any angular component to render in stories
],
},
{
Expand Down Expand Up @@ -82,6 +83,7 @@ interface NgxStoriesOptions {
* Keyboard navigation(Experimental).
* Loading: A Loading svg will be shown when story buffers (SVG from <a href="https://www.svgbackgrounds.com/elements/animated-svg-preloaders/">Animated SVG Preloaders by SVGBackgrounds.com</a>)
* Audio controls: Toggle audio on/off for stories that have audio.
* Component based stories for best control.

## Contributing
[Contributing Guide](https://github.com/Gauravdarkslayer/ngx-stories/blob/main/CONTRIBUTING.md)
Expand Down
2 changes: 2 additions & 0 deletions projects/ngx-stories/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class AppComponent {
name: 'John Doe',
stories: [
{ id: 101, type: 'image', content: 'https://example.com/story1.jpg' },
{ id: 101, type: 'component', content: CustomComponent }, // pass any angular component to render in stories
],
},
{
Expand Down Expand Up @@ -82,6 +83,7 @@ interface NgxStoriesOptions {
* Keyboard navigation(Experimental).
* Loading: A Loading svg will be shown when story buffers (SVG from <a href="https://www.svgbackgrounds.com/elements/animated-svg-preloaders/">Animated SVG Preloaders by SVGBackgrounds.com</a>)
* Audio controls: Toggle audio on/off for stories that have audio.
* Component based stories for best control.

## Contributing
[Contributing Guide](https://github.com/Gauravdarkslayer/ngx-stories/blob/main/CONTRIBUTING.md)
Expand Down
2 changes: 1 addition & 1 deletion projects/ngx-stories/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-stories",
"version": "0.1.0",
"version": "0.2.0",
"peerDependencies": {
"@angular/common": "^18.2.0",
"@angular/core": "^18.2.0",
Expand Down
6 changes: 4 additions & 2 deletions projects/ngx-stories/src/lib/interfaces/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
export type StoryType = 'image' | 'video';
import { Component, Type } from "@angular/core";

export type StoryType = 'image' | 'video' | 'component';
export type StoryStateType = 'playing' | 'paused' | 'holding' | 'buffering' ;
export interface Story {
id: number,
type: StoryType,
content: string,
content: string | Type<Component>,
}

export interface StoryGroup {
Expand Down
2 changes: 2 additions & 0 deletions projects/ngx-stories/src/lib/ngx-stories.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ <h5 [style.visibility]="isHolding ? 'hidden' : 'visible'">
} @else if(story.type === 'video') {
<video [src]="story.content" (loadeddata)="onContentLoaded()"
(waiting)="onContentBuffering()" (playing)="onContentLoaded()" ></video>
} @else if(story.type === 'component') {
<ng-container #dynamicComponentContainer></ng-container>
}
</div>
</div>
Expand Down
23 changes: 16 additions & 7 deletions projects/ngx-stories/src/lib/ngx-stories.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AfterViewInit, Component, ElementRef, Input, Output, QueryList, ViewChildren, HostListener } from '@angular/core';
import { AfterViewInit, Component, ElementRef, Input, Output, QueryList, ViewChildren, HostListener, ViewChild, ViewContainerRef, Type } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HammerModule } from '@angular/platform-browser';
import { StoryGroup, StoryStateType } from '../lib/interfaces/interfaces';
Expand Down Expand Up @@ -53,12 +53,14 @@ export class NgxStoriesComponent implements AfterViewInit {

// Queries the story containers in the view for gesture handling
@ViewChildren('storyContainer') storyContainers!: QueryList<ElementRef>;
// Add a ViewContainerRef to inject dynamic components
@ViewChild('dynamicComponentContainer', { read: ViewContainerRef, static: false }) dynamicComponentContainer!: ViewContainerRef;

constructor(
private storyService: NgxStoriesService
) {
private storyService: NgxStoriesService,
) {



}


Expand Down Expand Up @@ -96,21 +98,28 @@ export class NgxStoriesComponent implements AfterViewInit {
let storyDuration = 5000; // Default duration (in milliseconds) for images
if (currentStory.type === 'video') {
const videoElement: HTMLVideoElement = document.createElement('video');
videoElement.src = currentStory.content;
videoElement.src = currentStory.content as string;

// Use the video duration or a default if not available
videoElement.onloadedmetadata = () => {
this.onContentLoaded(); // Call when metadata is loaded
storyDuration = videoElement.duration * 1000; // Convert to milliseconds
this.startProgressInterval(storyDuration);
};
} else if (currentStory.type === 'component') {
setTimeout(() => {
// Small delay to detect changes in DOM
this.storyService.renderComponent(this.dynamicComponentContainer, currentStory.content as Type<any>);
this.onContentLoaded();
this.startProgressInterval(5000); // Default duration for components
}, 100);
} else {
// Handling for images
const imageElement = document.createElement('img');
imageElement.src = currentStory.content;
imageElement.src = currentStory.content as string;

// Check if the image is cached
if (this.storyService.isImageCached(currentStory.content)) {
if (this.storyService.isImageCached(currentStory.content as string)) {
this.onContentLoaded(); // Call immediately if cached
this.startProgressInterval(storyDuration); // Use default image duration
} else {
Expand Down
19 changes: 17 additions & 2 deletions projects/ngx-stories/src/lib/ngx-stories.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ElementRef, Injectable, QueryList } from "@angular/core";
import { ElementRef, Injectable, QueryList, Type, ViewContainerRef } from "@angular/core";
import { NgxStoriesOptions, StoryGroup } from "./interfaces/interfaces";
@Injectable({
providedIn: 'root',
Expand Down Expand Up @@ -73,5 +73,20 @@ export class NgxStoriesService {
img.src = src;
return img.complete;
}


/**
* Dynamically creates and injects a component into the specified ViewContainerRef.
* @param containerRef The ViewContainerRef where the component should be injected.
* @param component The component class (Type) to be dynamically created.
*/
renderComponent<T>(
containerRef: ViewContainerRef,
component: Type<T>,
): T {
// Clear previous components in the container (if any)
containerRef?.clear();

const componentRef = containerRef.createComponent(component);
return componentRef.instance;
}
}
12 changes: 7 additions & 5 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { Component, Type } from '@angular/core';
import { NgxStoriesComponent, NgxStoriesOptions, StoryGroup } from '../../projects/ngx-stories/src/public-api';
import { CustomComponentComponent } from './components/custom-component/custom-component.component';

@Component({
selector: 'app-root',
Expand All @@ -24,10 +25,11 @@ export class AppComponent {
name: 'Steve Smith',
stories: [
{ id: 1, type: 'image', content: 'https://i.ibb.co/ZMVy3KN/pexels-rpnickson-2486168.jpg' },
{ id: 2, type: 'video', content: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' },
{ id: 3, type: 'video', content: 'https://www.w3schools.com/html/mov_bbb.mp4' },
{ id: 4, type: 'video', content: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4' },
{ id: 5, type: 'image', content: 'https://d38b044pevnwc9.cloudfront.net/cutout-nuxt/enhancer/3.jpg' }
{ id: 2, type: 'component', content: CustomComponentComponent },
{ id: 3, type: 'video', content: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' },
{ id: 4, type: 'video', content: 'https://www.w3schools.com/html/mov_bbb.mp4' },
{ id: 5, type: 'video', content: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4' },
{ id: 6, type: 'image', content: 'https://d38b044pevnwc9.cloudfront.net/cutout-nuxt/enhancer/3.jpg' }
]
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div class="custom-container">
<h3>Custom Component</h3>
<p>This is a beautifully styled custom component, designed to showcase how dynamic rendering can be visually appealing.</p>

<p>Possibilities are endless. For example, here's a simple code snippet:</p>
<pre class="code-block">console.log('Dynamic rendering!');</pre>

<img src="https://images.unsplash.com/photo-1568667185695-edcbcd4938cb?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
width="500" height="300" alt="Example image" class="custom-image" />

<p>And there's more! You can extend this component further to fit your use case.</p>

</div>

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.custom-container {
width: 100%;
background: linear-gradient(to right, #ff7f50, #ff6f61); /* Coral gradient */
padding-top: 0.6rem;
padding-bottom: 0.6rem;
margin: 0rem auto;
color: #fff;
text-align: center;
font-family: Arial, sans-serif;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.custom-container h3 {
font-size: 2rem;
margin-bottom: 2rem;
color: #333;
background-color: #f4f4f4;
}

.custom-container p {
margin: 0.8rem 0;
font-size: 1rem;
}

.code-block {
background-color: #f4f4f4;
color: #333;
padding: 0.8rem;
border-radius: 8px;
font-family: 'Courier New', Courier, monospace;
font-size: 0.9rem;
text-align: left;
display: inline-block;
margin: 1rem auto;
}

.custom-image {
width: 100%;
height: auto;
// margin: 1rem 0;
border-radius: 8px;
}

.custom-link {
display: inline-block;
margin-top: 1rem;
text-decoration: none;
color: #fff;
background-color: #ff5722;
padding: 0.5rem 1rem;
border-radius: 8px;
transition: background-color 0.3s;
}

.custom-link:hover {
background-color: #e64a19;
}

12 changes: 12 additions & 0 deletions src/app/components/custom-component/custom-component.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-custom-component',
standalone: true,
imports: [],
templateUrl: './custom-component.component.html',
styleUrl: './custom-component.component.scss'
})
export class CustomComponentComponent {

}
Loading