Merge branch 'main' into convert-to-monorepo
|
|
@ -63,6 +63,10 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
button.HomeButton {
|
||||
padding: 0.625rem;
|
||||
}
|
||||
|
||||
.HomeButton * {
|
||||
fill: var(--foreground-color-0);
|
||||
filter: none;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,14 @@
|
|||
|
||||
.App-icon svg {
|
||||
filter: var(--svg-drop-shadow-0);
|
||||
transform: scale(100%);
|
||||
transform-origin: center;
|
||||
transition: transform 300ms ease-out 150ms;
|
||||
}
|
||||
|
||||
.App-icon:not(.Active):hover svg,
|
||||
.App-icon:not(.Active):focus-visible svg {
|
||||
transform: scale(115%);
|
||||
}
|
||||
|
||||
.App-icon.Hidden {
|
||||
|
|
|
|||
|
|
@ -9,18 +9,21 @@
|
|||
}
|
||||
|
||||
.AppButton {
|
||||
--size: 2rem;
|
||||
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: var(--border-radius-1);
|
||||
}
|
||||
|
||||
.AppButton > div,
|
||||
.AppButton > div > div,
|
||||
.AppButton > div > div > svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
}
|
||||
|
||||
.AppButton svg {
|
||||
|
|
|
|||
|
|
@ -4,28 +4,84 @@
|
|||
}
|
||||
|
||||
.HomeMenu {
|
||||
--default-buttons-width: 3rem;
|
||||
--buttons-count: 5;
|
||||
|
||||
min-width: 16rem;
|
||||
max-width: 32rem;
|
||||
min-height: calc(var(--default-buttons-width) * var(--buttons-count));
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: var(--border-radius-1);
|
||||
}
|
||||
|
||||
.Buttons {
|
||||
--buttons-width: var(--default-buttons-width);
|
||||
--padding: 0.25rem;
|
||||
--filter: blur(0px);
|
||||
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 0.25rem;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: var(--padding);
|
||||
margin-top: auto;
|
||||
width: var(--buttons-width);
|
||||
height: 100%;
|
||||
border-radius: var(--border-radius-1);
|
||||
overflow: hidden;
|
||||
transition: width 200ms ease-out 200ms;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.Buttons::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
backdrop-filter: var(--filter);
|
||||
transition: backdrop-filter 200ms ease-out 200ms;
|
||||
}
|
||||
|
||||
.Buttons:hover,
|
||||
.Buttons:focus-visible {
|
||||
--buttons-width: 10rem;
|
||||
--filter: var(--taskbar-filter);
|
||||
}
|
||||
|
||||
.Buttons > button {
|
||||
--button-width: calc(var(--buttons-width) - var(--padding) * 2);
|
||||
--button-height: calc(var(--default-buttons-width) - var(--padding) * 2);
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
width: var(--button-width);
|
||||
height: var(--button-height);
|
||||
border-radius: var(--border-radius-1);
|
||||
overflow: hidden;
|
||||
transition: inherit;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.Buttons > button > svg {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.Buttons > button > p {
|
||||
position: absolute;
|
||||
left: calc(var(--default-buttons-width) - var(--padding) * 2);
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.Apps {
|
||||
|
|
@ -33,15 +89,44 @@
|
|||
flex-direction: column;
|
||||
width: 100% !important;
|
||||
max-height: 20rem;
|
||||
padding: 1rem;
|
||||
padding: 0.5rem;
|
||||
padding-right: 0.25rem;
|
||||
padding-bottom: 0;
|
||||
margin-left: var(--default-buttons-width);
|
||||
}
|
||||
|
||||
.Apps > h1 {
|
||||
.Logo {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
width: auto;
|
||||
margin-bottom: 0.5rem;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.Logo div,
|
||||
.Logo svg {
|
||||
width: auto;
|
||||
height: 2.25rem;
|
||||
color: inherit;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.Logo svg {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.Logo svg * {
|
||||
color: inherit;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.Logo > h1 {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
|
@ -60,16 +60,18 @@ export function HomeMenu({ active, setActive, search }: HomeMenuProps) {
|
|||
<div className={classNames.join(" ")}>
|
||||
<div className={`${styles.HomeMenu} ${taskbarStyles.Menu}`}>
|
||||
<div className={styles.Buttons}>
|
||||
<button title="Shut Down" tabIndex={tabIndex} onClick={() => { closeViewport(true, systemName); }}>
|
||||
<button tabIndex={tabIndex} onClick={() => { closeViewport(true, systemName); }}>
|
||||
<FontAwesomeIcon icon={faPowerOff}/>
|
||||
<p className={utilStyles.TextRegular}>Shut down</p>
|
||||
</button>
|
||||
<button title="Settings" tabIndex={tabIndex} onClick={() => {
|
||||
<button tabIndex={tabIndex} onClick={() => {
|
||||
setActive(false);
|
||||
windowsManager?.open("settings");
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faGear}/>
|
||||
<p className={utilStyles.TextRegular}>Settings</p>
|
||||
</button>
|
||||
<button title="Info" tabIndex={tabIndex} onClick={() => {
|
||||
<button tabIndex={tabIndex} onClick={() => {
|
||||
setActive(false);
|
||||
windowsManager?.open("text-editor", {
|
||||
mode: "view",
|
||||
|
|
@ -78,22 +80,28 @@ export function HomeMenu({ active, setActive, search }: HomeMenuProps) {
|
|||
});
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faCircleInfo}/>
|
||||
<p className={utilStyles.TextRegular}>Info</p>
|
||||
</button>
|
||||
<button title="Images" tabIndex={tabIndex} onClick={() => {
|
||||
<button tabIndex={tabIndex} onClick={() => {
|
||||
setActive(false);
|
||||
windowsManager?.open(fileExplorer.id, { path: "~/Pictures" });
|
||||
}}>
|
||||
<FontAwesomeIcon icon={faImage}/>
|
||||
<p className={utilStyles.TextRegular}>Images</p>
|
||||
</button>
|
||||
<button title="Documents" tabIndex={tabIndex} onClick={() => {
|
||||
<button tabIndex={tabIndex} onClick={() => {
|
||||
setActive(false);
|
||||
windowsManager?.open(fileExplorer.id, { path: "~/Documents" }); }
|
||||
}>
|
||||
<FontAwesomeIcon icon={faFileLines}/>
|
||||
<p className={utilStyles.TextRegular}>Documents</p>
|
||||
</button>
|
||||
</div>
|
||||
<div className={styles.Apps}>
|
||||
<h1 className={utilStyles.TextBold}>{systemName}</h1>
|
||||
<span className={styles.Logo}>
|
||||
<ReactSVG src={"/assets/logo.svg"}/>
|
||||
<h1 className={utilStyles.TextBold}>{systemName}</h1>
|
||||
</span>
|
||||
<div className={appStyles.AppList}>
|
||||
{appsConfig.apps.sort((a, b) => a.name.localeCompare(b.name)).map(({ name, id, iconUrl }) =>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -55,12 +55,12 @@ export function loadDefaultData(systemManager: SystemManager, virtualRoot: Virtu
|
|||
file.setContent("Hello world!");
|
||||
}).createFile("Info", "md", (file) => {
|
||||
file.setProtected(true)
|
||||
.setSource("/documents/Info.md")
|
||||
.setSource("/documents/info.md")
|
||||
.setIconUrl(virtualDriveConfig.infoFileIcon);
|
||||
linkedPaths.info = file.path;
|
||||
}).createFile("Prozilla", "md", (file) => {
|
||||
file.setProtected(true)
|
||||
.setSource("/documents/Prozilla.md");
|
||||
.setSource("/documents/prozilla.md");
|
||||
linkedPaths.links = file.path;
|
||||
});
|
||||
linkedPaths.documents = folder.path;
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
21
public/assets/logo.svg
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<svg width="1740" height="1236" viewBox="0 0 1740 1236" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_366_104)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M655.534 48.012C665.892 48.3096 675.304 54.1104 680.225 63.2294L777.822 244.076C782.744 253.195 782.426 264.247 776.989 273.068C771.553 281.89 761.823 287.141 751.465 286.843L546.048 280.941C535.691 280.643 526.278 274.843 521.357 265.724C516.436 256.605 516.754 245.553 522.19 236.732L630.01 61.7866C635.446 52.9653 645.176 47.7144 655.534 48.012Z" fill="#36B39E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M262.365 192.847C269.861 187.431 279.677 186.424 288.116 190.207L455.479 265.233C463.918 269.016 469.698 277.014 470.641 286.214C471.585 295.414 467.548 304.418 460.053 309.835L311.397 417.263C303.901 422.68 294.085 423.686 285.646 419.903C277.207 416.12 271.427 408.122 270.484 398.922L251.776 216.469C250.833 207.269 254.869 198.264 262.365 192.847Z" fill="#36B39E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.4691 501.642C58.3118 493.893 65.0727 488.25 73.2049 486.837L234.483 458.822C242.616 457.41 250.884 460.443 256.173 466.779C261.463 473.116 262.97 481.793 260.127 489.542L203.749 643.22C200.907 650.969 194.146 656.613 186.014 658.026C177.881 659.438 169.613 656.405 164.324 650.069L59.423 524.405C54.1335 518.068 52.6263 509.391 55.4691 501.642Z" fill="#36B39E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.404 832.181C46.9537 824.958 49.4667 817.51 54.9965 812.643L164.662 716.116C170.192 711.248 177.898 709.701 184.878 712.056C191.858 714.411 197.052 720.311 198.502 727.534L227.265 870.771C228.715 877.994 226.202 885.441 220.672 890.309C215.142 895.176 207.436 896.723 200.456 894.368L62.0277 847.659C55.0477 845.303 49.8543 839.403 48.404 832.181Z" fill="#36B39E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M201.099 1064.52C197.593 1060.17 196.675 1054.28 198.691 1049.07L238.673 945.697C240.689 940.485 245.332 936.744 250.854 935.884C256.376 935.024 261.938 937.175 265.444 941.527L334.973 1027.84C338.479 1032.19 339.397 1038.08 337.381 1043.29C335.365 1048.51 330.721 1052.25 325.2 1053.11L215.688 1070.17C210.166 1071.03 204.604 1068.87 201.099 1064.52Z" fill="#36B39E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M635.249 1091.1C843.65 1091.1 1012.59 922.162 1012.59 713.761C1012.59 505.36 843.65 336.417 635.249 336.417C426.847 336.417 257.905 505.36 257.905 713.761C257.905 922.162 426.847 1091.1 635.249 1091.1ZM835.861 717.324C835.861 827.757 746.338 917.28 635.905 917.28C525.472 917.28 435.948 827.757 435.948 717.324C435.948 606.891 525.472 517.367 635.905 517.367C746.338 517.367 835.861 606.891 835.861 717.324Z" fill="#4DFFE1"/>
|
||||
<path d="M1291.9 1089.56C1230.02 1089.56 1175.96 1080.72 1129.72 1063.04C1084.16 1044.68 1042 1015.78 1003.24 976.344L1130.74 848.844C1157.26 874.004 1185.14 893.384 1214.38 906.984C1243.62 919.904 1274.9 926.364 1308.22 926.364C1336.1 926.364 1357.18 922.284 1371.46 914.124C1385.74 905.284 1392.88 893.384 1392.88 878.424C1392.88 863.464 1386.76 851.224 1374.52 841.704C1362.28 831.504 1345.96 822.664 1325.56 815.184C1305.84 807.024 1283.74 798.864 1259.26 790.704C1235.46 782.544 1211.66 772.684 1187.86 761.124C1164.06 749.564 1141.96 735.624 1121.56 719.304C1101.84 702.304 1085.86 681.564 1073.62 657.084C1061.38 631.924 1055.26 601.324 1055.26 565.284C1055.26 518.364 1066.48 477.904 1088.92 443.904C1111.36 409.904 1142.98 384.064 1183.78 366.384C1224.58 348.024 1272.52 338.844 1327.6 338.844C1382 338.844 1432.32 347.684 1478.56 365.364C1525.48 382.364 1564.24 406.844 1594.84 438.804L1466.32 566.304C1443.88 544.544 1421.44 528.564 1399 518.364C1376.56 507.484 1352.08 502.044 1325.56 502.044C1304.48 502.044 1287.48 505.444 1274.56 512.244C1262.32 519.044 1256.2 529.244 1256.2 542.844C1256.2 557.124 1262.32 569.024 1274.56 578.544C1286.8 587.384 1302.78 595.544 1322.5 603.024C1342.9 610.504 1365 618.324 1388.8 626.484C1413.28 634.644 1437.42 644.504 1461.22 656.064C1485.02 666.944 1506.78 681.224 1526.5 698.904C1546.9 715.904 1563.22 737.324 1575.46 763.164C1587.7 789.004 1593.82 820.284 1593.82 857.004C1593.82 931.124 1567.3 988.584 1514.26 1029.38C1461.9 1069.5 1387.78 1089.56 1291.9 1089.56Z" fill="#4DFFE1"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_366_104" x="48" y="48" width="1548" height="1068" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="24"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.117 0 0 0 0 0.1485 0 0 0 0 0.18 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_366_104"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_366_104" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
218
src/components/apps/text-editor/TextEditor.tsx
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
import { ReactNode, Ref, useEffect, useRef, useState } from "react";
|
||||
import styles from "./TextEditor.module.css";
|
||||
import { HeaderMenu } from "../_utils/header-menu/HeaderMenu";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import { CODE_FORMATS, DEFAULT_ZOOM, EXTENSION_TO_LANGUAGE, ZOOM_FACTOR } from "../../../config/apps/textEditor.config";
|
||||
import { AppsManager } from "../../../features/apps/appsManager";
|
||||
import { TITLE_SEPARATOR } from "../../../config/windows.config";
|
||||
import { MarkdownLink } from "./overrides/MarkdownLink";
|
||||
import { MarkdownImage } from "./overrides/MarkdownImage";
|
||||
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { useWindowedModal } from "../../../hooks/modals/windowedModal";
|
||||
import { DEFAULT_FILE_SELECTOR_SIZE } from "../../../config/modals.config";
|
||||
import { FileSelector } from "../../modals/file-selector/FileSelector";
|
||||
import { SELECTOR_MODE } from "../../../config/apps/fileExplorer.config";
|
||||
import { VirtualFile } from "../../../features/virtual-drive/file";
|
||||
import { WindowProps } from "../../windows/WindowView";
|
||||
import { DropdownAction } from "../../actions/actions/DropdownAction";
|
||||
import { ClickAction } from "../../actions/actions/ClickAction";
|
||||
import { ModalsManager } from "../../../features/modals/modalsManager";
|
||||
import { App } from "../../../features/apps/app";
|
||||
import { WindowsManager } from "../../../features/windows/windowsManager";
|
||||
import { MarkdownBlockquote } from "./overrides/MarkdownBlockquote";
|
||||
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext";
|
||||
import { APP_NAMES } from "../../../config/apps.config";
|
||||
import { Divider } from "../../actions/actions/Divider";
|
||||
|
||||
const OVERRIDES = {
|
||||
a: MarkdownLink,
|
||||
img: MarkdownImage,
|
||||
blockquote: MarkdownBlockquote,
|
||||
};
|
||||
|
||||
export interface MarkdownProps {
|
||||
modalsManager: ModalsManager;
|
||||
setCurrentFile: Function;
|
||||
currentFile: VirtualFile;
|
||||
app: App;
|
||||
windowsManager: WindowsManager;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
interface TextEditorProps extends WindowProps {
|
||||
file?: VirtualFile;
|
||||
mode?: "view" | "edit";
|
||||
path?: string;
|
||||
}
|
||||
|
||||
export function TextEditor({ file, path, setTitle, setIconUrl, close, mode, app, modalsManager }: TextEditorProps) {
|
||||
const ref = useRef<HTMLDivElement | HTMLTextAreaElement>();
|
||||
const windowsManager = useWindowsManager();
|
||||
const virtualRoot = useVirtualRoot();
|
||||
const [currentFile, setCurrentFile] = useState<VirtualFile | null>(file as VirtualFile);
|
||||
const [currentMode, setCurrentMode] = useState<TextEditorProps["mode"]>(mode);
|
||||
const [content, setContent] = useState(file?.content ?? "");
|
||||
const [unsavedChanges, setUnsavedChanges] = useState(file == null);
|
||||
const [zoom, setZoom] = useState(DEFAULT_ZOOM);
|
||||
const [initialised, setInitialised] = useState(false);
|
||||
const { openWindowedModal } = useWindowedModal();
|
||||
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
let newContent: string | null = "";
|
||||
|
||||
// Load file
|
||||
if (currentFile) {
|
||||
newContent = await currentFile.read() as string | null;
|
||||
|
||||
const iconUrl = currentFile.getIconUrl();
|
||||
if (iconUrl)
|
||||
setIconUrl?.(iconUrl);
|
||||
|
||||
if (newContent?.trim() === "")
|
||||
setCurrentMode("edit");
|
||||
} else if (app != null) {
|
||||
setIconUrl?.(AppsManager.getAppIconUrl(app.id));
|
||||
}
|
||||
|
||||
if (newContent != null)
|
||||
setContent(newContent);
|
||||
|
||||
if (ref.current)
|
||||
(ref.current as HTMLElement).scrollTo(0, 0);
|
||||
})();
|
||||
}, [app?.id, currentFile, setIconUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
// Update title
|
||||
let label = currentFile?.id ?? "Untitled";
|
||||
|
||||
if (unsavedChanges)
|
||||
label += "*";
|
||||
|
||||
if (currentMode === "view")
|
||||
label += " (preview)";
|
||||
|
||||
setTitle?.(app != null ? `${label} ${TITLE_SEPARATOR} ${app.name}` : label);
|
||||
}, [currentFile, setTitle, unsavedChanges, currentMode, app?.name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialised && currentFile == null && path != null) {
|
||||
const newFile = virtualRoot?.navigate(path);
|
||||
|
||||
if (newFile == null || !newFile.isFile())
|
||||
return;
|
||||
|
||||
setCurrentFile(newFile as VirtualFile);
|
||||
setInitialised(true);
|
||||
}
|
||||
}, [path, currentFile]);
|
||||
|
||||
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: Event | { target: { value: string } }) => {
|
||||
const value = (event.target as HTMLInputElement).value;
|
||||
|
||||
if (currentFile != null) {
|
||||
setUnsavedChanges(currentFile.content !== value);
|
||||
} else {
|
||||
setUnsavedChanges(true);
|
||||
}
|
||||
|
||||
return setContent(value);
|
||||
};
|
||||
|
||||
const overrides: Record<string, object> = {};
|
||||
for (const [key, value] of Object.entries(OVERRIDES)) {
|
||||
overrides[key] = {
|
||||
component: value,
|
||||
props: {
|
||||
modalsManager,
|
||||
setCurrentFile,
|
||||
currentFile,
|
||||
app,
|
||||
windowsManager
|
||||
} as MarkdownProps
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.TextEditor} style={{ fontSize: zoom }}>
|
||||
<HeaderMenu>
|
||||
<DropdownAction label="File" showOnHover={false}>
|
||||
<ClickAction label="New" onTrigger={() => { newText(); }} shortcut={["Control", "e"]}/>
|
||||
<ClickAction label="Open" onTrigger={() => {
|
||||
openWindowedModal({
|
||||
size: DEFAULT_FILE_SELECTOR_SIZE,
|
||||
Modal: (props: object) => <FileSelector
|
||||
type={SELECTOR_MODE.SINGLE}
|
||||
onFinish={(file) => {
|
||||
setCurrentFile(file as VirtualFile);
|
||||
setUnsavedChanges(false);
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
});
|
||||
}} shortcut={["Control", "o"]}/>
|
||||
<Divider/>
|
||||
<ClickAction label="Save" onTrigger={() => { saveText(); }} shortcut={["Control", "s"]}/>
|
||||
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} disabled={currentFile == null} onTrigger={() => {
|
||||
if (windowsManager != null) currentFile?.parent?.open(windowsManager);
|
||||
}}/>
|
||||
<Divider/>
|
||||
<ClickAction label="Quit" onTrigger={() => { close?.(); }} shortcut={["Control", "q"]}/>
|
||||
</DropdownAction>
|
||||
<DropdownAction label="View" showOnHover={false}>
|
||||
<ClickAction label={currentMode === "view" ? "Edit mode" : "Preview mode"} onTrigger={() => {
|
||||
setCurrentMode(currentMode === "view" ? "edit" : "view");
|
||||
}} shortcut={["Control", "u"]}/>
|
||||
<Divider/>
|
||||
<ClickAction label="Zoom in" onTrigger={() => { setZoom(zoom + ZOOM_FACTOR); }} shortcut={["Control", "+"]}/>
|
||||
<ClickAction label="Zoom out" onTrigger={() => { setZoom(zoom - ZOOM_FACTOR); }} shortcut={["Control", "-"]}/>
|
||||
<ClickAction label="Reset Zoom" disabled={zoom == DEFAULT_ZOOM} onTrigger={() => { setZoom(DEFAULT_ZOOM); }} shortcut={["Control", "0"]}/>
|
||||
</DropdownAction>
|
||||
</HeaderMenu>
|
||||
{currentMode === "view"
|
||||
? currentFile?.extension != null && CODE_FORMATS.includes(currentFile?.extension)
|
||||
? <SyntaxHighlighter
|
||||
language={EXTENSION_TO_LANGUAGE[currentFile?.extension] ?? currentFile?.extension}
|
||||
className={styles.Code}
|
||||
useInlineStyles={false}
|
||||
showLineNumbers={true}
|
||||
>{content}</SyntaxHighlighter>
|
||||
: <div ref={ref as Ref<HTMLDivElement>} className={styles.View}>
|
||||
{currentFile?.extension === "md"
|
||||
? <Markdown options={{ overrides } as object}>{content}</Markdown>
|
||||
: <pre><p>{content}</p></pre>
|
||||
}
|
||||
</div>
|
||||
: <textarea
|
||||
ref={ref as Ref<HTMLTextAreaElement>}
|
||||
className={styles.View}
|
||||
value={content}
|
||||
onChange={onChange}
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
autoFocus
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
color: var(--foreground-color-1);
|
||||
text-decoration: none;
|
||||
transition: color 300ms ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.Logo:hover {
|
||||
|
|
@ -25,7 +26,7 @@
|
|||
.Logo div,
|
||||
.Logo svg {
|
||||
width: auto;
|
||||
height: 1.5rem;
|
||||
height: 2.5rem;
|
||||
color: inherit;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
|
@ -43,6 +44,8 @@
|
|||
margin: 0;
|
||||
color: inherit;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
||||
.ExitButton {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ interface StandaloneHeaderProps {
|
|||
export function StandaloneHeader({ exit }: StandaloneHeaderProps) {
|
||||
return <header className={styles.Header}>
|
||||
<a className={styles.Logo} href="/" tabIndex={0}>
|
||||
<ReactSVG src={"/assets/icon-mask.svg"}/>
|
||||
<ReactSVG src={"/assets/logo.svg"}/>
|
||||
<h1>{NAME}</h1>
|
||||
</a>
|
||||
<Button className={styles.ExitButton} onClick={exit} icon={faTimes}>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
.StandaloneView {
|
||||
--header-height: 2.5rem;
|
||||
--header-height: 2.75rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||