Skip to content

Commit

Permalink
Add typedoc (#95)
Browse files Browse the repository at this point in the history
This adds API documentation to most of the important methods exposed
publically to a superglue dev. Its meant to be paired with a larger
documentation website that is a wip.
  • Loading branch information
jho406 authored Aug 13, 2024
1 parent d62a5f5 commit 02f8c24
Show file tree
Hide file tree
Showing 12 changed files with 539 additions and 102 deletions.
21 changes: 10 additions & 11 deletions superglue/lib/action_creators/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@ import {
FetchArgs,
BeforeRemote,
HandleError,
RemoteProps,
VisitProps,
VisitResponse,
SuperglueState,
Meta,
MetaThunk,
Dispatch,
RemoteCreator,
VisitCreator,
} from '../types'

function beforeVisit(payload: {
Expand Down Expand Up @@ -95,16 +94,16 @@ function buildMeta(
}
}

export function remote(
path: string,
export const remote: RemoteCreator = (
path,
{
method = 'GET',
headers,
body,
pageKey: rawPageKey,
beforeSave = (prevPage, receivedPage) => receivedPage,
}: RemoteProps = {}
): MetaThunk {
} = {}
) => {
path = withoutBusters(path)
rawPageKey = rawPageKey && urlToPageKey(rawPageKey)

Expand Down Expand Up @@ -161,17 +160,17 @@ let lastVisitController = {
},
}

export function visit(
path: string,
export const visit: VisitCreator = (
path,
{
method = 'GET',
headers,
body,
placeholderKey,
beforeSave = (prevPage, receivedPage) => receivedPage,
revisit = false,
}: VisitProps = {}
): MetaThunk {
} = {}
) => {
path = withoutBusters(path)
let pageKey = urlToPageKey(path)

Expand Down
55 changes: 49 additions & 6 deletions superglue/lib/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { urlToPageKey, pathWithoutBZParams } from '../utils'
import { REMOVE_PAGE, HISTORY_CHANGE } from '../actions'
import {
HistoryState,
Keypath,
NavigationAction,
PageOwnProps,
Remote,
SuperglueStore,
Expand All @@ -25,11 +27,19 @@ interface State {
ownProps: Record<string, unknown>
}

/**
* A Nav component for browsers. It handles changine the browser history,
* deciding which page component to render based on a passed mapping, and
* passes a `navigateTo` to all page components.
*/
class Nav extends React.Component<Props, State> {
public history: History
public hasWindow: boolean
public unsubscribeHistory: () => void
private history: History
private hasWindow: boolean
private unsubscribeHistory: () => void

/**
* @ignore
*/
constructor(props: Props) {
super(props)
const { history, initialPageKey } = this.props
Expand All @@ -43,18 +53,39 @@ class Nav extends React.Component<Props, State> {
}
this.hasWindow = typeof window !== 'undefined'
}

/**
* @ignore
*/
componentDidMount(): void {
this.unsubscribeHistory = this.history.listen(this.onHistoryChange)
}

/**
* @ignore
*/
componentWillUnmount(): void {
this.unsubscribeHistory()
}

/**
* Passed to every page component. Manually navigate using pages that exists
* in the store and restores scroll position. This is what {@link Visit} in
* your `application_visit.js` ultimately calls.
*
* @param path
* @param options when `none`, immediately returns `false`
* @returns `true` if the navigation was a success, `false` if the page was not found in the
* store.
*/
navigateTo(
path: string,
{ action, ownProps } = { action: 'push', ownProps: {} }
path: Keypath,
{
action,
ownProps,
}: { action: NavigationAction; ownProps: Record<string, unknown> } = {
action: 'push',
ownProps: {},
}
): boolean {
if (action === 'none') {
return false
Expand Down Expand Up @@ -126,10 +157,16 @@ class Nav extends React.Component<Props, State> {
}
}

/**
* @ignore
*/
scrollTo(posX: number, posY: number): void {
this.hasWindow && window.scrollTo(posX, posY)
}

/**
* @ignore
*/
onHistoryChange({ location, action }: Update): void {
const { store, visit } = this.props
const { pathname, search, hash } = location
Expand Down Expand Up @@ -197,6 +234,9 @@ class Nav extends React.Component<Props, State> {
}
}

/**
* @ignore
*/
notFound(identifier: string | undefined): never {
let reminder = ''
if (!identifier) {
Expand All @@ -211,6 +251,9 @@ class Nav extends React.Component<Props, State> {
throw error
}

/**
* @ignore
*/
render(): JSX.Element {
const { store, visit, remote } = this.props
const { pageKey, ownProps } = this.state
Expand Down
89 changes: 69 additions & 20 deletions superglue/lib/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ export {
HISTORY_CHANGE,
} from './actions'

export {
mapStateToProps,
mapDispatchToProps,
mapDispatchToPropsIncludingVisitAndRemote,
} from './utils/react'
import { mapStateToProps, mapDispatchToProps } from './utils/react'
import {
Remote,
Expand All @@ -46,7 +41,7 @@ import {
Page,
JSONValue,
} from './types'
export { superglueReducer, pageReducer, rootReducer } from './reducers'
// export { superglueReducer, pageReducer, rootReducer } from './reducers'
export { fragmentMiddleware } from './middleware'
export { getIn } from './utils/immutability'
export { urlToPageKey }
Expand Down Expand Up @@ -101,31 +96,59 @@ function start({
initialPageKey,
}
}

interface Props {
/**
* Props for the `ApplicationBase` component
*/
interface ApplicationProps {
/**
* The global var SUPERGLUE_INITIAL_PAGE_STATE is set by your erb
* template, e.g., index.html.erb
*/
initialPage: VisitResponse
/**
* The base url prefixed to all calls made by `visit` and
* `remote`.
*/
baseUrl: string
/**
* The path of the current page. It should equal to the `location.pathname` +
* `location.search` + `location.hash`
*/
path: string
/**
* The app element that was passed to React's `createRoot`. This will be used
* to setup UJS helpers.
*/
appEl: HTMLElement
}

type ConnectedMapping = Record<
string,
ConnectedComponent<React.ComponentType, PageOwnProps>
>
/**
* The entry point to your superglue application. You should create a class
* (Application) that inherit from the ApplicationBase component and override
* the {@link buildStore}, {@link mapping}, and {@link visitAndRemote} methods.
*
* This would be setup for you when installing Superglue at `application.js`.
*/
export abstract class ApplicationBase extends React.Component<ApplicationProps> {
private hasWindow: boolean
private navigatorRef: React.RefObject<Nav>
private initialPageKey: string
private store: SuperglueStore
private history: History
private connectedMapping: ConnectedMapping
private ujsHandlers: Handlers
private visit: Visit
private remote: Remote

export abstract class ApplicationBase extends React.Component<Props> {
public hasWindow: boolean
public navigatorRef: React.RefObject<Nav>
public initialPageKey: string
public store: SuperglueStore
public history: History
public connectedMapping: ConnectedMapping
public ujsHandlers: Handlers
public visit: Visit
public remote: Remote

constructor(props: Props) {
/**
* The constructor of the `ApplicationBase` class.
* @param props
*/
constructor(props: ApplicationProps) {
super(props)
this.hasWindow = typeof window !== 'undefined'

Expand Down Expand Up @@ -174,6 +197,17 @@ export abstract class ApplicationBase extends React.Component<Props> {
this.remote = remote
}

/**
* Override this method to return a visit and remote function. These functions
* will be used by Superglue to power its UJS attributes and passed to your
* page components. You may customize this functionality to your liking, e.g,
* adding a progress bar.
*
* @param navigatorRef
* @param store
*
* @returns
*/
abstract visitAndRemote(
// eslint-disable-next-line
navigatorRef: React.RefObject<Nav>,
Expand Down Expand Up @@ -203,6 +237,14 @@ export abstract class ApplicationBase extends React.Component<Props> {
appEl.removeEventListener('submit', onSubmit)
}

/**
* Override this method and return a Redux store for Superglue to use. This
* would be setup and generated for you in `store.js`. We recommend using
* using Redux toolkit's `configureStore` to build the store.
*
* @param initialState - A preconfigured intial state to pass to your store.
* @param reducer - A preconfigured reducer
*/
abstract buildStore(
initialState: { pages: AllPages; [key: string]: JSONValue },
reducer: typeof rootReducer
Expand All @@ -218,6 +260,13 @@ export abstract class ApplicationBase extends React.Component<Props> {
}
}

/**
* Override this method and return a mapping between a componentIdentifier and
* a PageComponent. This will be passed to Superglue to determine which Page component
* to render with which payload.
*
* @returns
*/
abstract mapping(): Record<string, React.ComponentType>

render(): JSX.Element {
Expand Down
6 changes: 6 additions & 0 deletions superglue/lib/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { KeyPathError } from './utils/immutability'

const actionValues = Object.values(actions).map((action) => action.toString())

/**
* A middleware that will update all {@link Fragment} across the {@link
* AllPages} slice, if a fragment on any page was mutated.
*
* @experimental
*/
const fragmentMiddleware: Middleware<unknown, RootState, Dispatch> =
(store) => (next) => (action) => {
const prevState = store.getState()
Expand Down
3 changes: 3 additions & 0 deletions superglue/lib/types/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export interface HistoryChange extends Action {
}
}

/**
* Tuple of Fetch arguments that Superglue passes to Fetch.
*/
export type FetchArgs = [string, BasicRequestInit]

export interface BeforeVisit extends Action {
Expand Down
Loading

0 comments on commit 02f8c24

Please sign in to comment.