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

Comments Added #49

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 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
10 changes: 5 additions & 5 deletions projects/ngx-stories/src/lib/interfaces/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ export interface Story {
type: StoryType,
content: string,
}

// StoryGroup interface
export interface StoryGroup {
id: number, // unique id
name: string, // name of the storyGroup which is to be displayed over story
stories: Story[] // array of stories
id: number,
name: string,
stories: Story[]
}

// NgxStoriesOptions interface
export interface NgxStoriesOptions {
width: number,
height: number,
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 @@ -9,6 +9,7 @@
<h5 [style.visibility]="isHolding ? 'hidden' : 'visible'">
{{storyGroup.name}}
</h5>
// <!-- Story content -->
<button (click)="togglePause()">
<img [ngClass]="{ 'pause-icon': storyState==='paused', 'play-icon': storyState==='playing' }"
[alt]="storyState==='paused' ? 'play-icon' : 'pause-icon'" width="20" height="20">
Expand All @@ -24,6 +25,7 @@ <h5 [style.visibility]="isHolding ? 'hidden' : 'visible'">
</div>
}


<div class="progress-bar-container" [style.visibility]="isHolding ? 'hidden' : 'visible'">
@for(story of storyGroup.stories; track story.id; let x = $index ) {
<progress class="progress-bar" [value]="getProgressValue(x)" max="100" [class.active]="x===currentStoryIndex">
Expand Down
5 changes: 3 additions & 2 deletions projects/ngx-stories/src/lib/ngx-stories.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { NgxStoriesComponent } from './ngx-stories.component';
import { StoryGroup } from './interfaces/interfaces';

// Test suite for the NgxStoriesComponent
describe('NgxStoriesComponent', () => {
let component: NgxStoriesComponent;
let fixture: ComponentFixture<NgxStoriesComponent>;
Expand All @@ -16,7 +17,7 @@ describe('NgxStoriesComponent', () => {
});

it('should create the component', () => {
expect(component).toBeTruthy(); // This checks if the component is created successfully
expect(component).toBeTruthy();
});

it('should start progress interval for an image story', () => {
Expand All @@ -34,7 +35,7 @@ describe('NgxStoriesComponent', () => {
spyOn(component, 'startProgressInterval');
component.startStoryProgress();

expect(component.startProgressInterval).toHaveBeenCalledWith(5000); // default duration
expect(component.startProgressInterval).toHaveBeenCalledWith(5000);
});

it('should accept storyGroups input and display stories', () => {
Expand Down
49 changes: 25 additions & 24 deletions projects/ngx-stories/src/lib/ngx-stories.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ export class NgxStoriesComponent implements AfterViewInit {

// Input property to accept the list of storyGroup and their stories
@Input({ required: true }) storyGroups: StoryGroup[] = [];

// options
// Input property to accept the options for the stories component
@Input() options: NgxStoriesOptions = {
width: 360,
height: 768,
Expand All @@ -35,20 +34,19 @@ export class NgxStoriesComponent implements AfterViewInit {
currentStoryIndex: number = 0;
currentStoryGroupIndex: number = 0;
progressWidth: number = 0;
intervalId: any; // Interval for story progress
isTransitioning = false; // Prevents multiple transitions at once
intervalId: any;
isTransitioning = false;
isSwipingLeft = false;
isSwipingRight = false;
isHolding = false;
holdTimeout: any; // Timeout for holding the story (pause functionality)
holdTimeout: any;
storyState: 'playing' | 'paused' | 'holding' = 'playing';

// constants

readonly HOLD_DELAY_MS = 500;
readonly PROGRESS_INTERVAL_MS = 50;
readonly FULL_PROGRESS_WIDTH = 100;

// Queries the story containers in the view for gesture handling
@ViewChildren('storyContainer') storyContainers!: QueryList<ElementRef>;

constructor(
Expand All @@ -66,20 +64,18 @@ export class NgxStoriesComponent implements AfterViewInit {
ngAfterViewInit(): void {
this.initHammer();
}

private startStoryProgress() {
// Function to start the progress bar for the current story
private startStoryProgress() {
const currentStory = this.storyGroups[this.currentStoryGroupIndex].stories[this.currentStoryIndex];
let storyDuration = 5000; // Default duration (in milliseconds) for images
let storyDuration = 5000;
if (currentStory.type === 'video') {
const videoElement = document.createElement('video');
videoElement.src = currentStory.content;
// Use the video duration or a default if not available
videoElement.onloadedmetadata = () => {
storyDuration = videoElement.duration * 1000; // Convert to milliseconds
storyDuration = videoElement.duration * 1000;
this.startProgressInterval(storyDuration);
};
} else {
// For images, start with default duration
this.startProgressInterval(storyDuration);
}
}
Expand Down Expand Up @@ -121,13 +117,13 @@ export class NgxStoriesComponent implements AfterViewInit {
}
this.goToNextStoryGroup();
this.resetSwipe();
}, 600); // Match the animation duration
}, 600);
} else if (direction === 'right') {
this.isSwipingRight = true;
setTimeout(() => {
this.goToPreviousStoryGroup();
this.resetSwipe();
}, 600); // Match the animation duration
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep existing comments

}, 600);
} else if (direction === 'down') {
clearInterval(this.intervalId);
this.onExit();
Expand All @@ -140,13 +136,14 @@ export class NgxStoriesComponent implements AfterViewInit {
this.isSwipingLeft = false;
this.isSwipingRight = false;
}

// Function to navigate to the next or previous story
navigateStory(direction: 'next' | 'previous') {
if (this.isTransitioning) return;
this.pauseCurrentVideo(true); // Pause the video before navigating
this.pauseCurrentVideo(true);
this.setTransitionState(true);
clearInterval(this.intervalId);

// Get the next story index and story group index
const { storyGroupIndex, storyIndex } =
direction === 'next'
? this.storyService.nextStory(this.storyGroups, this.currentStoryGroupIndex, this.currentStoryIndex, this.storyGroupChange.bind(this))
Expand Down Expand Up @@ -199,7 +196,8 @@ export class NgxStoriesComponent implements AfterViewInit {
}
}

private goToNextStoryGroup() {
// Function to navigate to the next story group
private goToNextStoryGroup() {
if (this.isTransitioning) return;
this.isTransitioning = true;
this.currentStoryGroupIndex = (this.currentStoryGroupIndex + 1) % this.storyGroups.length;
Expand All @@ -211,9 +209,10 @@ export class NgxStoriesComponent implements AfterViewInit {
setTimeout(() => {
this.startStoryProgress();
this.isTransitioning = false;
}, this.HOLD_DELAY_MS); // Match this timeout with the CSS transition duration
}, this.HOLD_DELAY_MS);
}

// Function to navigate to the previous story group
private goToPreviousStoryGroup() {
if (this.isTransitioning) return;
this.isTransitioning = true;
Expand All @@ -227,16 +226,18 @@ export class NgxStoriesComponent implements AfterViewInit {
setTimeout(() => {
this.startStoryProgress();
this.isTransitioning = false;
}, this.HOLD_DELAY_MS); // Match this timeout with the CSS transition duration
}, this.HOLD_DELAY_MS);
}

// Function to set the transition state
private setTransitionState(isTransitioning: boolean, duration = this.HOLD_DELAY_MS): void {
this.isTransitioning = isTransitioning;
setTimeout(() => {
this.isTransitioning = false;
}, duration); // Ensure consistent transition timing
}, duration);
}

// Function to check if the user has reached the end of the stories
private hasReachedEndOfStories(): boolean {
let stories = this.storyGroups.find((storyGroup, index) => index === this.currentStoryGroupIndex)?.stories;
if (this.currentStoryIndex === Number(stories?.length) - 1 && this.currentStoryGroupIndex === this.storyGroups.length - 1) {
Expand All @@ -246,6 +247,7 @@ export class NgxStoriesComponent implements AfterViewInit {
return false;
}

// Function to get the progress value for the current story
getProgressValue(storyIndex: number): number {
if (this.isHolding) return this.progressWidth;
if (storyIndex < this.currentStoryIndex) {
Expand Down Expand Up @@ -276,7 +278,7 @@ export class NgxStoriesComponent implements AfterViewInit {
this.isHolding = false;
this.storyState = 'playing';
this.startStoryProgress();
this.playCurrentStoryVideo(); // Resume the video when released
this.playCurrentStoryVideo();
}
}

Expand All @@ -291,7 +293,7 @@ export class NgxStoriesComponent implements AfterViewInit {
this.pauseCurrentVideo();
} else {
this.startStoryProgress();
this.playCurrentStoryVideo(); // Resume the video when released
this.playCurrentStoryVideo();
}
}

Expand All @@ -300,7 +302,6 @@ export class NgxStoriesComponent implements AfterViewInit {
}

private onExit() {
// Swipe down event or cross button implementation in future
this.triggerOnExit.emit();
}

Expand Down
37 changes: 23 additions & 14 deletions projects/ngx-stories/src/lib/ngx-stories.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { TestBed } from '@angular/core/testing';

import { NgxStoriesService } from './ngx-stories.service';
import { StoryGroup } from './interfaces/interfaces';

Expand All @@ -17,21 +16,24 @@ describe('NgxStoriesService', () => {
expect(service).toBeTruthy();
});

// Testing the startProgress function
describe('startProgress', () => {
it('should call callback at the given interval', (done) => {
const callback = jasmine.createSpy('callback');
const interval = 100;

const intervalId = service.startProgress(interval, callback);

// Wait for the interval time + some buffer and check if the callback was called
setTimeout(() => {
expect(callback).toHaveBeenCalledTimes(1);
service.clearProgress(intervalId); // Clean up
service.clearProgress(intervalId);
done();
}, interval + 50);
});
});

// Testing the clearProgress function
describe('clearProgress', () => {
it('should clear the interval', (done) => {
const callback = jasmine.createSpy('callback');
Expand All @@ -40,13 +42,15 @@ describe('NgxStoriesService', () => {
const intervalId = service.startProgress(interval, callback);
service.clearProgress(intervalId);

// Ensure that the callback is not called after clearing the interval
setTimeout(() => {
expect(callback).not.toHaveBeenCalled();
done();
}, interval + 50);
});
});

// Testing the nextStory function
describe('nextStory', () => {
const storyGroups: StoryGroup[] = [
{
Expand All @@ -68,21 +72,22 @@ describe('NgxStoriesService', () => {
}
];

// Test to ensure the service moves to the next story within the same storyGroup
it('should move to the next story within the same storyGroup', () => {
const result = service.nextStory(storyGroups, 0, 0, onStoryGroupChangeMock); // Start at storyGroup 0, story 0
const result = service.nextStory(storyGroups, 0, 0, onStoryGroupChangeMock);
expect(result.storyGroupIndex).toBe(0);
expect(result.storyIndex).toBe(1);
});

// Test to ensure the service moves to the next storyGroup when the last story is reached
it('should move to the next storyGroup if current story is the last one', () => {
const result = service.nextStory(storyGroups, 0, 2, onStoryGroupChangeMock); // Start at storyGroup 0, story 2 (last story)
expect(result.storyGroupIndex).toBe(1); // Moved to next storyGroup
expect(result.storyIndex).toBe(0); // First story of the next storyGroup
const result = service.nextStory(storyGroups, 0, 2, onStoryGroupChangeMock);
expect(result.storyGroupIndex).toBe(1);
expect(result.storyIndex).toBe(0);
});

});


// Testing the prevStory function
describe('prevStory', () => {
const storyGroups: StoryGroup[] = [
{
Expand All @@ -104,23 +109,27 @@ describe('NgxStoriesService', () => {
}
];

// Test to ensure the service moves to the previous story within the same storyGroup
it('should move to the previous story within the same storyGroup', () => {
const result = service.prevStory(storyGroups, 0, 2, onStoryGroupChangeMock); // Start at storyGroup 0, story 2
const result = service.prevStory(storyGroups, 0, 2, onStoryGroupChangeMock);
expect(result.storyGroupIndex).toBe(0);
expect(result.storyIndex).toBe(1);
});

// Test to ensure the service moves to the previous storyGroup when the first story is reached
it('should move to the previous storyGroup if current story is the first one', () => {
const result = service.prevStory(storyGroups, 1, 0, onStoryGroupChangeMock); // Start at storyGroup 1, story 0
expect(result.storyGroupIndex).toBe(0); // Moved to previous storyGroup
expect(result.storyIndex).toBe(2); // Last story of the previous storyGroup
const result = service.prevStory(storyGroups, 1, 0, onStoryGroupChangeMock);
expect(result.storyGroupIndex).toBe(0);
expect(result.storyIndex).toBe(2);
});

// Test to ensure that no change occurs if already on the first story of the first storyGroup
it('should do nothing if on the first story of the first storyGroup', () => {
const result = service.prevStory(storyGroups, 0, 0, onStoryGroupChangeMock); // First story of the first storyGroup
const result = service.prevStory(storyGroups, 0, 0, onStoryGroupChangeMock);
expect(result.storyGroupIndex).toBe(0);
expect(result.storyIndex).toBe(0); // Remains at the first story
expect(result.storyIndex).toBe(0);
});
});

});

16 changes: 4 additions & 12 deletions projects/ngx-stories/src/lib/ngx-stories.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ export class NgxStoriesService {
startProgress(interval: number, callback: () => void): any {
return setInterval(() => callback(), interval);
}

// Clears the progress interval
clearProgress(intervalId: any): void {
clearInterval(intervalId);
}
Expand All @@ -23,40 +21,34 @@ export class NgxStoriesService {
onStoryGroupChange: (storyGroupIndex: number) => void): { storyGroupIndex: number, storyIndex: number } {
let stories = storyGroups[currentStoryGroupIndex]?.stories;
if (currentStoryIndex === stories.length - 1) {
// Move to the next storyGroup if the current story index is the last
currentStoryGroupIndex = (currentStoryGroupIndex + 1) % storyGroups.length;
currentStoryIndex = 0;
onStoryGroupChange(currentStoryGroupIndex);
} else {
// Otherwise, just move to the next story within the same storyGroup
currentStoryIndex++;
}
return { storyGroupIndex: currentStoryGroupIndex, storyIndex: currentStoryIndex };
}

prevStory(storyGroups: StoryGroup[],
// Array of story groups
prevStory(storyGroups: StoryGroup[],
currentStoryGroupIndex: number,
currentStoryIndex: number,
onStoryGroupChange: (storyGroupIndex: number) => void): { storyGroupIndex: number, storyIndex: number } {
let stories = storyGroups[currentStoryGroupIndex]?.stories;
if (currentStoryIndex === 0) {
// Move to the previous storyGroup if the current story index is 0
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please retain existing comments

if (currentStoryGroupIndex > 0) {
currentStoryGroupIndex--;
stories = storyGroups[currentStoryGroupIndex]?.stories;
currentStoryIndex = stories.length - 1;
onStoryGroupChange(currentStoryGroupIndex);
}
} else {
// Otherwise, just move to the previous story within the same storyGroup
currentStoryIndex--;
}
return { storyGroupIndex: currentStoryGroupIndex, storyIndex: currentStoryIndex };
}

setOptions(options: NgxStoriesOptions, storyContainers: QueryList<ElementRef>): void {
// Set the options for the service
// Set the width and height of the story container
// Set the options for the service
setOptions(options: NgxStoriesOptions, storyContainers: QueryList<ElementRef>): void {
storyContainers?.forEach(storyContainer => {
storyContainer.nativeElement.style.width = options.width + 'px';
storyContainer.nativeElement.style.height = options.height + 'px';
Expand Down
Loading