Skip to content

Commit

Permalink
feat: rewrite of frame loader
Browse files Browse the repository at this point in the history
  • Loading branch information
mbret committed Aug 9, 2024
1 parent 8baf5f2 commit f9e0044
Show file tree
Hide file tree
Showing 18 changed files with 416 additions and 326 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/enhancers/layoutEnhancer/layoutEnhancer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const layoutEnhancer =
* Consider creating a bug ticket on both chromium and gecko projects.
*/
reader.spineItemsManager.items.forEach((item) => {
const frame = item.frame.getFrameElement()
const frame = item.frame.element

if (!hasRedrawn && frame) {
/* eslint-disable-next-line no-void */
Expand All @@ -113,7 +113,7 @@ export const layoutEnhancer =

if (
item?.item.renditionLayout === `reflowable` &&
frame?.getIsReady() &&
frame?.isReady &&
!isImageType() &&
!frame.getViewportDimensions()
) {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/enhancers/pagination/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ export const mapPaginationInfoToExtendedInfo =
// domIndex: number;
// charOffset: number;
// serializeString?: string;
beginSpineItemReadingDirection: beginItem?.getReadingDirection(),
beginSpineItemReadingDirection: beginItem?.readingDirection,
endChapterInfo: endItem ? chaptersInfo[endItem.item.id] : undefined,
endSpineItemReadingDirection: endItem?.getReadingDirection(),
endSpineItemReadingDirection: endItem?.readingDirection,
// spineItemReadingDirection: focusedSpineItem?.getReadingDirection(),
/**
* This percentage is based of the weight (kb) of every items and the number of pages.
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/hooks/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Manifest } from "@prose-reader/shared"
import { Observable } from "rxjs"
import { type createFrameItem } from "../spineItem/frame/FrameItem"

export type UserDestroyFn = () => void | Observable<unknown>

Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/spine/loader/SpineItemsLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ReaderSettingsManager } from "../../settings/ReaderSettingsManager"
import { DestroyableClass } from "../../utils/DestroyableClass"
import { loadItems } from "./loadItems"
import { mapToItemsToLoad } from "./mapToItemsToLoad"
import { waitForSwitch } from "../../utils/rxjs"

export class SpineItemsLoader extends DestroyableClass {
constructor(
Expand Down Expand Up @@ -45,8 +46,11 @@ export class SpineItemsLoader extends DestroyableClass {
* layout method we have to take it into consideration.
*/
const loadSpineItems$ = merge(navigationUpdate$, layoutHasChanged$).pipe(
// this can be changed by whatever we want and SHOULD not break navigation.
// Ideally loading faster is better but loading too close to user navigating can
// be dangerous.
debounceTime(100, animationFrameScheduler),
withLatestFrom(this.context.bridgeEvent.viewportFree$),
waitForSwitch(this.context.bridgeEvent.viewportFree$),
withLatestFrom(this.context.bridgeEvent.navigation$),
map(([, navigation]) => navigation.position),
mapToItemsToLoad({ spineLocator }),
Expand Down
8 changes: 2 additions & 6 deletions packages/core/src/spine/locator/SpineLocator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import { getSpineItemFromPosition } from "./getSpineItemFromPosition"
import { getVisibleSpineItemsFromPosition } from "./getVisibleSpineItemsFromPosition"
import { getItemVisibilityForPosition } from "./getItemVisibilityForPosition"

export type SpineLocator = ReturnType<
typeof createSpineLocator
>
export type SpineLocator = ReturnType<typeof createSpineLocator>

export const createSpineLocator = ({
spineItemsManager,
Expand Down Expand Up @@ -105,9 +103,7 @@ export const createSpineLocator = ({
}

const getSpineItemFromIframe = (iframe: Element) => {
return spineItemsManager
.items
.find((item) => item.frame.getFrameElement() === iframe)
return spineItemsManager.items.find((item) => item.frame.element === iframe)
}

const getSpineItemPageIndexFromNode = (
Expand Down
38 changes: 19 additions & 19 deletions packages/core/src/spineItem/commonSpineItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const createCommonSpineItem = ({
const overlayElement = createOverlayElement(parentElement, item)
const fingerTracker = createFingerTracker()
const selectionTracker = createSelectionTracker()
const frame = new FrameItem(
const frameItem = new FrameItem(
containerElement,
item,
context,
Expand Down Expand Up @@ -74,8 +74,8 @@ export const createCommonSpineItem = ({
}

const injectStyle = (cssText: string) => {
frame.removeStyle(`prose-reader-css`)
frame.addStyle(`prose-reader-css`, cssText)
frameItem.removeStyle(`prose-reader-css`)
frameItem.addStyle(`prose-reader-css`, cssText)
}

const adjustPositionOfElement = ({
Expand Down Expand Up @@ -106,8 +106,8 @@ export const createCommonSpineItem = ({

const getViewPortInformation = () => {
const { width: pageWidth, height: pageHeight } = context.getPageSize()
const viewportDimensions = frame.getViewportDimensions()
const frameElement = frame.element
const viewportDimensions = frameItem.getViewportDimensions()
const frameElement = frameItem.element

if (
containerElement &&
Expand All @@ -125,12 +125,12 @@ export const createCommonSpineItem = ({
}
}

const load = () => frame.load()
const load = () => frameItem.load()

const unload = () => frame.unload()
const unload = () => frameItem.unload()

const getBoundingRectOfElementFromSelector = (selector: string) => {
const frameElement = frame.element
const frameElement = frameItem.element
if (frameElement && selector) {
if (selector.startsWith(`#`)) {
return frameElement.contentDocument
Expand Down Expand Up @@ -206,7 +206,7 @@ export const createCommonSpineItem = ({
// window (viewport). This is handy because we can easily get the translated x/y without any extra information
// such as page index, etc. However this might be a bit less performance to request heavily getBoundingClientRect
const { left = 0, top = 0 } =
frame.getFrameElement()?.getBoundingClientRect() || {}
frameItem.element?.getBoundingClientRect() || {}
const computedScale = getViewPortInformation()?.computedScale ?? 1
const adjustedX = position.clientX * computedScale + left
const adjustedY = position.clientY * computedScale + top
Expand Down Expand Up @@ -249,8 +249,8 @@ export const createCommonSpineItem = ({
...options,
})

const contentLayout$ = frame.contentLayoutChange$.pipe(
withLatestFrom(frame.isReady$),
const contentLayout$ = frameItem.contentLayoutChange$.pipe(
withLatestFrom(frameItem.isReady$),
map(([data, isReady]) => ({
isFirstLayout: data.isFirstLayout,
isReady,
Expand All @@ -263,22 +263,22 @@ export const createCommonSpineItem = ({
overlayElement,
adjustPositionOfElement,
getElementDimensions,
getHtmlFromResource: frame.getHtmlFromResource,
getHtmlFromResource: frameItem.getHtmlFromResource,
getResource,
translateFramePositionIntoPage,
injectStyle,
load,
unload,
frame,
frame: frameItem,
element: containerElement,
getBoundingRectOfElementFromSelector,
getViewPortInformation,
isImageType,
isReady: frame.getIsReady,
isReady: () => frameItem.isReady,
destroy: () => {
destroySubject$.next()
containerElement.remove()
frame?.destroy()
frameItem?.destroy()
fingerTracker.destroy()
selectionTracker.destroy()
destroySubject$.complete()
Expand All @@ -297,23 +297,23 @@ export const createCommonSpineItem = ({
return `rtl`
}

const direction = this.frame.loader.getComputedStyleAfterLoad()?.direction
const direction = this.frame.getComputedStyleAfterLoad()?.direction
if ([`ltr`, `rtl`].includes(direction || ``))
return direction as `ltr` | `rtl`

return undefined
},
isUsingVerticalWriting: () =>
frame.getWritingMode()?.startsWith(`vertical`),
frameItem.getWritingMode()?.startsWith(`vertical`),
executeOnLayoutBeforeMeasurementHook: executeOnLayoutBeforeMeasurementHook,
selectionTracker,
fingerTracker,
getDimensionsForReflowableContent,
getDimensionsForPaginatedContent,
$: {
contentLayout$,
loaded$: frame.loaded$,
isReady$: frame.isReady$,
loaded$: frameItem.loaded$,
isReady$: frameItem.isReady$,
},
}
}
Expand Down
101 changes: 39 additions & 62 deletions packages/core/src/spineItem/frame/FrameItem.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { merge, Observable, Subject } from "rxjs"
import { merge, Observable } from "rxjs"
import { Manifest } from "../.."
import { Context } from "../../context/Context"
import {
Expand All @@ -14,7 +14,7 @@ import { type HookManager } from "../../hooks/HookManager"
import { DestroyableClass } from "../../utils/DestroyableClass"

export class FrameItem extends DestroyableClass {
public loader: ReturnType<typeof createLoader>
protected loader: ReturnType<typeof createLoader>

public contentLayoutChange$: Observable<{
isFirstLayout: boolean
Expand All @@ -37,43 +37,29 @@ export class FrameItem extends DestroyableClass {
settings,
})

this.loader.$.isLoaded$.subscribe({
next: (value) => {
this.isLoadedSync = value
},
})

this.loader.$.isReady$.subscribe({
next: (value) => {
this.isReadySync = value
},
})

/**
* This is used as upstream layout change. This event is being listened to by upper app
* in order to layout again and adjust every element based on the new content.
*/
this.contentLayoutChange$ = merge(
this.loader.$.unloaded$.pipe(map(() => ({ isFirstLayout: false }))),
this.loader.unloaded$.pipe(map(() => ({ isFirstLayout: false }))),
this.ready$.pipe(map(() => ({ isFirstLayout: true }))),
)
}

public destroySubject$ = new Subject<void>()
// @todo optimize
public getComputedStyleAfterLoad() {
const frame = this.loader.element
const body = frame?.contentDocument?.body

/**
* @deprecated
*/
public isLoadedSync = false

/**
* @deprecated
*/
public isReadySync = false
if (body) {
return frame?.contentWindow?.getComputedStyle(body)
}
}

// @todo memoize
public getViewportDimensions = () => {
const frame = this.loader.$.frameElement$.getValue()
const frame = this.loader.element

if (frame && frame?.contentDocument) {
const doc = frame.contentDocument
Expand Down Expand Up @@ -102,7 +88,7 @@ export class FrameItem extends DestroyableClass {
}

public getWritingMode = () => {
return this.loader.getComputedStyleAfterLoad()?.writingMode as
return this.getComputedStyleAfterLoad()?.writingMode as
| `vertical-rl`
| `horizontal-tb`
| undefined
Expand All @@ -116,21 +102,33 @@ export class FrameItem extends DestroyableClass {
return createHtmlPageFromResource(response, this.item)
}

get element() {
return this.loader.$.frameElement$.getValue()
public get element() {
return this.loader.element
}

/**
* @deprecated
*/
public getIsLoaded = () => this.isLoadedSync
public get unloaded$() {
return this.loader.unloaded$
}

/**
* @deprecated
*/
public getIsReady = () => this.isReadySync
public get loaded$() {
return this.loader.loaded$
}

public getFrameElement = () => this.loader.$.frameElement$.getValue()
public get ready$() {
return this.loader.ready$
}

public get isReady$() {
return this.loader.isReady$
}

public get isLoaded() {
return this.loader.state === "loaded" || this.loader.state === "ready"
}

public get isReady() {
return this.loader.state === "ready"
}

public load() {
this.loader.load()
Expand All @@ -147,7 +145,7 @@ export class FrameItem extends DestroyableClass {
* want the iframe to trigger a new `layout` even and have infinite loop.
*/
public staticLayout = (size: { width: number; height: number }) => {
const frame = this.loader.$.frameElement$.getValue()
const frame = this.loader.element
if (frame) {
frame.style.width = `${size.width}px`
frame.style.height = `${size.height}px`
Expand All @@ -160,45 +158,24 @@ export class FrameItem extends DestroyableClass {
}

public addStyle(id: string, style: string, prepend?: boolean) {
const frameElement = this.loader.$.frameElement$.getValue()
const frameElement = this.loader.element

if (frameElement) {
createAddStyleHelper(frameElement)(id, style, prepend)
}
}

public removeStyle(id: string) {
const frameElement = this.loader.$.frameElement$.getValue()
const frameElement = this.loader.element

if (frameElement) {
createRemoveStyleHelper(frameElement)(id)
}
}

get unload$() {
return this.loader.$.unload$
}

get unloaded$() {
return this.loader.$.unloaded$
}

get loaded$() {
return this.loader.$.loaded$
}

get ready$() {
return this.loader.$.ready$
}

get isReady$() {
return this.loader.$.isReady$
}

public destroy = () => {
super.destroy()

this.loader.unload()
this.loader.destroy()
}
}
Loading

0 comments on commit f9e0044

Please sign in to comment.