Compare commits
2 commits
dfcb95bd0d
...
bf5686f462
Author | SHA1 | Date | |
---|---|---|---|
bf5686f462 | |||
9fecf5bbfa |
11 changed files with 91 additions and 45 deletions
|
@ -1,3 +1,6 @@
|
|||
{
|
||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||
"extends": ["next/core-web-vitals", "next/typescript"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off"
|
||||
}
|
||||
}
|
||||
|
|
55
Dockerfile
Normal file
55
Dockerfile
Normal file
|
@ -0,0 +1,55 @@
|
|||
FROM chimeralinux/chimera:latest AS base
|
||||
RUN apk add libgcc-chimera yarn
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* ./
|
||||
RUN yarn --frozen-lockfile;
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN yarn run build;
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||
# ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN apk add shadow
|
||||
RUN groupadd --system --gid 1001 nodejs
|
||||
RUN useradd --system --uid 1001 nextjs
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nextjs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nextjs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 4730
|
||||
|
||||
ENV PORT=4730
|
||||
|
||||
# server.js is created by next build from the standalone output
|
||||
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
CMD ["node", "server.js"]
|
|
@ -1,7 +1,9 @@
|
|||
import { createVanillaExtractPlugin } from '@vanilla-extract/next-plugin'
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {}
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
}
|
||||
|
||||
const withVanillaExtract = createVanillaExtractPlugin()
|
||||
|
||||
|
|
|
@ -4,14 +4,14 @@ import { useState, useEffect } from 'react'
|
|||
|
||||
import NetworkError from './types/error/network';
|
||||
import ContentTypeError from './types/error/content_type';
|
||||
import Image from './types/image';
|
||||
import ImageContent from './types/image';
|
||||
import Terminal from './types/terminal';
|
||||
import Text from './types/text';
|
||||
|
||||
type ContentType<T> = {
|
||||
content_type: RegExp,
|
||||
extension: RegExp,
|
||||
emit: () => undefined | Processor<T>,
|
||||
path?: RegExp,
|
||||
emit: () => void | Processor<T>,
|
||||
};
|
||||
type Processor<T> = {
|
||||
process: (response: Response) => Promise<T>,
|
||||
|
@ -22,8 +22,8 @@ function not_match(regex: RegExp | undefined, str: string) {
|
|||
return !(regex ?? /(?:)/).test(str);
|
||||
}
|
||||
|
||||
function is_type(response: Response, type: ContentType<Any>) {
|
||||
if (not_match(type.content_type, response.headers.get('Content-Type').split(';')[0])) {
|
||||
function is_type(response: Response, type: ContentType<any>) {
|
||||
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;
|
||||
|
@ -32,12 +32,12 @@ function is_type(response: Response, type: ContentType<Any>) {
|
|||
}
|
||||
|
||||
export default function Content({ src }: { src: string}) {
|
||||
const [content, set_content] = useState();
|
||||
const [content, set_content] = useState<JSX.Element>();
|
||||
|
||||
const recognized_types: ContentType<Any>[] = [{
|
||||
const recognized_types: ContentType<any>[] = [{
|
||||
content_type: /image\/\w+/,
|
||||
emit: () => {
|
||||
set_content(<Image src={src} />);
|
||||
set_content(<ImageContent src={src} />);
|
||||
},
|
||||
}, {
|
||||
content_type: /application\/octet-stream/,
|
||||
|
@ -70,9 +70,8 @@ export default function Content({ src }: { src: string}) {
|
|||
if (content == undefined) {
|
||||
const result = fetch(src)
|
||||
.then(response => {
|
||||
const content_type = response.headers.get('Content-Type').split(';')[0];
|
||||
for (const type of recognized_types) {
|
||||
if (type.content_type.test(content_type)) {
|
||||
if (is_type(response, type)) {
|
||||
const emitted = type.emit();
|
||||
if (emitted != undefined) {
|
||||
result.then(emitted.postprocess);
|
||||
|
@ -81,13 +80,13 @@ export default function Content({ src }: { src: string}) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
set_content(<ContentTypeError content_type={content_type} />);
|
||||
set_content(<ContentTypeError content_type={response.headers.get('Content-Type')!.split(';')[0]} />);
|
||||
})
|
||||
.catch(err => {
|
||||
set_content(<NetworkError err={err} />);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
});
|
||||
|
||||
return content ?? <p>Loading...</p>;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
'use client'
|
||||
|
||||
import { useState } from 'react';
|
||||
// import { useState } from 'react';
|
||||
|
||||
import * as style from './download_tty.css';
|
||||
|
||||
export default function DownloadTTY({ text }: { text: string }) {
|
||||
const [copied, set_copied] = useState(false);
|
||||
function make_copy_text(text) {
|
||||
return (event) => {
|
||||
// const [copied, set_copied] = useState(false);
|
||||
function make_copy_text(text: string) {
|
||||
return () => {
|
||||
navigator.clipboard.writeText(text);
|
||||
set_copied(true);
|
||||
// set_copied(true);
|
||||
// setTimeout(() => { set_copied(false); }, 5000);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ export const download_button = style({
|
|||
width: 'auto',
|
||||
height: '100%',
|
||||
marginLeft: '2em',
|
||||
border: 0,
|
||||
padding: 0,
|
||||
border: `1px solid ${colors.background2}`,
|
||||
backgroundColor: colors.background,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Metadata, ResolvingMetadata } from 'next';
|
||||
import type { Metadata } from 'next';
|
||||
import Image from 'next/image';
|
||||
|
||||
import DownloadTTY from './download_tty';
|
||||
|
@ -10,7 +10,7 @@ import download_image_light from './download_light.svg';
|
|||
type SearchParams = { [key: string]: string | string[] | undefined };
|
||||
|
||||
type Props = {
|
||||
params: { file: string },
|
||||
params: { file: string[] },
|
||||
searchParams: SearchParams,
|
||||
};
|
||||
|
||||
|
@ -24,7 +24,6 @@ function get_root(search_params: SearchParams) {
|
|||
|
||||
export async function generateMetadata(
|
||||
{ params, searchParams }: Props,
|
||||
parent: ResolvingMetadata,
|
||||
): Promise<Metadata> {
|
||||
return {
|
||||
title: `${get_path(params.file)} | ${get_root(searchParams)}`,
|
||||
|
@ -52,7 +51,7 @@ export default async function Page({
|
|||
<p className={style.title}>{path}</p>
|
||||
</div>
|
||||
<button className={style.download_button}>
|
||||
<a className={style.download_link} href={full} download>
|
||||
<a href={full} download>
|
||||
<Image
|
||||
className={style.download_button_image_dark}
|
||||
src={download_image_dark}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import { style } from '@vanilla-extract/css'
|
||||
|
||||
import * as colors from '../../colors.css'
|
||||
|
||||
|
||||
export const content = style({
|
||||
margin: 0,
|
||||
fontFamily: 'monospace',
|
||||
|
|
|
@ -23,19 +23,19 @@ const ansi_bright_colors = [
|
|||
];
|
||||
|
||||
function ansi(text: string) {
|
||||
const segments = text.split(/(?=\033)/g);
|
||||
const segments = text.split(/(?=\x1B)/g);
|
||||
const spans = [];
|
||||
const style = {
|
||||
color: undefined,
|
||||
backgroundColor: undefined,
|
||||
fontWeight: undefined,
|
||||
}
|
||||
const style: {
|
||||
color?: string,
|
||||
backgroundColor?: string,
|
||||
fontWeight?: string,
|
||||
} = {};
|
||||
for (const [index, segment] of segments.entries()) {
|
||||
const ansi_segment = segment.substring(0, segment.indexOf('m') + 1);
|
||||
const escape_codes = [
|
||||
...
|
||||
ansi_segment.matchAll(/\d+/g)
|
||||
].map(element => element[0]);
|
||||
].map(element => Number(element[0]));
|
||||
if (escape_codes.length == 0) {
|
||||
style.color = undefined;
|
||||
style.backgroundColor = undefined;
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
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 (
|
||||
<div className={style.group}>
|
||||
|
@ -22,9 +13,9 @@ export default function Text({ text }: { text: string }) {
|
|||
<div className={style.content}>
|
||||
{lines.map((line, index) => {
|
||||
if (line != '') {
|
||||
return <p ref={refs[index]} key={index} className={style.text}>{line}</p>;
|
||||
return <p key={index} className={style.text}>{line}</p>;
|
||||
} else {
|
||||
return <p ref={refs[index]} key={index} className={style.text}>{'\n'}</p>;
|
||||
return <p key={index} className={style.text}>{'\n'}</p>;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"target": "es6",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
|
|
Loading…
Reference in a new issue