From 5c422bef7d9093c316776fcf64929d311f608852 Mon Sep 17 00:00:00 2001 From: Kamil Kondratowicz Date: Tue, 22 Oct 2024 01:07:32 +0200 Subject: [PATCH] feat: update module to use the latest ver. of ovee.ja and add docs (#65) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: update module to use the latest ver. of ovee.ja and add docs * refactor: update typings a little --------- Co-authored-by: Kamil Kondratowicz Co-authored-by: MiƂosz Mandowski --- docs/guide/addons/content-loader/module.md | 129 ++++++++++++++++++ docs/guide/addons/content-loader/resolver.md | 57 ++++++++ packages/ovee-content-loader/package.json | 2 +- .../ovee-content-loader/src/ContentLoader.ts | 102 ++++++++------ packages/ovee-content-loader/src/Resolver.ts | 12 +- yarn.lock | 2 +- 6 files changed, 257 insertions(+), 47 deletions(-) create mode 100644 docs/guide/addons/content-loader/module.md create mode 100644 docs/guide/addons/content-loader/resolver.md diff --git a/docs/guide/addons/content-loader/module.md b/docs/guide/addons/content-loader/module.md new file mode 100644 index 0000000..4aa9b71 --- /dev/null +++ b/docs/guide/addons/content-loader/module.md @@ -0,0 +1,129 @@ +# ContentLoader Module + +The ContentLoader module is responsible for loading and transitioning content. It uses resolvers to manage different types of content transitions and handles errors during the content loading process. + +## Constants + +- `DEFAULT_TIMEOUT`: The default timeout value for loading content, set to 20,000 milliseconds (20 seconds). + +## Methods + +### `async loadPage(url, resolverName, target, pushState): Promise` + +Asynchronously loads new content using the specified [resolver](./resolver.md). + +- `url: string` - The URL of the page to load. +- `resolverName: string` - The name of the resolver to use for the content transition. +- `target?: Element | null `- The target DOM element for the content. Defaults to `null`. +- `pushState?: boolean` - Indicates whether to push the state to the history. Defaults to `true`. + +## How it's works + +1. Makes a request to load the content from the specified URL with a timeout. +2. Calls the `contentOut` method on the resolver to manage the transition of the old content. +3. Attempts to fetch and parse the new content. + 1. If it occurs error, it calls the `handleError` method and the process ends. + 2. If fetch attempt is successfull and the `Resolver` should push the state, it updates the browser's history (calls the `updateHistory` method). +4. Calls the resolver's `updateContent` method to update the DOM with the new content. +5. Finally, calls the `contentIn` method on the resolver to finalize the transition. + +## Types + +### ContentLoaderOptions + +Defines the options that can be passed to the `ContentLoader` module. + +- `resolvers: Record` - Required. A record of resolver classes keyed by a string identifier. These resolvers are responsible for managing different aspects of content transitions. +- `timeout?: number` - Optional. The timeout duration in milliseconds for content loading requests. + +### ContentLoaderReturn + +The return type of the ContentLoader module, providing a method for loading pages. + +`loadPage: (url: string, resolverName: string, target?: Element | null, pushState?: boolean) => Promise` - Function to load content using the specified resolver and handle the content transition. + +## Example + +::: code-group +```ts [app.ts] +import { createApp } from 'ovee.js'; +import OveeBarba from '@ovee.js/barba'; + +import { ContentLoader } from '@ovee.js/content-loader'; // [!code focus] +import { DefaultResolver } from './DefaultResolver'; + +const root = document.body; + +createApp() + .use('ContentLoader', ContentLoader, { // [!code focus] + timeout: 15000, // [!code focus] + resolvers: { // [!code focus] + 'default': DefaultResolver, // [!code focus] + } // [!code focus] + }) // [!code focus] + .run(root); +``` + +```ts [DefaultResolver.ts] +import { Resolver } from '@ovee.js/content-loader'; +import gsap from 'gsap'; + +export class DefaultResolver extends Resolver { + oldContent: HTMLElement | null = null; + newContent: HTMLElement | null = null; + shoudlPushState = true; + + async contentOut() { + this.oldContent = document.querySelector('.content'); + + await gsap.to(this.oldContent, { + autoAlpha: 0, + }); + } + + handleError() { + window.alert('Oops, something went wrong'); + + gsap.set(this.oldContent, { + autoAlpha: 1, + }); + } + + async updateContent(doc: Document) { + this.newContent = doc.querySelector('.content'); + + if(!this.newContent) { + return; + } + + gsap.set(this.oldContent, { + autoAlpha: 0 + }); + + this.oldContent.insertAfter(this.newContent); + this.oldContent.remove(); + } + + async contentIn() { + await gsap.to(this.newContent, { + autoAlpha: 1 + }); + } +} +``` + +```ts [LoadMoreComponent.ts] +export const LoadMore = defineComponent((element) => { + const contentLoader = useModule('ContentLoader', true); + const nextPageUrl = useDataAttr('next-page-url'); + + async function loadMore() { + if(!nextPageUrl) { + return; + } + + await contentLoader?.loadPage(nextPageUrl, 'default', null, false); + } +}) +``` +::: \ No newline at end of file diff --git a/docs/guide/addons/content-loader/resolver.md b/docs/guide/addons/content-loader/resolver.md new file mode 100644 index 0000000..3a50011 --- /dev/null +++ b/docs/guide/addons/content-loader/resolver.md @@ -0,0 +1,57 @@ +# Resolver + +The Resolver class is responsible for managing content transitions and history updates. It integrates with the `@barba/core` library for history management. + +## Props + +- `shouldPushState: boolean` - Indicates whether the state should be pushed to the history. + +## Methods + +### `async contentOut(): Promise` + +This asynchronous method is intended to handle the transition of the old content. It's an empty method that can be overridden in subclasses. + +### `async updateContent(doc: Document): Promise` + +Updates the content based on the provided `Document`. This method is asynchronous and is designed to be overridden in subclasses. + +`doc: Document` - The new document content. + +### `updateHistory(title: string, url: string): void` + +Updates the browser's history and document title. + +`title: string` - The new title for the document. +`url: string` - The URL to add to the history. + +Default: + +```ts +updateHistory(title: string, url: string): void { + document.title = title; + history.add(url, 'barba'); +} +``` + +### `handleError(): void` + +Handles errors that may occur during the content update or navigation process. The method is currently a stub and can be implemented in subclasses. + +### `async contentIn(): Promise` + +This asynchronous method is intended to handle the transition of new content. Like `contentOut`, it's an empty method that can be overridden in subclasses. + +## Type Alias + +### `ResolverClass` + +A TypeScript type alias representing the `Resolver` class. + +```ts +export type ResolverClass = typeof Resolver; +``` + +## Example + +[See example](./module.md#example) diff --git a/packages/ovee-content-loader/package.json b/packages/ovee-content-loader/package.json index 793c5ad..1b4408f 100644 --- a/packages/ovee-content-loader/package.json +++ b/packages/ovee-content-loader/package.json @@ -24,7 +24,7 @@ }, "peerDependencies": { "@barba/core": "^2.9.7", - "ovee.js": "2.0.0 - 2.2.x" + "ovee.js": "^3.0.0-alpha.6" }, "scripts": { "lint": "eslint . --fix", diff --git a/packages/ovee-content-loader/src/ContentLoader.ts b/packages/ovee-content-loader/src/ContentLoader.ts index 2136392..912cc60 100644 --- a/packages/ovee-content-loader/src/ContentLoader.ts +++ b/packages/ovee-content-loader/src/ContentLoader.ts @@ -1,68 +1,82 @@ -import Barba from '@barba/core'; -import { Module } from 'ovee.js'; +import barba from '@barba/core'; +import { defineModule } from 'ovee.js'; import { ResolverClass } from './Resolver'; -const { history, dom, request } = Barba; export const DEFAULT_TIMEOUT = 20000; export interface ContentLoaderOptions { + resolvers: Record; timeout?: number; - resolvers?: Record; } -export class ContentLoader extends Module { - timeout = DEFAULT_TIMEOUT; - resolvers: Record = {}; +export interface ContentLoaderReturn { + loadPage: LoadPage; +} - init(): void { - this.timeout = this.options.timeout ?? DEFAULT_TIMEOUT; - this.resolvers = this.options.resolvers ?? {}; - } +type LoadPage = ( + url: string, + resolverName: string, + target?: Element | null, + pushState?: boolean +) => Promise; + +export const ContentLoader = defineModule( + ({ app, options }) => { + const timeout = options.timeout ?? DEFAULT_TIMEOUT; + const resolvers: Record = options.resolvers; - getResolver(name: string): ResolverClass | false { - if (this.resolvers[name] === undefined) { - console.error(`Resolver not registered for key ${name}`); + function getResolver(name: string): ResolverClass | false { + if (resolvers[name] === undefined) { + console.error(`[ovee.js/ContentLoader] Resolver not registered for key ${name}`); - return false; + return false; + } + + return resolvers[name]; } - return this.resolvers[name]; - } + const loadPage: LoadPage = async (url, resolverName, target = null, pushState = true) => { + const ResolverCtor = getResolver(resolverName); - async loadPage( - url: string, - resolverName: string, - target: Element | null = null, - pushState = true - ): Promise { - const ResolverCtor = this.getResolver(resolverName); + if (!ResolverCtor) { + return; + } - if (!ResolverCtor) { - return; - } + const resolver = new ResolverCtor(app, target, url, pushState); + const requestPage = barba.request(url, timeout, (reqUrl, reqErr) => { + console.error(`[ovee.js/ContentLoader] Error while requesting ${reqUrl}`, reqErr); - const resolver = new ResolverCtor(this.$app, target, url, pushState); - const requestPage = request(url, this.timeout, (_url, err) => { - console.error(`[ContentLoader] Error while requesting ${_url}`, err); + return false; + }); - return false; - }); + await resolver.contentOut(); - await resolver.contentOut(); + let content: string | null = null; - const content = dom.toDocument(await requestPage); + try { + content = await requestPage; + } catch { + resolver.handleError(); + } - if (resolver.pushState && resolver.shouldPushState) { - document.title = content.title; - history.add(url, 'barba'); - } + if (!content) { + return; + } - await resolver.updateContent(content); - await resolver.contentIn(); - } + const parser = new DOMParser(); + const doc = parser.parseFromString(content, 'text/html'); + + if (resolver.pushState && resolver.shouldPushState) { + resolver.updateHistory(doc.title, url); + } - static getName(): string { - return 'ContentLoader'; + await resolver.updateContent(doc); + await resolver.contentIn(); + }; + + return { + loadPage, + }; } -} +); diff --git a/packages/ovee-content-loader/src/Resolver.ts b/packages/ovee-content-loader/src/Resolver.ts index 75a8556..1cfcb12 100644 --- a/packages/ovee-content-loader/src/Resolver.ts +++ b/packages/ovee-content-loader/src/Resolver.ts @@ -1,5 +1,8 @@ +import barba from '@barba/core'; import { App } from 'ovee.js'; +const { history } = barba; + export class Resolver { shouldPushState = false; @@ -15,8 +18,15 @@ export class Resolver { async contentOut(): Promise {} + updateHistory(title: string, url: string): void { + document.title = title; + history.add(url, 'barba'); + } + + handleError(): void {} + // eslint-disable-next-line @typescript-eslint/no-unused-vars - async updateContent(content: HTMLDocument): Promise {} + async updateContent(doc: Document): Promise {} } export type ResolverClass = typeof Resolver; diff --git a/yarn.lock b/yarn.lock index 895f3fb..99cdfa0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4217,7 +4217,7 @@ __metadata: ovee.js: "workspace:*" peerDependencies: "@barba/core": ^2.9.7 - ovee.js: 2.0.0 - 2.2.x + ovee.js: ^3.0.0-alpha.6 languageName: unknown linkType: soft