Files
spacedrive/interface/components/TextViewer/index.tsx
nikec 5603392087 [ENG-972] quick view improvements (#1233)
* quick preview improvements

* fix ts

* improvements

* fix merge

* non-indexed support

* Update index.tsx

* Update pnpm-lock.yaml

* update quick preview

* sidebar icon weight

* fix thumb

* ts

* fix focus

* remove usePortal

* quick preview store

* Update index.tsx

* Update index.tsx

* cleanup

* add tooltip to name

* hide nav buttons and match explorer nav buttons

---------

Co-authored-by: Jamie Pine <32987599+jamiepine@users.noreply.github.com>
2023-09-08 11:45:37 +00:00

103 lines
2.6 KiB
TypeScript

import clsx from 'clsx';
import { memo, useEffect, useRef, useState } from 'react';
import './prism.css';
export interface TextViewerProps {
src: string;
onLoad?: (event: HTMLElementEventMap['load']) => void;
onError?: (event: HTMLElementEventMap['error']) => void;
className?: string;
codeExtension?: string;
}
// prettier-ignore
type Worker = typeof import('./worker')
export const worker = new ComlinkWorker<Worker>(new URL('./worker', import.meta.url));
const NEW_LINE_EXP = /\n(?!$)/g;
export const TextViewer = memo(
({ src, onLoad, onError, className, codeExtension }: TextViewerProps) => {
const ref = useRef<HTMLPreElement>(null);
const [highlight, setHighlight] = useState<{
code: string;
length: number;
language: string;
}>();
const [textContent, setTextContent] = useState('');
useEffect(() => {
// Ignore empty urls
if (!src || src === '#') return;
const controller = new AbortController();
fetch(src, { mode: 'cors', signal: controller.signal })
.then(async (response) => {
if (!response.ok) throw new Error(`Invalid response: ${response.statusText}`);
const text = await response.text();
if (controller.signal.aborted) return;
onLoad?.(new UIEvent('load', {}));
setTextContent(text);
if (codeExtension) {
try {
const env = await worker.highlight(text, codeExtension);
if (env && !controller.signal.aborted) {
const match = text.match(NEW_LINE_EXP);
setHighlight({
...env,
length: (match ? match.length + 1 : 1) + 1
});
}
} catch (error) {
console.error(error);
}
}
})
.catch((error) => {
if (!controller.signal.aborted)
onError?.(new ErrorEvent('error', { message: `${error}` }));
});
return () => controller.abort();
}, [src, onError, onLoad, codeExtension]);
return (
<pre
ref={ref}
tabIndex={0}
className={clsx(
'text-ink',
className,
highlight && ['relative !pl-[3.8em]', `language-${highlight.language}`]
)}
>
{highlight ? (
<>
<span className="pointer-events-none absolute left-0 top-[1em] w-[3em] select-none text-[100%] tracking-[-1px] text-ink-dull">
{Array.from(highlight, (_, i) => (
<span
key={i}
className={clsx('token block text-end', i % 2 && 'bg-black/40')}
>
{i + 1}
</span>
))}
</span>
<code
style={{ whiteSpace: 'inherit' }}
className={clsx('relative', `language-${highlight.language}`)}
dangerouslySetInnerHTML={{ __html: highlight.code }}
/>
</>
) : (
textContent
)}
</pre>
);
}
);