Files
spacedrive/interface/components/TextViewer/index.tsx
Aditya 08ba4f917a Extending QuickPreview functionality with additional filetype support (#1231)
* added some files `standard` mime type

* Used `TEXTViewer` Component to show Code Preview

* Update Thumb.tsx

* added `prismjs`

* removed unnecessary comment

* `CODEViewer` Component for Syntax Highlighting

* formatting

* using **Atom** Theme for `Prism`

* merge text/code viewers & bg-app-focus for prism

currently calling onError and onLoad without an Event argument
that should change but i'm not really sure what to do there

* removed unused imports

* Update index.ts

* `TEXTViewer` to `TextViewer_`

* `TextViewer_` to `TextViewer`

* Don't highlight normal TextFiles

* clean code

* `TEXTViewer` to `TextViewer`

* using tailwind classes more

* doing things correctly.

* installed `prismjs` in interface

* using own scroller

* Update Thumb.tsx

* Add an AbortController to the fetch request
 - Fix onError and onLoad calls
 - Format code

* Fix onError being called when request was aborted due to re-render
 - Fix Compoenent re-rendering loop due to circular reference in useEffect
 - Remove unused imports

* Improve text file serving and code syntax highlight
 - Implement way to identify text files in file-ext crate
 - Do not depend only on the file extension to identify text files in custom_uri
 - Import more prismjs language rules files
 - Add line numbers to TextViewer when rendering code

* Clippy and prettier

* Fix reading zero byte data to Vec
 - Improve empty file handling

* Expand code highlight to more file types
 - Fix 10MB when it should be 10KB
 - Add supported for more code and config files extensions to sd-file-ext
 - Add comlink and vite-plugin-comlink for easy js worker integration
 - Move Prismjs logic to a Worker, because larger files (1000+ lines) where causing the UI to hang
 - Replace line-number prismjs plugin with our own implementation

* Fix uppercase extension name

---------

Co-authored-by: Utku <74243531+utkubakir@users.noreply.github.com>
Co-authored-by: pr <pineapplerind.info@gmail.com>
Co-authored-by: Vítor Vasconcellos <vasconcellos.dev@gmail.com>
2023-08-29 10:47:04 +00:00

102 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(
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>
);
}
);