ProzillaOS/src/components/apps/text-editor/TextEditor.jsx
2023-12-26 11:53:48 +01:00

155 lines
No EOL
3.6 KiB
JavaScript

import React, { useEffect, useState } from "react";
import styles from "./TextEditor.module.css";
import { HeaderMenu } from "../_utils/header-menu/HeaderMenu.jsx";
import Markdown from "markdown-to-jsx";
import { DEFAULT_ZOOM, ZOOM_FACTOR } from "../../../config/apps/textEditor.config.js";
import AppsManager from "../../../features/apps/appsManager.js";
import { TITLE_SEPARATOR } from "../../../config/windows.config.js";
/**
* @param {import("../../windows/WindowView.jsx").windowProps} props
*/
export function TextEditor({ file, setTitle, setIconUrl, close, mode, app }) {
const [currentFile, setCurrentFile] = useState(file);
const [currentMode, setCurrentMode] = useState(mode);
const [content, setContent] = useState(file?.content ?? "");
const [unsavedChanges, setUnsavedChanges] = useState(file == null);
const [zoom, setZoom] = useState(DEFAULT_ZOOM);
useEffect(() => {
(async () => {
let newContent = "";
// Load file
if (currentFile) {
if (currentFile.content) {
newContent = currentFile.content;
} else if (currentFile.source) {
await fetch(currentFile.source).then((response) =>
response.text()
).then((response) => {
newContent = response;
});
}
const iconUrl = currentFile.getIconUrl();
if (iconUrl)
setIconUrl(iconUrl);
} else {
setIconUrl(AppsManager.getAppIconUrl(app.id));
}
setContent(newContent);
})();
}, [app.id, currentFile, setIconUrl]);
useEffect(() => {
// Update title
let label = currentFile?.id ?? "Untitled";
if (unsavedChanges)
label += "*";
if (currentMode === "view")
label += " (preview)";
setTitle(`${label} ${TITLE_SEPARATOR} ${app.name}`);
}, [currentFile, setTitle, unsavedChanges, currentMode, app.name]);
const newText = () => {
setCurrentFile(null);
setCurrentMode("edit");
setUnsavedChanges(true);
};
const saveTextAs = () => {
onChange({ target: { value: content } });
};
const saveText = () => {
if (currentFile == null)
return saveTextAs();
currentFile.setContent(content);
onChange({ target: { value: content } });
};
const onChange = (event) => {
const value = event.target.value;
if (currentFile != null) {
setUnsavedChanges(currentFile.content !== value);
} else {
setUnsavedChanges(true);
}
return setContent(value);
};
return (
<div className={styles.Container} style={{ fontSize: zoom }}>
<HeaderMenu
options={{
"File": {
"New": newText,
"Save": saveText,
// "Save As": saveTextAs,
"Quit": () => {
close();
},
},
"View": {
[currentMode === "view" ? "Edit mode" : "Preview mode"]: () => {
setCurrentMode(currentMode === "view" ? "edit" : "view");
},
"Zoom In": () => {
setZoom(zoom + ZOOM_FACTOR);
},
"Zoom Out": () => {
setZoom(zoom - ZOOM_FACTOR);
},
"Reset Zoom": () => {
setZoom(DEFAULT_ZOOM);
}
}
}}
shortcuts={{
"File": {
"New": ["Control", "e"],
"Save": ["Control", "s"],
"Quit": ["Control", "q"],
},
"View": {
"Zoom In": ["Control", "+"],
"Zoom Out": ["Control", "-"],
"Reset Zoom": ["Control", "0"],
}
}}
/>
{currentMode === "view"
? <div className={styles.View}>
{file?.extension === "md"
? <Markdown options={{ overrides: {
a: {
props: {
target: "_blank"
}
}
} }}>
{content}
</Markdown>
: <p>{content}</p>
}
</div>
: <textarea
className={styles.View}
value={content}
onChange={onChange}
spellCheck={false}
autoComplete="off"
autoFocus
/>
}
</div>
);
}