Browse Source

fix: status checker, other misc

feat/enable-search
Thomas Osmonson 4 years ago
committed by Thomas Osmonson
parent
commit
fb52272594
  1. 3
      package.json
  2. 32
      public/static/fonts.css
  3. 30
      src/common/utils/index.ts
  4. 21
      src/components/custom-blocks/page-reference.tsx
  5. 151
      src/components/lazy-image.tsx
  6. 36
      src/components/mdx/image.tsx
  7. 67
      src/components/status-check.tsx
  8. 2
      src/components/toc.tsx
  9. 69
      src/pages/_document.tsx
  10. 9
      src/pages/api/status.js
  11. 26
      yarn.lock

3
package.json

@ -48,6 +48,8 @@
"prettier": "^2.0.5",
"preval.macro": "^5.0.0",
"react-gesture-responder": "^2.1.0",
"react-intersection-observer": "^8.26.2",
"react-spring": "^8.0.27",
"remark": "^12.0.1",
"remark-custom-blocks": "^2.5.0",
"remark-emoji": "2.1.0",
@ -73,6 +75,7 @@
"unist-util-select": "^3.0.1",
"unist-util-visit": "^2.0.3",
"use-events": "^1.4.2",
"use-is-in-viewport": "^1.0.9",
"yaml-loader": "^0.6.0"
},
"devDependencies": {

32
public/static/fonts.css

@ -0,0 +1,32 @@
@font-face {
font-family: 'Soehne Mono';
src: url('/static/fonts/soehne-mono-web-buch.woff2') format('woff2'),
url('/static/fonts/soehne-mono-web-buch.woff') format('woff');
font-weight: 400;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Soehne';
src: url('/static/fonts/soehne-web-buch.woff2') format('woff2'),
url('/static/fonts/soehne-web-buch.woff') format('woff');
font-weight: 400;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Soehne';
src: url('/static/fonts/soehne-web-kraftig_1.woff2') format('woff2'),
url('/static/fonts/soehne-web-kraftig_1.woff') format('woff');
font-weight: 500;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Soehne';
src: url('/static/fonts/soehne-web-halbfett_1.woff2') format('woff2'),
url('/static/fonts/soehne-web-halbfett_1.woff') format('woff');
font-weight: 600;
font-display: swap;
font-style: normal;
}

30
src/common/utils/index.ts

@ -92,3 +92,33 @@ export const getSlug = (asPath: string) => {
}
return;
};
interface CancelablePromise {
promise: Promise<any>;
cancel: () => void;
}
/** Make a Promise "cancelable".
*
* Rejects with {isCanceled: true} if canceled.
*
* The way this works is by wrapping it with internal hasCanceled_ state
* and checking it before resolving.
*/
export const makeCancelable = (promise: Promise<any>): CancelablePromise => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
void promise.then((val: any) => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)));
void promise.catch((error: any) =>
hasCanceled_ ? reject({ isCanceled: true }) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};

21
src/components/custom-blocks/page-reference.tsx

@ -100,12 +100,22 @@ const InlineCard = ({ page }) => {
position="relative"
{...bind}
>
<Box flexShrink={0} size="64px" overflow="hidden" borderRadius={'12px'}>
<Box
flexShrink={0}
position="relative"
size="64px"
overflow="hidden"
bg="#9985FF"
borderRadius={'12px'}
>
<Image
size="102%"
left={'-2%'}
top={'-2%'}
position="absolute"
transition={transition('0.45s')}
transform={(hover || active) && 'scale(1.18)'}
style={{ willChange: 'transform' }}
size="64px"
src={page?.images?.sm}
alt={`Graphic for: ${page.title || page.headings[0]}`}
/>
@ -171,16 +181,15 @@ const GridCardImage: React.FC<
>
<Grid style={{ placeItems: 'center' }} height="0px" paddingTop="56.25%">
<Image
width="102%"
size="102%"
left={'-2%'}
top={'-2%'}
position="absolute"
transition={transition('0.45s')}
transform={isHovered && 'scale(1.08)'}
style={{ willChange: 'transform' }}
src={src}
position="absolute"
alt={alt}
left={'-2%'}
top={'-2%'}
/>
</Grid>
</Box>

151
src/components/lazy-image.tsx

@ -0,0 +1,151 @@
import React from 'react';
import { Box, BoxProps, useSafeLayoutEffect } from '@blockstack/ui';
import { useSpring, animated, config } from 'react-spring';
import { useInView } from 'react-intersection-observer';
import { makeCancelable } from '@common/utils';
interface ImageProps {
/** The source of the image to load */
src: string;
/** The source set of the image to load */
srcSet?: string;
/** The alt text description of the image you are loading */
alt?: string;
/** Sizes descriptor */
sizes?: string;
}
const loadImage = (
{ src, srcSet, alt, sizes }: ImageProps,
experimentalDecode = false
): Promise<any> =>
// eslint-disable-next-line @typescript-eslint/no-misused-promises
new Promise((resolve, reject) => {
if (typeof Image !== 'undefined') {
const image = new Image();
if (srcSet) {
image.srcset = srcSet;
}
if (alt) {
image.alt = alt;
}
if (sizes) {
image.sizes = sizes;
}
image.src = src;
/** @see: https://www.chromestatus.com/feature/5637156160667648 */
if (experimentalDecode && 'decode' in image) {
return (
image
// NOTE: .decode() is not in the TS defs yet
// TODO: consider writing the .decode() definition and sending a PR
//@ts-ignore
.decode()
//@ts-ignore
.then((image: HTMLImageElement) => resolve(image))
.catch((err: any) => reject(err))
);
}
image.onload = resolve;
image.onerror = reject;
}
});
export const LazyImage: React.FC<
BoxProps & {
src?: string;
srcSet?: string;
loading?: string;
placeholder?: string;
}
> = ({ src, srcSet, style = {}, placeholder, ...props }) => {
const [ref, inView] = useInView({
triggerOnce: true,
rootMargin: '200px 0px',
});
const [loading, setLoading] = React.useState(false);
const [source, setSrc] = React.useState({
src: undefined,
srcSet: undefined,
});
const loadingPromise = makeCancelable(loadImage({ src, srcSet }, true));
const onLoad = React.useCallback(
() =>
requestAnimationFrame(() => {
console.log('on-load');
setSrc({ src, srcSet });
}),
[]
);
useSafeLayoutEffect(() => {
if (!source.src && !loading && inView) {
setLoading(true);
loadingPromise.promise
.then(_res => {
console.log('loaded');
onLoad();
})
.catch(e => {
// If the Loading Promise was canceled, it means we have stopped
// loading due to unmount, rather than an error.
if (!e.isCanceled) {
console.error('failed to load image');
}
});
}
}, [source, loading, inView]);
const styleProps = useSpring({ opacity: source.src ? 1 : 0, config: config.gentle });
const placeholderProps = useSpring({ opacity: source.src ? 0 : 1, config: config.gentle });
return (
<Box
as="span"
maxWidth="100%"
width={['100%', '100%', 'inherit', 'inherit']}
display="block"
position="absolute"
ref={ref}
{...props}
>
<Box as="span" top={0} left={0} width="100%" position="absolute">
<Box
as={animated.img}
width="100%"
style={{
filter: 'blur(5px)',
...placeholderProps,
}}
//@ts-ignore
src={placeholder}
/>
</Box>
{source.src ? (
<Box
maxWidth="100%"
width={['100%', '100%', 'inherit', 'inherit']}
display="block"
as={animated.img}
zIndex={99}
style={{
opacity: 0,
willChange: 'opacity',
...style,
...styleProps,
}}
{...source}
{...props}
/>
) : null}
</Box>
);
};

36
src/components/mdx/image.tsx

@ -34,9 +34,12 @@ const useImgix = (src: string) => {
${_src}&w=480&fit=max&q=20&dpr=3 3x`;
const base = `${_src}&w=720&dpr=1&fit=max`;
const placeholder = `${_src}&w=40&dpr=1&fit=max`;
return {
srcset,
src: base,
placeholder,
};
};
@ -48,21 +51,33 @@ const getAspectRatio = dimensions => {
return (height / width) * 100;
};
const BaseImg: React.FC<any> = props => (
<Box
loading="lazy"
maxWidth="100%"
width={['100%', '100%', 'inherit', 'inherit']}
display="block"
as="img"
{...props}
/>
);
const BaseImg: React.FC<
BoxProps & {
src?: string;
srcSet?: string;
loading?: string;
}
> = ({ style = {}, ...props }) => {
return (
<Box
maxWidth="100%"
width={['100%', '100%', 'inherit', 'inherit']}
display="block"
loading="lazy"
as="img"
style={{
...style,
}}
{...props}
/>
);
};
export const Img: React.FC<
BoxProps & { loading?: string; src?: string; alt?: string; dimensions?: any }
> = React.memo(({ src: _src, dimensions, ...rest }) => {
const { src, srcset } = useImgix(_src);
const props = {
src,
srcSet: srcset,
@ -73,7 +88,6 @@ export const Img: React.FC<
// means the image is local and we can generate the aspect ratio
// and prevent the page from jumping due to lack of an image being loaded
// (because of the built in lazy-loading)
const aspectRatio = getAspectRatio(dimensions);
const width = dimensions.width <= 720 ? dimensions.width : '100%';

67
src/components/status-check.tsx

@ -1,7 +1,7 @@
import React from 'react';
import useSWR from 'swr';
import { Box, Flex, space, color, BoxProps } from '@blockstack/ui';
import { border } from '@common/utils';
import { border, transition } from '@common/utils';
import { Link } from '@components/mdx';
import { LinkProps, Text } from '@components/typography';
import { Spinner } from '@blockstack/ui';
@ -13,29 +13,46 @@ import { getCapsizeStyles } from '@components/mdx/typography';
const fetcher = url => fetch(url).then(r => r.json());
const StatusWords: React.FC<BoxProps & { status?: boolean }> = ({ status, ...rest }) => (
type Status = 'online' | 'slow' | 'degraded' | 'loading' | undefined;
const getColor = (status: Status) => {
switch (status) {
case 'degraded':
return 'feedback-error';
case 'online':
return 'feedback-success';
case 'slow':
return 'feedback-alert';
}
};
const StatusWords: React.FC<BoxProps & { status?: Status }> = ({ status, ...rest }) => (
<>
<Box as="span">:</Box>
<Box as="span" color={status ? color('feedback-success') : color('feedback-alert')}>{`${
status ? ' online' : ' offline'
}`}</Box>
<Box as="span" color={color(getColor(status))}>{` ${status}`}</Box>
</>
);
const getStatus = (data: number): Status | 'loading' => {
switch (data) {
case 0:
return 'online';
case 1:
return 'slow';
case 2:
return 'degraded';
default:
return 'loading';
}
};
export const StatusCheck: React.FC<LinkProps> = props => {
const { data, error } = useSWR(`${STATUS_CHECKER_URL}/json`, fetcher);
const [status, setStatus] = React.useState(undefined);
const { data, error } = useSWR(`/api/status`, fetcher);
React.useEffect(() => {
if (data?.masterNodePings?.length > 1) {
setStatus(data.masterNodePings[0].value);
} else if (status) {
setStatus(undefined);
}
}, [data, error]);
const status = getStatus(data);
const critical = error && !status;
const positive = data && status && !error;
const critical = error || status === 'degraded';
const warn = status === 'slow';
return (
<Link
@ -48,16 +65,26 @@ export const StatusCheck: React.FC<LinkProps> = props => {
py={space('tight')}
color={color('text-caption')}
_hover={{ cursor: 'pointer', bg: color('bg-alt') }}
opacity={data || error ? 1 : 0}
transition={transition()}
{...props}
>
<Flex align="center">
<Box mr={space('tight')}>
{!data && !error ? (
<Spinner color={color('accent')} speed="1s" thickness="2px" size="sm" />
) : critical ? (
<AlertCircleIcon color={color('feedback-alert')} size="20px" />
<Box
style={{
display: 'grid',
placeItems: 'center',
}}
size="24px"
>
<Spinner color={color('accent')} speed="1s" thickness="2px" size="sm" />
</Box>
) : critical || warn ? (
<AlertCircleIcon color={color(getColor(status))} size="24px" />
) : (
positive && <CircleCheck size="20px" color={color('feedback-success')} />
<CircleCheck size="24px" color={color('feedback-success')} />
)}
</Box>
<Text

2
src/components/toc.tsx

@ -36,7 +36,7 @@ const Item = ({
const isActive = isOnScreen || _isActive;
const adjustedLevel = level - 2;
const shouldRender = limit ? adjustedLevel > 0 && adjustedLevel <= 2 : true;
const shouldRender = limit ? adjustedLevel > 0 && adjustedLevel <= 1 : true;
return shouldRender ? (
<Box pl={getLevelPadding(level - 2)} py={space('extra-tight')}>
<NextLink href={`#${slug}`} passHref>

69
src/pages/_document.tsx

@ -40,45 +40,36 @@ export default class MyDocument extends Document<DocumentProps> {
<Html lang="en">
<Head />
<body>
<style
type="text/css"
dangerouslySetInnerHTML={{
__html: `
@font-face {
font-family: 'Soehne Mono';
src: url('/static/fonts/soehne-mono-web-buch.woff2') format('woff2'),
url('/static/fonts/soehne-mono-web-buch.woff') format('woff');
font-weight: 400;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Soehne';
src: url('/static/fonts/soehne-web-buch.woff2') format('woff2'),
url('/static/fonts/soehne-web-buch.woff') format('woff');
font-weight: 400;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Soehne';
src: url('/static/fonts/soehne-web-kraftig_1.woff2') format('woff2'),
url('/static/fonts/soehne-web-kraftig_1.woff') format('woff');
font-weight: 500;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Soehne';
src: url('/static/fonts/soehne-web-halbfett_1.woff2') format('woff2'),
url('/static/fonts/soehne-web-halbfett_1.woff') format('woff');
font-weight: 600;
font-display: swap;
font-style: normal;
}
`,
}}
<link
rel="preload"
href="/static/fonts/soehne-mono-web-buch.woff2"
as="font"
type="font/woff2"
crossOrigin="true"
/>
<link
rel="preload"
href="/static/fonts/soehne-web-buch.woff2"
as="font"
type="font/woff2"
crossOrigin="true"
/>
<link
rel="preload"
href="/static/fonts/soehne-web-kraftig_1.woff2"
as="font"
type="font/woff2"
crossOrigin="true"
/>
<link
rel="preload"
href="/static/fonts/soehne-web-halbfett_1.woff2"
as="font"
type="font/woff2"
crossOrigin="true"
/>
<link rel="preload" href="/static/fonts.css" as="style" />
<script
dangerouslySetInnerHTML={{
__html: `(function() {
@ -93,7 +84,7 @@ export default class MyDocument extends Document<DocumentProps> {
})()`,
}}
/>
<link rel="preconnect" href="https://bh4d9od16a-dsn.algolia.net" crossOrigin="true" />
{/*<link rel="preconnect" href="https://bh4d9od16a-dsn.algolia.net" crossOrigin="true" />*/}
<link rel="preconnect" href="https://cdn.usefathom.com" crossOrigin="true" />
<Main />
<NextScript />

9
src/pages/api/status.js

@ -0,0 +1,9 @@
export default async (req, res) => {
const response = await fetch('http://status.test-blockstack.com/json');
const json = await response.json();
if (json) {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(json.blockRateStatus));
}
};

26
yarn.lock

@ -6345,6 +6345,11 @@ object.values@^1.1.1:
function-bind "^1.1.1"
has "^1.0.3"
observe-element-in-viewport@0.0.15:
version "0.0.15"
resolved "https://registry.yarnpkg.com/observe-element-in-viewport/-/observe-element-in-viewport-0.0.15.tgz#06284e84da80bd43c348ac3d957ba6b99fb2bbd0"
integrity sha512-BaJGtCHp8XXxe8zFOnsbv93f4SRLb0W5P3okGJkLLKC9IM9aXvpKF4C8lFxivcS5eROlNkhOCnUOSk4tUnJnKQ==
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@ -7050,6 +7055,13 @@ react-gesture-responder@^2.1.0:
"@types/react-dom" "^16.8.3"
tslib "^1.9.3"
react-intersection-observer@^8.26.2:
version "8.26.2"
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.26.2.tgz#0562ff0c06b2b10e809190c2fa9b6ded656e6e16"
integrity sha512-GmSjLNK+oV7kS+BHfrJSaA4wF61ELA33gizKHmN+tk59UT6/aW8kkqvlrFGPwxGoaIzLKS2evfG5fgkw5MIIsg==
dependencies:
tiny-invariant "^1.1.0"
react-is@16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -7065,7 +7077,7 @@ react-refresh@0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
react-spring@8.0.27:
react-spring@8.0.27, react-spring@^8.0.27:
version "8.0.27"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.27.tgz#97d4dee677f41e0b2adcb696f3839680a3aa356a"
integrity sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==
@ -8376,6 +8388,11 @@ tiny-emitter@^2.0.0:
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tiny-invariant@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
@ -8921,6 +8938,13 @@ use-events@^1.4.1, use-events@^1.4.2:
dependencies:
resize-observer-polyfill "1.5.1"
use-is-in-viewport@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/use-is-in-viewport/-/use-is-in-viewport-1.0.9.tgz#bbc1f2f39ece752c7af0ddcd17b251ba5b4587e2"
integrity sha512-Dgi0z/X9eTk3ziI+b28mZVoYtCtyoUFQ+9VBq6fR5EdjqmmsSlbr8ysXAwmEl89OUNBQwVGLGdI9nqwiu3168g==
dependencies:
observe-element-in-viewport "0.0.15"
use-isomorphic-layout-effect@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.0.0.tgz#f56b4ed633e1c21cd9fc76fe249002a1c28989fb"

Loading…
Cancel
Save