Skip to content

Commit

Permalink
Calendar: Start writing HolidayViewModel
Browse files Browse the repository at this point in the history
And introduce (and document) separate content indices and page numbers.
  • Loading branch information
SLaks committed Oct 10, 2024
1 parent 28c5c5a commit e6e01a1
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 10 deletions.
18 changes: 18 additions & 0 deletions src/calendar-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,21 @@ This class (and associated helper functions) has several purposes:
- Fetch pages as the user scrolls
- Generate titles and navigation links for the header UI
- Calculate עלייה labels to show alongside the scroll

### Page Indices

The base `ScrollViewModel` class deals in abstract content indices – an index into a contiguous range of rendered content.

`FullScrollViewModel` simply renders every page of a scroll (this is used to render all of חומש for regular פרשיות, and to render מגילות). Therefore, it translates content indices directly to page numbers within the scroll.

`HolidayViewModel` renders only the pages that contain a particular day's leining. When multiple ספרי תורה are used, it will also render a message between those pages indicating how many עמודים are skipped between leinings (this can be useful to plan which ספרי תורה to scroll where, especially when sharing between multiple Minyanim).

Therefore, it maps the contiguous content indices to whatever pages from the scroll are rendered in this view, including the skip messages.

This logic is implemented in the `pageNumberFromContentIndex()` and `contentIndexFromPageNumber()`, which convert back and forth between these two indices. `pageNumberFromContentIndex()` can return either a page number or a message to render for that particular content index.

Note: Page numbers (which correspond to JSON page files and values in the tables of contents) are 1-based, whereas content indices are 0-based.

These methods (and the array in `HolidayViewModel` that backs them) specify exactly what gets displayed when a יום טוב is selected.

Tip: We could easily change this to allow a single `ScrollViewModel` to contain multiple scrolls as well. This would let us show the הפטרה for יום טוב by scrolling down past מפטיר, rather than clicking a navigation button. To do this, `pageNumber` in these two methods would change to a tuple of page number and scroll.
69 changes: 59 additions & 10 deletions src/calendar-model/scroll-view-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ export interface ViewModelState {
readonly nextRun: LeiningRun
}

/** Information to render a single page from a scroll. */
export interface RenderedPageInfo {
type: 'page'
lines: RenderedLineInfo[]

/**
Expand All @@ -25,6 +27,15 @@ export interface RenderedPageInfo {
run: LeiningRun
}

/** Information to render a message between `RenderedPageInfo`s. */
export interface RenderedMessageInfo {
type: 'message'
text: string
}

/** A single entry rendered as the user scrolls. */
export type RenderedEntry = RenderedPageInfo | RenderedMessageInfo

/**
* Information to render a single line in the UI.
* Most of these properties come from the JSON files in pages/.
Expand Down Expand Up @@ -56,9 +67,9 @@ export interface RenderedLineInfo {

/** Tracks scrolling through a single "view" of a scroll, associated with one or more LeiningRuns. */
export class ScrollViewModel {
private readonly currentPage: ReturnType<typeof IntegerIterator.new>
private readonly currentContentIndex: ReturnType<typeof IntegerIterator.new>
readonly startingLocation: Promise<{
page: RenderedPageInfo
page: RenderedEntry
lineNumber: number
}>
protected constructor(
Expand All @@ -69,7 +80,10 @@ export class ScrollViewModel {
// TODO(later): Load TOCs lazily.
// This means converting currentPage to a promise and awaiting it everywhere.
const { pageNumber, lineNumber } = physicalLocationFromRef(initialRef)
this.currentPage = IntegerIterator.new({ startingAt: pageNumber })

this.currentContentIndex = IntegerIterator.new({
startingAt: this.contentIndexFromPageNumber(pageNumber),
})
this.startingLocation = this.fetchPage(pageNumber).then((page) => ({
page,
lineNumber,
Expand All @@ -87,17 +101,40 @@ export class ScrollViewModel {
return new HolidayViewModel(generator, run)
}

fetchPreviousPage(): Promise<RenderedPageInfo> {
return this.fetchPage(this.currentPage.previous())
fetchPreviousPage(): Promise<RenderedEntry> {
return this.fetchPage(this.currentContentIndex.previous())
}

fetchNextPage(): Promise<RenderedPageInfo> {
return this.fetchPage(this.currentPage.next())
fetchNextPage(): Promise<RenderedEntry> {
return this.fetchPage(this.currentContentIndex.next())
}

private async fetchPage(index: number): Promise<RenderedPageInfo> {
/**
* Calculates the page number (within the scroll) to fetch, or
* a fixed string, to render for the given (contiguous) index.
*
* This is overridden to render only a subset of pages.
*
* See the Readme for more background.
*/
protected pageNumberFromContentIndex(
contentIndex: number
): number | RenderedMessageInfo {
// Page numbers in the JSON are 1-based
return contentIndex + 1
}
/** Returns the (contiguous) index at which the given page is rendered. */
protected contentIndexFromPageNumber(pageNumber: number): number {
// Content indices are 0-based.
return pageNumber - 1
}

private async fetchPage(contentIndex: number): Promise<RenderedEntry> {
const pageNumber = this.pageNumberFromContentIndex(contentIndex)
if (typeof pageNumber === 'object') return pageNumber

const page: LineType[] = (
await import(`../data/pages/${this.allRuns[0].scroll}/${index}.json`)
await import(`../data/pages/${this.allRuns[0].scroll}/${pageNumber}.json`)
).default
let run: LeiningRun | undefined
let relevantRuns: LeiningRun[] = []
Expand All @@ -120,7 +157,7 @@ export class ScrollViewModel {
labels: getLabels(relevantRuns, verses),
}
})
return { lines, run: lines.find((o) => o.run)!.run! }
return { type: 'page', lines, run: lines.find((o) => o.run)!.run! }
}
}

Expand Down Expand Up @@ -149,9 +186,21 @@ class FullScrollViewModel extends ScrollViewModel {

/** A view that only renders pages containing the actual leinings. Used for יום טוב. */
class HolidayViewModel extends ScrollViewModel {
private readonly pages: Array<number | RenderedMessageInfo>
constructor(generator: LeiningGenerator, run: LeiningRun) {
// TODO(decide): Should this include the whole LeiningDate?
super(run.leining.runs, run.aliyot[0].start)

// TODO: Populate pages from the scroll.
}

protected override pageNumberFromContentIndex(
index: number
): number | RenderedMessageInfo {
return this.pages[index]
}
protected override contentIndexFromPageNumber(pageNumber: number): number {
return this.pages.indexOf(pageNumber)
}
}

Expand Down

0 comments on commit e6e01a1

Please sign in to comment.