Skip to content

Commit

Permalink
Fix part of oppia#18384: Reusable carousel component for learner dash…
Browse files Browse the repository at this point in the history
…board redesign (oppia#20306)

* Added nav

Update with newest commits from PR 20185

* Updated margins

* Corrected shifts for card nav

* Updated shift

* Fixed nav spacing

* Added translation key

* Replaced explicit width with var cardWidth

* fixed margins and import errors

* Updated spacing between sections

* Changed width to include margin and added test cases

* Added card-display tests

* Refactored logic

* Refactored nextCard()

* Reverted testing statements to default

* Removed comments

* Updated TODO comments

* Replaced headings with i18n keys,font-awesome for nav buttons; added comments

* Renamed folder

* Updated view more button

* Updated styling for view more button

* Fixed capitalization of description and community text for cards

* Fixed lint errors, added copyright

* Updated lesson card test case

* Renamed variable

* Changed selector

* Updated variable names

* Removed extra file from prev commit
  • Loading branch information
amyyeung17 authored Jun 11, 2024
1 parent 6476a58 commit 9cc8aab
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 35 deletions.
6 changes: 6 additions & 0 deletions assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@
"I18N_LEARNER_DASHBOARD_CARD_BUTTON_REDO": "Redo",
"I18N_LEARNER_DASHBOARD_CARD_BUTTON_RESUME": "Resume",
"I18N_LEARNER_DASHBOARD_CARD_BUTTON_START": "Start",
"I18N_LEARNER_DASHBOARD_CARD_DISPLAY_BUTTON": "View more",
"I18N_LEARNER_DASHBOARD_CARD_TOPIC": "<[cardTopic]>",
"I18N_LEARNER_DASHBOARD_CLASSROOM_TITLE": "Basic <[classroomName]>",
"I18N_LEARNER_DASHBOARD_COMMUNITY_LESSONS_SECTION": "Community Lessons",
Expand Down Expand Up @@ -789,6 +790,11 @@
"I18N_LEARNER_DASHBOARD_EXPLORATIONS_SORT_BY_LAST_PLAYED": "Last Played",
"I18N_LEARNER_DASHBOARD_GOALS_SECTION": "Goals",
"I18N_LEARNER_DASHBOARD_GOLD_BADGE": "Gold",
"I18N_LEARNER_DASHBOARD_HOME_CLASSROOM_SECTION": "Topics available in Oppia's Classroom",
"I18N_LEARNER_DASHBOARD_HOME_CLASSROOM_SECTION_LINK": "Or Explore All Lessons in Classroom",
"I18N_LEARNER_DASHBOARD_HOME_PROGRESS_SECTION": "Lessons in progress",
"I18N_LEARNER_DASHBOARD_HOME_RECOMMEND_SECTION": "Recommended for you",
"I18N_LEARNER_DASHBOARD_HOME_SAVED_SECTION": "Lesson you saved for later",
"I18N_LEARNER_DASHBOARD_HOME_SECTION": "Home",
"I18N_LEARNER_DASHBOARD_INCOMPLETE": "Incomplete",
"I18N_LEARNER_DASHBOARD_INCOMPLETE_SECTION": "In Progress",
Expand Down
6 changes: 6 additions & 0 deletions assets/i18n/qqq.json
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@
"I18N_LEARNER_DASHBOARD_CARD_BUTTON_REDO": "Text for card button - allows user to redo a lesson",
"I18N_LEARNER_DASHBOARD_CARD_BUTTON_RESUME": "Text for card button - allows user to resume a lesson",
"I18N_LEARNER_DASHBOARD_CARD_BUTTON_START": "Text for card button - allows user to start a lesson",
"I18N_LEARNER_DASHBOARD_CARD_DISPLAY_BUTTON": "Text for card display button - allows user to view more cards",
"I18N_LEARNER_DASHBOARD_CARD_TOPIC": "Topic or type of lesson (community lessons or explorations)",
"I18N_LEARNER_DASHBOARD_CLASSROOM_TITLE": "Text for the Learn Something New section in the learner dashboard. - This text is the title of a classroom.",
"I18N_LEARNER_DASHBOARD_COMMUNITY_LESSONS_SECTION": "Text for the community lessons section in the learner dashboard.",
Expand Down Expand Up @@ -789,6 +790,11 @@
"I18N_LEARNER_DASHBOARD_EXPLORATIONS_SORT_BY_LAST_PLAYED": "Option in drop down list for sorting explorations - This is an option for sorting explorations by Last Played",
"I18N_LEARNER_DASHBOARD_GOALS_SECTION": "Text for the goals section in the learner dashboard.",
"I18N_LEARNER_DASHBOARD_GOLD_BADGE": "Text for the gold badge in the learner dashboard.\n\n{{Identical|Gold}}",
"I18N_LEARNER_DASHBOARD_HOME_CLASSROOM_SECTION": "Text for the classroom section in the learner dashboard",
"I18N_LEARNER_DASHBOARD_HOME_CLASSROOM_SECTION_LINK": "Text for the classroom section link in the learner dashboard - when user clicks on link, they are taken to classroom page",
"I18N_LEARNER_DASHBOARD_HOME_PROGRESS_SECTION": "Text for the progress section in the learner dashboard",
"I18N_LEARNER_DASHBOARD_HOME_RECOMMEND_SECTION": "Text for the recommended section in the learner dashboard",
"I18N_LEARNER_DASHBOARD_HOME_SAVED_SECTION": "Text for the saved lessons section in the learner dashboard",
"I18N_LEARNER_DASHBOARD_HOME_SECTION": "Text for the home section in the learner dashboard.",
"I18N_LEARNER_DASHBOARD_INCOMPLETE": "Title of the section that shows incomplete explorations in the learner dashboard.",
"I18N_LEARNER_DASHBOARD_INCOMPLETE_SECTION": "Text for the incomplete section in the learner dashboard.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</div>
</a>
</mat-card>
<a *ngIf="redesignFeatureFlag" class="d-flex flex-column flex-shrink-0 oppia-class-card text-decoration-none" [href]="getTopicLink()" target="{{ openInNewWindow ? '_blank' : '_self' }}">
<a *ngIf="redesignFeatureFlag" class="d-flex flex-column flex-shrink-0 oppia-class-card text-decoration-none" [href]="getTopicLink()" target="{{ openInNewWindow ? '_blank' : '_self' }}" [ngClass]="{'oppia-class-card-not-last': !lastCard}">
<div class="oppia-class-card-img rounded-top" [ngStyle]="{'background-color': thumbnailBgColor}">
<img alt="" class="oppia-thumbnail-image" [src]="thumbnailUrl">
</div>
Expand Down Expand Up @@ -93,7 +93,6 @@
border-radius: .25em;
box-shadow: 0 2px 2px 0 #00000040;
height: 220px;
margin-right: 16px;
padding: 0;
width: 210px;
}
Expand All @@ -103,7 +102,7 @@
box-shadow: 0 0 8px 0 #00000040;
}

.oppia-class-card:not(:last-child) {
.oppia-class-card-not-last {
margin-right: 16px;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class LearnerTopicSummaryTileComponent implements OnInit {
thumbnailBgColor!: string;
openInNewWindow = false;
@Input() redesignFeatureFlag!: boolean;
@Input() lastCard!: boolean;

constructor(
private urlInterpolationService: UrlInterpolationService,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<div class="lesson-card-container mr-4">
<!--TODO(#18384): Fix when there's only one card, margin-right still appears-->
<div class="lesson-card-container" [ngClass]="{'lesson-card-not-last': !lastCard}">
<div class="align-items-center d-flex justify-content-center lesson-card-img relative rounded-top w-100" [ngStyle]="{'background-color': imgColor}">
<img alt="" class="h-100" [src]="imgUrl">
<div class="d-flex flex-column lesson-card-tags w-100">
Expand All @@ -11,7 +12,7 @@
<p class="lesson-card-title mb-0"> {{ title }} </p>
</div>
<p class="lesson-card-topic mb-0"> {{ 'I18N_LEARNER_DASHBOARD_CARD_TOPIC' | translate: {cardTopic: lessonTopic} }} </p>
<p class="lesson-card-desc mb-0"> {{ desc }} </p>
<p class="lesson-card-desc mb-0"> {{ desc.charAt(0).toUpperCase() + desc.slice(1) }} </p>
</div>
<div class="align-items-center d-flex justify-content-between mt-2">
<circle-progress [percent]="progress"> </circle-progress>
Expand Down Expand Up @@ -49,19 +50,20 @@
}

.lesson-card-info {
border-radius: 0 0 .25em .25em;
height: 150px;
padding: 8px;
}

.lesson-card-img {
height: 136px;
position: relative;
}

.lesson-card-tags {
left: 0;
padding: 8px;
position: absolute;
top: 0;
}

.lesson-card-title {
Expand All @@ -85,4 +87,8 @@
font-size: 11px;
-webkit-line-clamp: 2;
}

.lesson-card-not-last {
margin-right: 16px;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ describe('LessonCardComponent', () => {
expect(component.title).toEqual(sampleExploration.title);

expect(component.progress).toEqual(0);
expect(component.lessonTopic).toEqual('Community Lessons');
expect(component.lessonTopic).toEqual('Community Lesson');
});

it('should set story to complete StorySummary and its non-url values to the respective fields', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {StoryNode} from 'domain/story/story-node.model';
export class LessonCardComponent implements OnInit {
@Input() story!: StorySummary | LearnerExplorationSummary | CollectionSummary;
@Input() topic!: string;
@Input() lastCard!: boolean;

desc!: string;
imgColor!: string;
Expand Down Expand Up @@ -108,7 +109,7 @@ export class LessonCardComponent implements OnInit {
this.progress = 0;
this.title = explorationModel.title;
this.lessonUrl = `/explore/${explorationModel.id}`;
this.lessonTopic = 'Community Lessons';
this.lessonTopic = 'Community Lesson';
}

getStorySummaryThumbnailUrl(filename: string, id: string): string {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<div class="d-flex flex-column h-100 mb-4 w-100" [ngStyle]="{'max-width': (tabType === 'homeFull' || tabType.includes('progress') ? '100%' : 'fit-content')}">
<div class="d-flex justify-content-between">
<p class="card-display-heading"> {{ headingI18n | translate }} </p>
<div class="d-flex" *ngIf="(numCards > 1) && ((numCards - 1) * cardWidth + (cardWidth - 32) > cards.offsetWidth) && (tabType.includes('home'))">
<button [disabled]="currentShift === 0" class="mr-2 px-0 card-display-button" (click)="nextCard(currentShift - 1)">
<span class="fas fa-chevron-left"></span>
</button>
<p> </p>
<button [disabled]="currentShift === getMaxShifts(cards.offsetWidth)" class="px-0 card-display-button" (click)="nextCard(currentShift + 1)">
<span class="fas fa-chevron-right"></span>
</button>
</div>
</div>
<hr color="#00645c" class="mt-2 mb-4" size="2" width="100%">
<div class="d-flex card-display-content-container pb-1 mb-n1" #cards>
<ng-content></ng-content>
</div>
<!--TODO(#18384): Implement view more for progress tab-->
<button *ngIf="!tabType.includes('home')" class="align-self-end card-display-more-button mt-4">
{{ 'I18N_LEARNER_DASHBOARD_CARD_DISPLAY_BUTTON' | translate }}
<span class="fas fa-chevron-down ml-2"></span>
</button>
</div>

<style>
:host {
max-width: fit-content;
overflow: hidden;
}

.card-display-button {
align-items: center;
background-color: #00645c;
border: none;
border-radius: .25em;
color: #fff;
display: flex;
height: 24px;
justify-content: center;
width: 24px;
}

.card-display-button:disabled {
background-color: #667085;
}

.card-display-button:not(:disabled):hover {
background-color: #429488;
}

.card-display-more-button {
align-items: center;
background-color: #fff;
border: none;
border-radius: .25em;
color: #00645c;
display: flex;
font-weight: 500;
justify-content: center;
padding: 8px;
}

.card-display-content-container {
display: flex;
max-width: 100%;
overflow-x: hidden;
padding: .5px;
}

.card-display-heading {
color: #333;
font-size: 20px;
font-weight: 500;
margin-bottom: 0;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright 2024 The Oppia Authors. 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.

/**
* @fileoverview Unit tests for CardDisplayComponent
*/

import {HttpClientTestingModule} from '@angular/common/http/testing';
import {FormsModule} from '@angular/forms';
import {waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing';
import {MockTranslatePipe} from 'tests/unit-test-utils';
import {CardDisplayComponent} from './card-display.component';
import {NO_ERRORS_SCHEMA} from '@angular/core';

describe('CardDisplayComponent', () => {
let component: CardDisplayComponent;
let fixture: ComponentFixture<CardDisplayComponent>;
let scrollLeftSetterSpy: jasmine.Spy;

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [FormsModule, HttpClientTestingModule],
declarations: [CardDisplayComponent, MockTranslatePipe],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(CardDisplayComponent);
component = fixture.componentInstance;

component.numCards = 5;
component.tabType = 'home';
component.headingI18n = 'I18N_LEARNER_DASHBOARD_HOME_SAVED_SECTION';
fixture.detectChanges();

spyOnProperty(
component.cards.nativeElement,
'offsetWidth',
'get'
).and.returnValue(400);
scrollLeftSetterSpy = spyOnProperty(
component.cards.nativeElement,
'scrollLeft',
'set'
);
});

it('should return 0 shifts for getMaxShifts if container can fit cards perfectly', () => {
component.numCards = 2;

expect(component.getMaxShifts(650)).toEqual(0);
});

it('should return correct number of shifts for getMaxShifts if container cannot fit cards', () => {
expect(component.getMaxShifts(400)).toEqual(4);
});

describe('when shifting cards container to the right', () => {
it('should shift by (cardWidth - 32) if first shift to the right', () => {
component.nextCard(1);

fixture.detectChanges();

expect(scrollLeftSetterSpy.calls.mostRecent().args[0]).toBe(200);
});

it('should shift by cardWidth if not first shift or last to the right', () => {
spyOnProperty(
component.cards.nativeElement,
'scrollLeft',
'get'
).and.returnValue(200);

component.currentShift = 1;
component.nextCard(2);

fixture.detectChanges();

expect(scrollLeftSetterSpy.calls.mostRecent().args[0]).toBe(432);
});

it('should shift by remainder needed to show last card if last shift to the right', () => {
spyOnProperty(
component.cards.nativeElement,
'scrollLeft',
'get'
).and.returnValue(664);

component.currentShift = 3;
component.nextCard(4);

fixture.detectChanges();

expect(scrollLeftSetterSpy.calls.mostRecent().args[0]).toBe(756.5);
});
});

describe('when shifting cards container to the left', () => {
it('should shift by remainder needed to show last if first shift', () => {
spyOnProperty(
component.cards.nativeElement,
'scrollLeft',
'get'
).and.returnValue(756.5);

component.currentShift = 4;
component.nextCard(3);

fixture.detectChanges();

expect(scrollLeftSetterSpy.calls.mostRecent().args[0]).toBe(664);
});

it('should shift by cardWidth if not first shift or last', () => {
spyOnProperty(
component.cards.nativeElement,
'scrollLeft',
'get'
).and.returnValue(664);

component.currentShift = 3;
component.nextCard(2);

fixture.detectChanges();

expect(scrollLeftSetterSpy.calls.mostRecent().args[0]).toBe(432);
});

it('should shift by (cardWidth - 32) if last shift', () => {
spyOnProperty(
component.cards.nativeElement,
'scrollLeft',
'get'
).and.returnValue(200);

component.currentShift = 1;
component.nextCard(0);

fixture.detectChanges();

expect(scrollLeftSetterSpy.calls.mostRecent().args[0]).toBe(0);
});
});
});
Loading

0 comments on commit 9cc8aab

Please sign in to comment.