diff --git a/packages/core/src/components/taskbar/Taskbar.module.css b/packages/core/src/components/taskbar/Taskbar.module.css
index 7a00449..64e2583 100644
--- a/packages/core/src/components/taskbar/Taskbar.module.css
+++ b/packages/core/src/components/taskbar/Taskbar.module.css
@@ -63,6 +63,10 @@
width: auto;
}
+button.HomeButton {
+ padding: 0.625rem;
+}
+
.HomeButton * {
fill: var(--foreground-color-0);
filter: none;
diff --git a/packages/core/src/components/taskbar/app-icon/AppIcon.module.css b/packages/core/src/components/taskbar/app-icon/AppIcon.module.css
index 2152ee2..e5938d8 100644
--- a/packages/core/src/components/taskbar/app-icon/AppIcon.module.css
+++ b/packages/core/src/components/taskbar/app-icon/AppIcon.module.css
@@ -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 {
diff --git a/packages/core/src/components/taskbar/menus/AppList.module.css b/packages/core/src/components/taskbar/menus/AppList.module.css
index 4d38309..9eee0fb 100644
--- a/packages/core/src/components/taskbar/menus/AppList.module.css
+++ b/packages/core/src/components/taskbar/menus/AppList.module.css
@@ -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 {
diff --git a/packages/core/src/components/taskbar/menus/HomeMenu.module.css b/packages/core/src/components/taskbar/menus/HomeMenu.module.css
index 2acd65f..e8942f2 100644
--- a/packages/core/src/components/taskbar/menus/HomeMenu.module.css
+++ b/packages/core/src/components/taskbar/menus/HomeMenu.module.css
@@ -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;
}
\ No newline at end of file
diff --git a/packages/core/src/components/taskbar/menus/HomeMenu.tsx b/packages/core/src/components/taskbar/menus/HomeMenu.tsx
index 8ab5d1d..44ee342 100644
--- a/packages/core/src/components/taskbar/menus/HomeMenu.tsx
+++ b/packages/core/src/components/taskbar/menus/HomeMenu.tsx
@@ -60,16 +60,18 @@ export function HomeMenu({ active, setActive, search }: HomeMenuProps) {
-
-
{systemName}
+
+
+ {systemName}
+
{appsConfig.apps.sort((a, b) => a.name.localeCompare(b.name)).map(({ name, id, iconUrl }) =>
{
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;
diff --git a/public/assets/banner-logo-title-small.png b/public/assets/banner-logo-title-small.png
index faeccb1..1bf1603 100644
Binary files a/public/assets/banner-logo-title-small.png and b/public/assets/banner-logo-title-small.png differ
diff --git a/public/assets/banner-logo-title.png b/public/assets/banner-logo-title.png
index 0d182c9..a612962 100644
Binary files a/public/assets/banner-logo-title.png and b/public/assets/banner-logo-title.png differ
diff --git a/public/assets/logo.png b/public/assets/logo.png
index 31b4694..1b9b2e4 100644
Binary files a/public/assets/logo.png and b/public/assets/logo.png differ
diff --git a/public/assets/logo.svg b/public/assets/logo.svg
new file mode 100644
index 0000000..f79ad30
--- /dev/null
+++ b/public/assets/logo.svg
@@ -0,0 +1,21 @@
+
diff --git a/src/components/apps/text-editor/TextEditor.tsx b/src/components/apps/text-editor/TextEditor.tsx
new file mode 100644
index 0000000..e1ef52e
--- /dev/null
+++ b/src/components/apps/text-editor/TextEditor.tsx
@@ -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();
+ const windowsManager = useWindowsManager();
+ const virtualRoot = useVirtualRoot();
+ const [currentFile, setCurrentFile] = useState(file as VirtualFile);
+ const [currentMode, setCurrentMode] = useState(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 = {};
+ for (const [key, value] of Object.entries(OVERRIDES)) {
+ overrides[key] = {
+ component: value,
+ props: {
+ modalsManager,
+ setCurrentFile,
+ currentFile,
+ app,
+ windowsManager
+ } as MarkdownProps
+ };
+ }
+
+ return (
+
+
+
+ { newText(); }} shortcut={["Control", "e"]}/>
+ {
+ openWindowedModal({
+ size: DEFAULT_FILE_SELECTOR_SIZE,
+ Modal: (props: object) => {
+ setCurrentFile(file as VirtualFile);
+ setUnsavedChanges(false);
+ }}
+ {...props}
+ />
+ });
+ }} shortcut={["Control", "o"]}/>
+
+ { saveText(); }} shortcut={["Control", "s"]}/>
+ {
+ if (windowsManager != null) currentFile?.parent?.open(windowsManager);
+ }}/>
+
+ { close?.(); }} shortcut={["Control", "q"]}/>
+
+
+ {
+ setCurrentMode(currentMode === "view" ? "edit" : "view");
+ }} shortcut={["Control", "u"]}/>
+
+ { setZoom(zoom + ZOOM_FACTOR); }} shortcut={["Control", "+"]}/>
+ { setZoom(zoom - ZOOM_FACTOR); }} shortcut={["Control", "-"]}/>
+ { setZoom(DEFAULT_ZOOM); }} shortcut={["Control", "0"]}/>
+
+
+ {currentMode === "view"
+ ? currentFile?.extension != null && CODE_FORMATS.includes(currentFile?.extension)
+ ?
{content}
+ :
} className={styles.View}>
+ {currentFile?.extension === "md"
+ ?
{content}
+ :
{content}
+ }
+
+ :
+ );
+}
\ No newline at end of file
diff --git a/src/components/standalone/StandaloneHeader.module.css b/src/components/standalone/StandaloneHeader.module.css
index 34eaa31..060b9bd 100644
--- a/src/components/standalone/StandaloneHeader.module.css
+++ b/src/components/standalone/StandaloneHeader.module.css
@@ -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 {
diff --git a/src/components/standalone/StandaloneHeader.tsx b/src/components/standalone/StandaloneHeader.tsx
index 5164927..2977204 100644
--- a/src/components/standalone/StandaloneHeader.tsx
+++ b/src/components/standalone/StandaloneHeader.tsx
@@ -11,7 +11,7 @@ interface StandaloneHeaderProps {
export function StandaloneHeader({ exit }: StandaloneHeaderProps) {
return
-
+
{NAME}
diff --git a/src/router/routes/StandaloneRoute.module.css b/src/router/routes/StandaloneRoute.module.css
index 2c58491..6ae894f 100644
--- a/src/router/routes/StandaloneRoute.module.css
+++ b/src/router/routes/StandaloneRoute.module.css
@@ -1,5 +1,5 @@
.StandaloneView {
- --header-height: 2.5rem;
+ --header-height: 2.75rem;
display: flex;
flex-direction: column;