Compare commits
4 commits
8ec4c95984
...
6d17d0b954
Author | SHA1 | Date | |
---|---|---|---|
6d17d0b954 | |||
9b3f590bec | |||
3eb11d3aeb | |||
bf6709e2fd |
11 changed files with 145 additions and 31 deletions
|
@ -6,8 +6,12 @@ Motto presents files from a static file server in a more friendly way
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
- `ROOT_URL=(url)` - the static file server instance to get files from
|
Configuration is done through environment variables in `.env.local`
|
||||||
- `ENABLE_REPOINTING=(true|default:false)` - enable [repointing](#Repointing)
|
|
||||||
|
| Variable | type | default | description |
|
||||||
|
|--------------------|---------|---------|---------------------------------------------------|
|
||||||
|
| `ROOT_URL` | string | N/A | the static file server instance to get files from |
|
||||||
|
|`ENABLE_REPOINTING` | boolean | `false` | enable [repointing](#Repointing) |
|
||||||
|
|
||||||
### Repointing
|
### Repointing
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
|
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
import NetworkError from './types/error/network';
|
import Loading from './loading';
|
||||||
import ContentTypeError from './types/error/content_type';
|
import ContentTypeError from './types/error/content_type';
|
||||||
|
import NetworkError from './types/error/network';
|
||||||
|
import StatusError from './types/error/status';
|
||||||
import ImageContent from './types/image';
|
import ImageContent from './types/image';
|
||||||
import MarkdownContent from './types/markdown';
|
import MarkdownContent from './types/markdown';
|
||||||
import Terminal from './types/terminal';
|
import Terminal from './types/terminal';
|
||||||
|
@ -86,17 +88,21 @@ export default function Content({ src }: { src: string}) {
|
||||||
if (content == undefined) {
|
if (content == undefined) {
|
||||||
const result = fetch(src)
|
const result = fetch(src)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
for (const type of recognized_types) {
|
if (response.status < 400) {
|
||||||
if (is_type(response, type)) {
|
for (const type of recognized_types) {
|
||||||
const emitted = type.emit();
|
if (is_type(response, type)) {
|
||||||
if (emitted != undefined) {
|
const emitted = type.emit();
|
||||||
result.then(emitted.postprocess);
|
if (emitted != undefined) {
|
||||||
return emitted.process(response);
|
result.then(emitted.postprocess);
|
||||||
|
return emitted.process(response);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
set_content(<ContentTypeError content_type={response.headers.get('Content-Type')!.split(';')[0]} />);
|
||||||
|
} else {
|
||||||
|
set_content(<StatusError status={response.status} />);
|
||||||
}
|
}
|
||||||
set_content(<ContentTypeError content_type={response.headers.get('Content-Type')!.split(';')[0]} />);
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
set_content(<NetworkError err={err} />);
|
set_content(<NetworkError err={err} />);
|
||||||
|
@ -104,5 +110,5 @@ export default function Content({ src }: { src: string}) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return content ?? <p>Loading...</p>;
|
return content ?? <Loading />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,20 +10,20 @@ export const copy = style({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const copy_button = style({
|
export const copy_button = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
margin: 0,
|
margin: 0,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
padding: 0,
|
padding: 0,
|
||||||
outline: 'inherit',
|
outline: 'inherit',
|
||||||
fontFamily: 'monospace',
|
fontFamily: 'monospace',
|
||||||
fontSize: '1.1em',
|
fontSize: '1.1em',
|
||||||
lineHeight: '2em',
|
lineHeight: '2em',
|
||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
transition: 'background-color 0.35s',
|
transition: 'background-color 0.35s',
|
||||||
background: 'none',
|
background: 'none',
|
||||||
width: 'max-content',
|
width: 'max-content',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
':hover': {
|
':hover': {
|
||||||
backgroundColor: colors.background2,
|
backgroundColor: colors.background2,
|
||||||
},
|
},
|
||||||
|
@ -79,7 +79,9 @@ export const copied_image_light = style([copy_image, {
|
||||||
|
|
||||||
|
|
||||||
export const copy_text = style({
|
export const copy_text = style({
|
||||||
margin: 0,
|
margin: 0,
|
||||||
marginLeft: '1em',
|
marginLeft: '1em',
|
||||||
width: 'max-content',
|
width: 'max-content',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: '1.2em',
|
||||||
});
|
});
|
||||||
|
|
35
src/app/[...file]/components/loading.css.ts
Normal file
35
src/app/[...file]/components/loading.css.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { keyframes, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
import * as colors from '../../colors.css';
|
||||||
|
|
||||||
|
export const loading_squares = style({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: '100%',
|
||||||
|
marginTop: '4em',
|
||||||
|
height: 'max-content',
|
||||||
|
});
|
||||||
|
|
||||||
|
const animation = keyframes({
|
||||||
|
'0%': { backgroundColor: colors.background2 },
|
||||||
|
'50%': { backgroundColor: colors.foreground },
|
||||||
|
'100%': { backgroundColor: colors.background2 },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const square = style({
|
||||||
|
margin: '0 1em',
|
||||||
|
width: '1em',
|
||||||
|
height: '1em',
|
||||||
|
backgroundColor: colors.background2,
|
||||||
|
animationName: animation,
|
||||||
|
animationDuration: '2s',
|
||||||
|
animationIterationCount: 'infinite',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const loading = [0, 1, 2, 3, 4].map(index =>
|
||||||
|
style([square, {
|
||||||
|
animationDelay: `${0.2 * index}s`
|
||||||
|
}])
|
||||||
|
);
|
||||||
|
|
||||||
|
|
9
src/app/[...file]/components/loading.tsx
Normal file
9
src/app/[...file]/components/loading.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import * as style from './loading.css';
|
||||||
|
|
||||||
|
export default function Loading() {
|
||||||
|
return (
|
||||||
|
<div className={style.loading_squares}>
|
||||||
|
{style.loading.map((style, index) => <div key={index} className={style}></div>)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import Error from './error'
|
||||||
|
|
||||||
export default function ContentTypeError({ content_type }: { content_type: string }) {
|
export default function ContentTypeError({ content_type }: { content_type: string }) {
|
||||||
return <p>{`Unrecognised MIME type: ${content_type}`}</p>
|
return <Error>{`Unrecognised MIME type: ${content_type}`}</Error>
|
||||||
}
|
}
|
||||||
|
|
18
src/app/[...file]/components/types/error/error.css.ts
Normal file
18
src/app/[...file]/components/types/error/error.css.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { style } from '@vanilla-extract/css'
|
||||||
|
|
||||||
|
export const error = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
height: 'max-content',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const message = style({
|
||||||
|
fontSize: '2em',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sad = style({
|
||||||
|
margin: '0em',
|
||||||
|
fontSize: '3em',
|
||||||
|
})
|
13
src/app/[...file]/components/types/error/error.tsx
Normal file
13
src/app/[...file]/components/types/error/error.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import * as style from './error.css'
|
||||||
|
|
||||||
|
const faces = [
|
||||||
|
"(•́︵•̀)",
|
||||||
|
"(╥﹏╥)",
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function Error({ children }: { children: React.ReactNode }) {
|
||||||
|
return <div className={style.error}>
|
||||||
|
<p className={style.message}>{children}</p>
|
||||||
|
<p className={style.sad}>{faces[Math.floor(Math.random() * faces.length)]}</p>
|
||||||
|
</div>
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
import Error from './error'
|
||||||
|
|
||||||
export default function NetworkError({ err }: { err: string }) {
|
export default function NetworkError({ err }: { err: string }) {
|
||||||
return <p>{`Error: ${err}`}</p>;
|
console.error(err);
|
||||||
|
return <Error>Network Error</Error>;
|
||||||
}
|
}
|
||||||
|
|
23
src/app/[...file]/components/types/error/status.tsx
Normal file
23
src/app/[...file]/components/types/error/status.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import Error from './error'
|
||||||
|
|
||||||
|
const statuses = {
|
||||||
|
[400]: "Bad Request",
|
||||||
|
[401]: "Unauthorized",
|
||||||
|
[402]: "Payment Required",
|
||||||
|
[403]: "Forbidden",
|
||||||
|
[404]: "Not Found",
|
||||||
|
[405]: "Method Not Allowed",
|
||||||
|
[500]: "Internal Server Error",
|
||||||
|
[501]: "Not Implemented",
|
||||||
|
[502]: "Bad Gateway",
|
||||||
|
[503]: "Service Unavailable",
|
||||||
|
[504]: "Gateway Timeout",
|
||||||
|
[505]: "HTTP Version Not Supported"
|
||||||
|
}
|
||||||
|
export default function StatusError({ status }: { status: number }) {
|
||||||
|
if (status in statuses) {
|
||||||
|
return <Error>{`${status} ${statuses[status as keyof typeof statuses]}`}</Error>
|
||||||
|
} else {
|
||||||
|
return <Error>{status}</Error>
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ import * as style from './page.css';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const supports_repointing = process.env.ENABLE_REPOINTING == 'true';
|
const supports_repointing = process.env.ENABLE_REPOINTING == 'true';
|
||||||
console.log(supports_repointing);
|
|
||||||
return (
|
return (
|
||||||
<div className={style.center}>
|
<div className={style.center}>
|
||||||
<div className={style.content}>
|
<div className={style.content}>
|
||||||
|
|
Loading…
Reference in a new issue