UNPKG

@furystack/shades-common-components

Version:

Common UI components for FuryStack Shades

72 lines 3.28 kB
import { hasCacheValue, isFailedCacheResult, isObsoleteCacheResult } from '@furystack/cache'; import { Shade, createComponent, transitionedValue } from '@furystack/shades'; import { cssVariableTheme } from '../services/css-variable-theme.js'; import { Button } from './button.js'; import { Result } from './result.js'; const getDefaultErrorUi = (error, retry) => (createComponent(Result, { status: "error", title: "Something went wrong", subtitle: String(error) }, createComponent(Button, { variant: "outlined", onclick: retry }, "Retry"))); /** * Renders the state of a cache entry for the given `cache` + `args`. * * Subscribes to the cache observable and dispatches in this order: * 1. **Error** — failed result renders the error UI with a retry callback. * 2. **Value** — loaded or obsolete result renders `content`. Obsolete also * triggers a single `cache.reload(...args)` per obsolete cycle. * 3. **Loading** — no value, no error: renders `loader` (default `null`). * * @example * ```tsx * const MyContent = Shade<{ data: CacheWithValue<User> }>({ * customElementName: 'my-content', * render: ({ props }) => <div>{props.data.value.name}</div>, * }) * * <CacheView cache={userCache} args={[userId]} content={MyContent} /> * ``` */ export const CacheView = Shade({ customElementName: 'shade-cache-view', css: { fontFamily: cssVariableTheme.typography.fontFamily, }, render: ({ props, useObservable, useState }) => { const { cache, args, content, loader, error, contentProps, viewTransition } = props; const argsKey = JSON.stringify(args); const observable = cache.getObservable(...args); const [result] = useObservable(`cache-${argsKey}`, observable); const getCategory = (r) => isFailedCacheResult(r) ? 'error' : hasCacheValue(r) ? 'value' : 'loading'; const displayedResult = transitionedValue(useState, 'displayedResult', result, viewTransition, (prev, next) => getCategory(prev) !== getCategory(next)); const [lastReloadedArgsKey, setLastReloadedArgsKey] = useState('lastReloadedArgsKey', null); const retry = () => { cache.reload(...args).catch(() => { /* error state will be set by cache */ }); }; // 1. Error first if (isFailedCacheResult(displayedResult)) { const errorRenderer = error ?? getDefaultErrorUi; return errorRenderer(displayedResult.error, retry); } // 2. Value next if (hasCacheValue(displayedResult)) { if (isObsoleteCacheResult(displayedResult)) { if (lastReloadedArgsKey !== argsKey) { setLastReloadedArgsKey(argsKey); cache.reload(...args).catch(() => { /* error state will be set by cache */ }); } } else if (lastReloadedArgsKey !== null) { setLastReloadedArgsKey(null); } return createComponent(content, { data: displayedResult, ...(contentProps ?? {}), }); } // 3. Loading last return loader ?? null; }, }); //# sourceMappingURL=cache-view.js.map