From 19cf7246e8213dd83aacb4620715bf93a99d47fc Mon Sep 17 00:00:00 2001 From: Gnarwhal Date: Tue, 17 Sep 2024 07:11:54 +0000 Subject: [PATCH] ANSI escapes working in some semblence --- src/app/[...file]/content.tsx | 66 ++++++++++++++----- src/app/[...file]/types/error.tsx | 3 - .../[...file]/types/error/content_type.tsx | 3 + src/app/[...file]/types/error/network.tsx | 3 + src/app/[...file]/types/terminal.css.ts | 13 ++++ src/app/[...file]/types/terminal.tsx | 66 +++++++++++++++++++ src/app/[...file]/types/text.css.ts | 23 ++++++- src/app/[...file]/types/text.tsx | 30 ++++++++- 8 files changed, 184 insertions(+), 23 deletions(-) delete mode 100644 src/app/[...file]/types/error.tsx create mode 100644 src/app/[...file]/types/error/content_type.tsx create mode 100644 src/app/[...file]/types/error/network.tsx create mode 100644 src/app/[...file]/types/terminal.css.ts create mode 100644 src/app/[...file]/types/terminal.tsx diff --git a/src/app/[...file]/content.tsx b/src/app/[...file]/content.tsx index 8d658dc..d70df99 100644 --- a/src/app/[...file]/content.tsx +++ b/src/app/[...file]/content.tsx @@ -2,29 +2,58 @@ import { useState, useEffect } from 'react' -import Error from './types/error'; -import Image from './types/image'; -import Text from './types/text'; +import NetworkError from './types/error/network'; +import ContentTypeError from './types/error/content_type'; +import Image from './types/image'; +import Terminal from './types/terminal'; +import Text from './types/text'; + +type ContentType = { + content_type: RegExp, + extension: RegExp, + emit: () => undefined | Processor, +}; +type Processor = { + process: (response: Response) => Promise, + postprocess: (data: T) => undefined, +}; + +function not_match(regex: RegExp | undefined, str: string) { + return !(regex ?? /(?:)/).test(str); +} + +function is_type(response: Response, type: ContentType) { + if (not_match(type.content_type, response.headers.get('Content-Type').split(';')[0])) { + return false; + } else if (not_match(type.path, window.location.pathname)) { + return false; + } + return true; +} export default function Content({ src }: { src: string}) { const [content, set_content] = useState(); - type ContentType = { - matcher: RegExp, - emit: () => undefined | Processor, - }; - type Processor = { - process: (response: Response) => Promise, - postprocess: (data: T) => undefined, - }; - const recognized_types: ContentType[] = [{ - matcher: /image\/\w+/, + content_type: /image\/\w+/, emit: () => { set_content(); }, }, { - matcher: /text\/\w+/, + content_type: /application\/octet-stream/, + path: /.*\.term/, + emit: () => { + return { + process: (response: Response) => { + return response.text(); + }, + postprocess: (data: string) => { + set_content(); + } + }; + } + }, { + content_type: /(text\/\w+)|(application\/octet-stream)/, emit: () => { return { process: (response: Response) => { @@ -42,8 +71,8 @@ export default function Content({ src }: { src: string}) { const result = fetch(src) .then(response => { const content_type = response.headers.get('Content-Type').split(';')[0]; - for (let type of recognized_types) { - if (type.matcher.test(content_type)) { + for (const type of recognized_types) { + if (type.content_type.test(content_type)) { const emitted = type.emit(); if (emitted != undefined) { result.then(emitted.postprocess); @@ -52,7 +81,10 @@ export default function Content({ src }: { src: string}) { return; } } - set_content(); + set_content(); + }) + .catch(err => { + set_content(); }); } }, []); diff --git a/src/app/[...file]/types/error.tsx b/src/app/[...file]/types/error.tsx deleted file mode 100644 index 2dc7c16..0000000 --- a/src/app/[...file]/types/error.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Error({ content_type }: { content_type: string }) { - return

{`Unrecognised MIME type: ${content_type}`}

-} diff --git a/src/app/[...file]/types/error/content_type.tsx b/src/app/[...file]/types/error/content_type.tsx new file mode 100644 index 0000000..84c9b29 --- /dev/null +++ b/src/app/[...file]/types/error/content_type.tsx @@ -0,0 +1,3 @@ +export default function ContentTypeError({ content_type }: { content_type: string }) { + return

{`Unrecognised MIME type: ${content_type}`}

+} diff --git a/src/app/[...file]/types/error/network.tsx b/src/app/[...file]/types/error/network.tsx new file mode 100644 index 0000000..f8efcb9 --- /dev/null +++ b/src/app/[...file]/types/error/network.tsx @@ -0,0 +1,3 @@ +export default function NetworkError({ err }: { err: string }) { + return

{`Error: ${err}`}

; +} diff --git a/src/app/[...file]/types/terminal.css.ts b/src/app/[...file]/types/terminal.css.ts new file mode 100644 index 0000000..b6b3e0f --- /dev/null +++ b/src/app/[...file]/types/terminal.css.ts @@ -0,0 +1,13 @@ +import { style } from '@vanilla-extract/css' + +import * as colors from '../../colors.css' + + +export const content = style({ + margin: 0, + fontFamily: 'monospace', + fontSize: '1.1em', + lineHeight: '1.4em', + whiteSpace: 'pre-wrap', + tabSize: 4, +}); diff --git a/src/app/[...file]/types/terminal.tsx b/src/app/[...file]/types/terminal.tsx new file mode 100644 index 0000000..d3ddaa8 --- /dev/null +++ b/src/app/[...file]/types/terminal.tsx @@ -0,0 +1,66 @@ +import * as style from './terminal.css'; + +const ansi_normal_colors = [ + "#000000", + "#bc2232", + "#2e9662", + "#a55d14", + "#2f5dab", + "#694Caa", + "#006080", + "#a5a2af", +]; + +const ansi_bright_colors = [ + "#44444f", + "#f22c40", + "#39bb7a", + "#d6781a", + "#407ee7", + "#8466ea", + "#0082ad", + "#FFFFFF", +]; + +function ansi(text: string) { + const segments = text.split(/(?=\033)/g); + const spans = []; + const style = { + color: undefined, + backgroundColor: undefined, + fontWeight: undefined, + } + for (const [index, segment] of segments.entries()) { + const ansi_segment = segment.match(/\033\[(\d+;)*(\d+)m/g)[0] + console.log(ansi_segment); + const escape_codes = [ + ... + ansi_segment.matchAll(/\d+/g) + ].map(element => element[0]); + for (const code of escape_codes) { + if (code == 0) { + style.color = undefined; + style.backgroundColor = undefined; + style.fontWeight = undefined; + } else if (code == 1) { + style.fontWeight = 'bold'; + } else if (30 <= code && code <= 37) { + style.color = ansi_normal_colors[code - 30]; + } else if (90 <= code && code <= 97) { + style.color = ansi_bright_colors[code - 90]; + } else if (40 <= code && code <= 47) { + style.backgroundColor = ansi_bright_colors[code - 40]; + } else if (100 <= code && code <= 107) { + style.backgroundColor = ansi_bright_colors[code - 100]; + } + } + spans.push({segment.substring(ansi_segment.length)}); + } + return spans; +} + +export default function Terminal({ text }: { text: string }) { + return ( +

{ansi(text)}

+ ); +} diff --git a/src/app/[...file]/types/text.css.ts b/src/app/[...file]/types/text.css.ts index fb8780b..3d9e1fa 100644 --- a/src/app/[...file]/types/text.css.ts +++ b/src/app/[...file]/types/text.css.ts @@ -1,6 +1,27 @@ import { style } from '@vanilla-extract/css' -export const text = style({ +import * as colors from '../../colors.css' + +export const group = style({ + display: 'flex', fontFamily: 'monospace', + fontSize: '1.1em', + lineHeight: '1.4em', whiteSpace: 'pre-wrap', + tabSize: 4, +}); + +export const line_numbers = style({ + margin: 0, + marginRight: '0.5em', + borderRight: `1px solid ${colors.background2}`, + paddingRight: '0.5em', + color: colors.foreground2, +}); + +export const content = style({ +}); + +export const text = style({ + margin: 0, }); diff --git a/src/app/[...file]/types/text.tsx b/src/app/[...file]/types/text.tsx index 8c2dbfc..2698ffa 100644 --- a/src/app/[...file]/types/text.tsx +++ b/src/app/[...file]/types/text.tsx @@ -1,7 +1,33 @@ -import * as style from './text.css' +import { useLayoutEffect, useRef } from 'react'; + +import * as style from './text.css'; export default function Text({ text }: { text: string }) { + const lines = text.split('\n'); + const refs = lines.map(() => useRef(null)); + + useLayoutEffect(() => { + for (let line of lines) { + + } + }, []); + return ( -

{text}

+
+
+ {lines.map((_, index) => { + return

{index}

; + })} +
+
+ {lines.map((line, index) => { + if (line != '') { + return

{line}

; + } else { + return

{'\n'}

; + } + })} +
+
); }