diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html b/src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html index d620b6a1278..f2fe7c1db9d 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.html @@ -32,17 +32,12 @@ }
@for (book of bookOptions; track book) { - - - {{ "canon.book_names." + book.bookId | transloco }} - - + > }
diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts index 34e0e2bd219..516f731701d 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/shared/book-multi-select/book-multi-select.component.ts @@ -3,6 +3,7 @@ import { MatChipsModule } from '@angular/material/chips'; import { TranslocoModule } from '@ngneat/transloco'; import { Canon } from '@sillsdev/scripture'; import { UICommonModule } from 'xforge-common/ui-common.module'; +import { ToggleBookComponent } from '../../translate/draft-generation/toggle-book/toggle-book.component'; export interface BookOption { bookNum: number; @@ -14,7 +15,7 @@ export interface BookOption { selector: 'app-book-multi-select', templateUrl: './book-multi-select.component.html', standalone: true, - imports: [UICommonModule, MatChipsModule, TranslocoModule], + imports: [UICommonModule, MatChipsModule, TranslocoModule, ToggleBookComponent], styleUrls: ['./book-multi-select.component.scss'] }) export class BookMultiSelectComponent implements OnChanges { diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.html b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.html new file mode 100644 index 00000000000..51cc617a5c6 --- /dev/null +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.html @@ -0,0 +1,19 @@ + + + + {{ bookName(book) }} + + + diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.scss b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.scss new file mode 100644 index 00000000000..3812ee60761 --- /dev/null +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.scss @@ -0,0 +1,51 @@ +:host { + --progress-color: hsl(0, 0%, 80%); + --progress-hover-color: hsl(0, 0%, 70%); + --progress-bg-color: hsl(0, 0%, 90%); + --progress-hover-bg-color: hsl(0, 0%, 80%); + + user-select: none; +} + +.wrapper { + display: inline-block; + border-radius: 1000px; +} + +.wrapper:has(.selected) { + background-image: var(--background-image); +} + +.book { + display: block; + background-color: var(--progress-bg-color); + padding: 0 16px; + line-height: 32px; + border-radius: 1000px; + margin: var(--border-width); + position: relative; + + background-image: linear-gradient( + 90deg, + var(--progress-color) var(--progress), + var(--progress-bg-color) var(--progress) + ); + + &.disabled:not(.selected) { + opacity: 0.7; + } + + &:not(.disabled) { + cursor: pointer; + } + + &:hover:not(.disabled), + &:focus:not(.disabled) { + outline: none; + background-image: linear-gradient( + 90deg, + var(--progress-hover-color) var(--progress), + var(--progress-hover-bg-color) var(--progress) + ); + } +} diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts new file mode 100644 index 00000000000..32075e49a3d --- /dev/null +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.component.ts @@ -0,0 +1,70 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { MatRippleModule } from '@angular/material/core'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { TranslocoModule } from '@ngneat/transloco'; +import { I18nService } from '../../../../xforge-common/i18n.service'; + +@Component({ + selector: 'app-toggle-book', + standalone: true, + imports: [TranslocoModule, MatTooltipModule, MatRippleModule], + templateUrl: './toggle-book.component.html', + styleUrl: './toggle-book.component.scss' +}) +export class ToggleBookComponent { + @Output() selectedChanged = new EventEmitter(); + @Input() selected = false; + @Input() disabled = false; + @Input() borderWidth = 2; + @Input() book!: number; + @Input() progress?: number; + @Input() hues: number[] = [230]; + + constructor(private readonly i18n: I18nService) {} + + bookName(book: number): string { + return this.i18n.localizeBook(book); + } + + toggleSelected(): void { + if (!this.disabled) { + this.selected = !this.selected; + this.selectedChanged.emit(this.book); + } + } + + onKeyPress(event: KeyboardEvent): void { + if (event.key === 'Enter' || event.key === ' ') { + this.toggleSelected(); + event.preventDefault(); + } + } + + get backgroundCssGradientStripes(): string { + const percentPerStripe = 12.5; + const colors = this.hues.map(hue => `hsl(${hue}, 80%, 60%)`); + let gradient = []; + for (const [index, color] of colors.entries()) { + const from = index * percentPerStripe; + const to = (index + 1) * percentPerStripe; + gradient.push(`${color} ${from}%, ${color} ${to}%`); + } + return `repeating-linear-gradient(135deg, ${gradient.join(', ')})`; + } + + get progressCssValue(): string { + return `${(this.progress ?? 0) * 100}%`; + } + + get borderWidthCssValue(): string { + return `${this.borderWidth}px`; + } + + get progressDescription(): string { + if (this.progress == null) return ''; + + // avoid showing 100% when it's not quite there + let percent = this.progress > 0.99 && this.progress < 1 ? 99 : Math.round(this.progress * 100); + return this.progress != null ? `${Math.round(percent)}% translated` : ''; + } +} diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.stories.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.stories.ts new file mode 100644 index 00000000000..e9dcfb187a7 --- /dev/null +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/toggle-book/toggle-book.stories.ts @@ -0,0 +1,68 @@ +import { CommonModule } from '@angular/common'; +import { TranslocoModule } from '@ngneat/transloco'; +import { Meta, moduleMetadata, StoryObj } from '@storybook/angular'; +import { TranslocoMarkupModule } from 'ngx-transloco-markup'; +import { I18nStoryModule } from '../../../../xforge-common/i18n-story.module'; +import { UICommonModule } from '../../../../xforge-common/ui-common.module'; +import { ToggleBookComponent } from './toggle-book.component'; + +const meta: Meta = { + title: 'Translate/ToggleBook', + component: ToggleBookComponent, + decorators: [ + moduleMetadata({ + imports: [UICommonModule, I18nStoryModule, CommonModule, TranslocoModule, TranslocoMarkupModule] + }) + ], + argTypes: { + progress: { control: { type: 'range', min: 0, max: 1, step: 0.01 } } + } +}; + +export default meta; + +interface StoryState { + book: number; + progress?: number; + hues: number[]; + selected: boolean; + disabled: boolean; +} + +type Story = StoryObj; + +export const Default: Story = { + args: { + book: 1, + progress: 0.37, + hues: [0] + } +}; + +export const Selected: Story = { + args: { + ...Default.args, + selected: true + } +}; + +export const TwoColor: Story = { + args: { + ...Selected.args, + hues: [0, 240] + } +}; + +export const ThreeColor: Story = { + args: { + ...Selected.args, + hues: [0, 120, 240] + } +}; + +export const Disabled: Story = { + args: { + book: 8, + disabled: true + } +};