code-editor-component.tsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { Editor, EditorProps } from "@monaco-editor/react";
  2. import React from "react";
  3. import { useTranslation } from "react-i18next";
  4. import { VscCode } from "react-icons/vsc";
  5. import { I18nKey } from "#/i18n/declaration";
  6. import { useFiles } from "#/context/files";
  7. import { useSaveFile } from "#/hooks/mutation/use-save-file";
  8. interface CodeEditorComponentProps {
  9. onMount: EditorProps["onMount"];
  10. isReadOnly: boolean;
  11. }
  12. function CodeEditorComponent({
  13. onMount,
  14. isReadOnly,
  15. }: CodeEditorComponentProps) {
  16. const { t } = useTranslation();
  17. const {
  18. files,
  19. selectedPath,
  20. modifiedFiles,
  21. modifyFileContent,
  22. saveFileContent: saveNewFileContent,
  23. } = useFiles();
  24. const { mutate: saveFile } = useSaveFile();
  25. const handleEditorChange = (value: string | undefined) => {
  26. if (selectedPath && value) modifyFileContent(selectedPath, value);
  27. };
  28. const isBase64Image = (content: string) => content.startsWith("data:image/");
  29. const isPDF = (content: string) => content.startsWith("data:application/pdf");
  30. const isVideo = (content: string) => content.startsWith("data:video/");
  31. React.useEffect(() => {
  32. const handleSave = async (event: KeyboardEvent) => {
  33. if (selectedPath && event.metaKey && event.key === "s") {
  34. const content = saveNewFileContent(selectedPath);
  35. if (content) {
  36. saveFile({ path: selectedPath, content });
  37. }
  38. }
  39. };
  40. document.addEventListener("keydown", handleSave);
  41. return () => {
  42. document.removeEventListener("keydown", handleSave);
  43. };
  44. }, [saveNewFileContent]);
  45. if (!selectedPath) {
  46. return (
  47. <div
  48. data-testid="code-editor-empty-message"
  49. className="flex flex-col h-full items-center justify-center text-neutral-400"
  50. >
  51. <VscCode size={100} />
  52. {t(I18nKey.CODE_EDITOR$EMPTY_MESSAGE)}
  53. </div>
  54. );
  55. }
  56. const fileContent: string | undefined =
  57. modifiedFiles[selectedPath] || files[selectedPath];
  58. if (fileContent) {
  59. if (isBase64Image(fileContent)) {
  60. return (
  61. <section className="flex flex-col relative items-center overflow-auto h-[90%]">
  62. <img
  63. src={fileContent}
  64. alt={selectedPath}
  65. className="object-contain"
  66. />
  67. </section>
  68. );
  69. }
  70. if (isPDF(fileContent)) {
  71. return (
  72. <iframe
  73. src={fileContent}
  74. title={selectedPath}
  75. width="100%"
  76. height="100%"
  77. />
  78. );
  79. }
  80. if (isVideo(fileContent)) {
  81. return (
  82. <video controls src={fileContent} width="100%" height="100%">
  83. <track kind="captions" label="English captions" />
  84. </video>
  85. );
  86. }
  87. }
  88. return (
  89. <Editor
  90. data-testid="code-editor"
  91. path={selectedPath ?? undefined}
  92. defaultValue=""
  93. value={selectedPath ? fileContent : undefined}
  94. onMount={onMount}
  95. onChange={handleEditorChange}
  96. options={{ readOnly: isReadOnly }}
  97. />
  98. );
  99. }
  100. export default React.memo(CodeEditorComponent);