Updated documentation

This commit is contained in:
Prozilla 2024-06-13 21:54:26 +02:00
parent 750a758634
commit ae6b8209b6
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
38 changed files with 1126 additions and 635 deletions

View file

@ -1,22 +0,0 @@
#!/bin/bash
# Edit these variables before deploying
COMMIT_MESSAGE="Deployed build to GitHub Pages"
REPO_URL="https://github.com/Prozilla/ProzillaOS"
# ------- You don't need to edit anything below this line -------
echo -e "\e[0;33mDeploying to GitHub Pages...\e[0m"
domain=$(cat dist/CNAME)
echo -e "Domain: \e[0;36m$domain\e[0m"
echo -e "Commit message: \e[0;36m$COMMIT_MESSAGE\e[0m"
echo -e "Repository: \e[0;36m$REPO_URL\e[0m"
echo ""
if gh-pages -d dist -m "$COMMIT_MESSAGE" -r "$REPO_URL" ; then
echo -e "\e[0;32m✓ Successfully deployed to \e[0;36mhttps://$domain/\e[0m"
else
echo -e "\e[0;31mFailed to deploy\e[0m"
fi

View file

@ -25,11 +25,11 @@ These are the scripts in logical order, that will be available when you have ins
1. `npm run start`
Start Vite dev server at [localhost:3000](http://localhost:3000/). Changes to module will dynamically be hot-reloaded, so normally there is no need for hard-refreshes.
Start Vite dev server at [localhost:3000](http://localhost:3000/). Changes to module will dynamically be hot-reloaded, so normally there is no need for hard-refreshes. VSCode is configured to run this script whenever the project is opened.
2. `npm run build`
Compile project using TypeScript and bundle all files into the `dist` directory. This directory can be uploaded to a web server.
Compile project using TypeScript and bundle all files into the `dist` directory, or the directory specified in config file. This directory can be uploaded to a web server.
3. `npm run serve`
@ -37,17 +37,14 @@ These are the scripts in logical order, that will be available when you have ins
4. `npm run stage`
Run [node program](../src/tools/stage.ts) that stages the build and prepares it for deployment. Script will generate a sitemap, CNAME file, etc.
Execute [stage.ts](../scripts/stage.ts), which stages the build and prepares it for deployment. Script will generate a sitemap, robots.txt and all other necessary files.
5. `npm run deploy`
Run scripts #2 and #4, then execute [deploy.sh](../deploy.sh), which deploys the staged build to GitHub Pages on branch called `gh-pages`.
Run scripts #2 and #4, then execute [deploy.ts](../scripts/deploy.ts), which uploads the staged build to GitHub Pages on branch called `gh-pages`. This should then trigger a GitHub Action that deploys the build to production.
> [!IMPORTANT]
> Check the "Deployment" section on the [Configuration](./configuration/README.md) page, to see what configurations must be made before starting the staging or deployment process.
> [!NOTE]
> After this deployment process, GitHub Pages will run its own build step to finalize the deployment, as seen in the `Actions` tab on GitHub. This usually takes less than a minute and once it's done, your deployment will be live.
> Make sure you update your [configuration](./configuration/README.md) before running the `stage` or `deploy` script, otherwise your deployment will probably fail or be partially broken.
### Other scripts

View file

@ -6,31 +6,45 @@ ProzillaOS can be configured in numerous ways. The most important one being via
For developers, these are the methods of configuring ProzillaOS:
- `src/config` - The `src/config` directory holds all global variables used in the rest of the `src` directory, which are mostly string and number constants, but also includes some arrays and dictionaries that can be adjusted to configure ProzillaOS.
- `styles` - Everything related to styles, can be configured in `styles` directory. Most configurations will happen inside `styles/global`, where you can define the fonts, css variables/properties and other details.
- `public/config` - This directory has XML files that serve as the default data for config files used by the app in the virtual drive. These can be edited by the user once they're loaded during initialisation.
- `src/config`
This directory contains the main configurations for ProzillaOS.
- `styles`
Everything related to styles can be configured in this directory. The stylesheets inside `styles/global` contain CSS variables, font declarations, etc.
- `public/config`
This directory has XML files that serve as the default data for config files used by the app in the virtual drive. These can be edited by the user once they're loaded during initialisation.
## Deployment
- [deploy.config.ts](../../src/config/deploy.config.ts)
Before starting the deployment process, you will need to make sure these parts of your configuration are set up correctly.
### Domain
To change the domain that your website will be deployed to, go to [`branding.config.ts`](../../src/config/branding.config.ts) and set the `DOMAIN` variable to your own domain. You may also want to adjust the `BASE_URL` in case your domain does not have an SSL certificate. If you are using GitHub Pages, go to your Pages settings on GitHub and make sure the domain matches the one you just configured to deploy to.
Set the `DOMAIN` variable to the domain you wish to deploy the website to. You may also want to adjust the `BASE_URL` in case your domain does not have an SSL certificate. If you are using GitHub Pages, go to your Pages settings on GitHub and make sure the domain option there matches this variable.
> [!WARNING]
> This configuration is also required before starting the staging process.
### Branch
If you are deploying to GitHub Pages, go to your Pages settings on GitHub, navigate to "Build and deployment" and set the source to "Deploy from a branch". Then select the `gh-pages` branch, select the `/ (root)` folder and click "Save". Make sure your domain matches the domain in your configuration, as described in the previous paragraph.
If you are deploying to GitHub Pages, go to your Pages settings on GitHub, navigate to "Build and deployment" and set the source to "Deploy from a branch". Then select the `gh-pages` branch and select the `/ (root)` folder.
If you would like to use a different branch, also set the `REPO.branch` to that same branch.
### Repository URL & commit message
If you are deploying to GitHub Pages, open [deploy.sh](../../deploy.sh) and set the `REPO_URL` variable to the URL of your GitHub repository. You may also want to customize the `COMMIT_MESSAGE` variable, which will be used to name commits made to the `gh-pages` branch.
If you are deploying to GitHub Pages, set `REPO.owner` and `REPO.name` to your GitHub username and your repository's name respectively. You may also want to customize the `COMMIT_MESSAGE` variable, which will be used by the `deploy` script to commit the build to the `gh-pages` branch.
## Branding
### Name
- [branding.config.ts](../../src/config/branding.config.ts)
Change the variable called `NAME` in [`branding.config.ts`](../../src/config/branding.config.ts).
### Name & tag line
Set the `NAME` and `TAG_LINE` variables to the name and tag line or short description you wish to use for the website respectively.

View file

@ -4,14 +4,14 @@
Most code for features can be found in the [features](../../src/features) directory. This directory is a library that is mostly used by files in the [components](../../src/components) and [hooks](../../src/hooks) directory.
> [!NOTE]
> [!NOTE]
> Mentions of directories inside this part of the documentation, are relative to the `src` folder.
> ```
> components -> src/components
> hooks -> src/hooks
> ```
> [!TIP]
> [!TIP]
> To see the status and to-do's of each feature, check the [task board](https://prozilla.notion.site/8325fabca1fb4f9885b6d6dfd5aa64c8?v=1a59f7ce50914f5ea711fe6460e52868&pvs=4) on Notion.
## Pages
@ -24,4 +24,47 @@ Most code for features can be found in the [features](../../src/features) direct
- [Taskbar](taskbar/README.md)
- [Virtual Drive](virtual-drive//README.md)
- [Windows](windows/README.md)
- [Z-index](z-index/README.md)
- [Z-index](z-index/README.md)
## Overview
This is a simplified overview of ProzillaOS' features, check the pages inside this folder for more details about each individual feature.
### System
- Customizable **taskbar** with a home menu, search menu, pinned apps and utilities
- Customizable **desktop** with icons, accompanied by custom wallpapers
- **Virtual drive** that can handle files, folders, symbolic links, as well as read from external sources
- Storage system that stores and loads the virtual drive from local storage
### Applications
- Resizable and draggable **windows**, with dynamic titles, for displaying and interacting with apps, which adapts to the user's screen resolution
- Native and web-view **applications**
- **File explorer** that interacts with virtual drive and allows user to browse the source code on the website itself
- **Terminal** with custom linux-inspired commands
- **Settings** application for customizing appearance, managing apps and managing virtual drive
- **Text editor** app that can read and write files as well as render markdown files
- Other applications like a calculator, minigames, image viewer, browser, etc.
- **Standalone** system that allows each app to have its own dedicated page in an isolated view, which is also indexable by search engines
- **URL params** that trigger an app to open with optional arguments
### Interactions
- **Modals** that can be used as context menus, header menus, file selectors, dialog boxes, etc.
- Advanced **actions** system, for easily assembling different menus that can handle dropdowns, selections, toggles, shortcuts, etc.
### Assets
- Custom **wallpapers** made in Figma
- Custom **icons** made in Figma inspired by Font Awesome
### Codebase
- Free, open-source and self-hostable
- Uses **React**, **Vite** and **TypeScript**
- Extensive **documentation**
- **Configuration** files that allow you to configure almost everything inside the project
- **Eslint** for linting
- Custom **build and deployment tools** to automate tasks
- Separation of concerns and feature-based folder structure

View file

@ -29,7 +29,7 @@ In the list above, you can see some app names, as well as the corresponding IDs,
| Apps list | :x: | :heavy_check_mark: |
| Standalone title | :x: | :heavy_check_mark: |
> [!CAUTION]
> [!CAUTION]
> Changing the ID of an application that has already been deployed once may cause unexpected issues, like creating invalid links.
## Common components

827
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,11 @@
"name": "prozilla-os",
"version": "0.1.0",
"private": false,
"author": "Prozilla",
"author": {
"name": "Prozilla",
"email": "business@prozilla.dev",
"url": "https://prozilla.dev/"
},
"homepage": "https://os.prozilla.dev/",
"repository": "https://github.com/Prozilla/ProzillaOS",
"type": "module",
@ -10,10 +14,10 @@
"start": "vite --port 3000 --host",
"build": "tsc && vite build",
"serve": "vite preview --port 8080 --host",
"stage": "tsx scripts/stage",
"predeploy": "npm run build && npm run stage",
"stage": "node --no-warnings --loader ts-node/esm ./src/tools/stage",
"deploy": "sh deploy.sh",
"fetch": "node --no-warnings --loader ts-node/esm ./src/tools/fetchRepository"
"deploy": "tsx scripts/deploy",
"fetch": "tsx scripts/fetchRepository"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0",
@ -22,7 +26,6 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"anser": "^2.1.1",
"core-js": "^3.37.0",
"escape-carriage": "^1.3.1",
"markdown-to-jsx": "^7.2.1",
"react": "^18.2.0",
@ -35,27 +38,22 @@
"react-syntax-highlighter": "^15.5.0",
"react-tabs": "^6.0.2",
"vite": "^5.2.12",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^4.3.2"
"vite-plugin-svgr": "^4.2.0"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@eslint/js": "^9.2.0",
"@types/gh-pages": "^6.1.0",
"@types/node": "^20.12.8",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/webpack-env": "^1.18.4",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-refresh": "^0.4.7",
"gh-pages": "^5.0.0",
"ts-node": "^10.9.2",
"tsx": "^4.15.4",
"typescript": "^5.4.5",
"typescript-eslint": "^7.8.0",
"web-vitals": "^4.0.1"
"typescript-eslint": "^7.8.0"
},
"browserslist": {
"production": [

View file

@ -0,0 +1,16 @@
# Prozilla <img height="150" width="150" align="right" alt="Prozilla's logo" src="https://prozilla.dev/public/media/Prozilla.svg">
Hi, I'm Sieben De Beule, but you can call me Prozilla. I'm a passionate software developer and 3D artist. Experienced in application (especially games) and web development as well as web design.
## About me
I made ProzillaOS and a few other projects, including a VR game called [Crumbling City](https://store.steampowered.com/app/1520290/Crumbling_City/) which is available on Steam.
My portfolio is available at [prozilla.dev](https://prozilla.dev/) and you can contact me [business@prozilla.dev](mailto:business@prozilla.dev). If you would like to support my work, feel free to donate on my [Ko-fi page](https://ko-fi.com/prozilla). If you have any suggestions, bug reports on ProzillaOS or if you've made your own version of ProzillaOS, don't hesitate to contact me!
## Connect with me
<a href="https://twitter.com/prozilladev" target="blank"><img align="center" src="https://raw.githubusercontent.com/rahuldkjain/github-profile-readme-generator/master/src/images/icons/Social/twitter.svg" alt="prozilladev" height="30" width="40" /></a>
<a href="https://linkedin.com/in/sieben-de-beule" target="blank"><img align="center" src="https://raw.githubusercontent.com/rahuldkjain/github-profile-readme-generator/master/src/images/icons/Social/linked-in-alt.svg" alt="sieben de beule" height="30" width="40" /></a>
<a href="https://instagram.com/prozilladev" target="blank"><img align="center" src="https://raw.githubusercontent.com/rahuldkjain/github-profile-readme-generator/master/src/images/icons/Social/instagram.svg" alt="prozilladev" height="30" width="40" /></a>
<a href="https://www.youtube.com/c/prozilla" target="blank"><img align="center" src="https://raw.githubusercontent.com/rahuldkjain/github-profile-readme-generator/master/src/images/icons/Social/youtube.svg" alt="prozilla" height="30" width="40" /></a>

View file

@ -1,13 +1,15 @@
<img src="/assets/banner-logo-title-small.png" width="1440" height="700" style="aspect-ratio: 3 / 1; width: 100%; height: auto; object-fit: cover;" alt="Banner with the logo of ProzillaOS"/>
<img alt="License" src="https://img.shields.io/github/license/Prozilla/ProzillaOS?style=flat-square&color=ee5253&label=License"> <img alt="Stars" src="https://img.shields.io/github/stars/Prozilla/ProzillaOS?style=flat-square&color=feca57&label=%E2%AD%90"> <img alt="Forks" src="https://img.shields.io/github/forks/Prozilla/ProzillaOS?style=flat-square&color=54a0ff&label=Forks">
# Info
# ProzillaOS
This is ProzillaOS, a web-based operating system made with React.js by [Prozilla](https://prozilla.dev/).
<img src="/assets/banner-logo-title-small.png" alt="Banner with the logo of ProzillaOS"/>
ProzillaOS is a web-based operating system inspired by Ubuntu Linux and Windows made with React by [Prozilla](https://prozilla.dev/). It's a virtual desktop environment that mimics a real operating system and runs entirely in the browser.
## Open-source
ProzillaOS is [open-source](https://github.com/Prozilla/ProzillaOS)! Feel free to fork this project and create your own OS or share feedback by creating an issue on the GitHub page.
This website was made using React, Vite and TypeScript. The source code is available both on [GitHub](https://github.com/Prozilla/ProzillaOS) and [on the website itself](/). You can find the documentation for this project inside the [docs](/docs) folder.
## Support ProzillaOS
You can support this project by donating to [ko-fi.com/prozilla](https://ko-fi.com/prozilla).
If you like what you see, consider supporting this project by donating to [ko-fi.com/prozilla](https://ko-fi.com/prozilla).

View file

@ -1,15 +0,0 @@
# Links
**ProzillaOS**
- [GitHub](https://github.com/Prozilla/ProzillaOS)
**Prozilla**
- [Website (prozilla.dev)](https://prozilla.dev/)
- [Ko-fi](https://ko-fi.com/prozilla)
**Crumbling City**
- [Website (crumblingcity.com)](https://crumblingcity.com/)
- [Steam page](https://store.steampowered.com/app/1520290/Crumbling_City/)

29
scripts/_utils/print.ts Normal file
View file

@ -0,0 +1,29 @@
import { ANSI } from "../../src/config/apps/terminal.config";
export type status = "error" | "info" | "file" | "success" | "start";
export function print(message: string, status?: status, newLine?: boolean) {
if (newLine)
console.log("");
if (status == null)
return console.log(message);
switch (status) {
case "start":
console.log(`${ANSI.fg.yellow}${message}...${ANSI.reset}`);
break;
case "file":
console.log(`- ${ANSI.fg.cyan}${message}${ANSI.reset}`);
break;
case "success":
console.log(`${ANSI.fg.green}${message}${ANSI.reset}`);
break;
case "error":
console.error(`${ANSI.fg.red}${message}${ANSI.reset}`);
break;
default:
console.log(message);
break;
}
}

27
scripts/deploy.ts Normal file
View file

@ -0,0 +1,27 @@
import ghpages from "gh-pages";
import { ANSI } from "../src/config/apps/terminal.config";
import { BASE_URL, BUILD_DIR, COMMIT_MESSAGE, DOMAIN, REPO_URL } from "../src/config/deploy.config";
function deploy() {
console.log(`${ANSI.fg.yellow}Deploying to GitHub Pages...${ANSI.reset}`);
console.log(`Domain: ${ANSI.fg.cyan + DOMAIN + ANSI.reset}`);
console.log(`Commit message: ${ANSI.fg.cyan + COMMIT_MESSAGE + ANSI.reset}`);
console.log(`Repository: ${ANSI.fg.cyan + REPO_URL + ANSI.reset}\n`);
void ghpages.publish(BUILD_DIR, {
repo: REPO_URL,
cname: DOMAIN,
message: COMMIT_MESSAGE
}, (error) => {
if (error == null)
return;
console.error(error);
console.log(`${ANSI.fg.red}⚠ Failed to deploy${ANSI.reset}`);
process.exit(1);
}).then(() => {
console.log(`${ANSI.fg.green}✓ Successfully deployed to ${ANSI.fg.cyan + BASE_URL + ANSI.reset}`);
});
}
deploy();

View file

@ -1,15 +1,10 @@
import fs from "node:fs";
import { ANSI } from "../config/apps/terminal.config";
import { ANSI } from "../src/config/apps/terminal.config";
import { REPO } from "../src/config/deploy.config";
const API_URL = "https://api.github.com/";
const TREE_PATH = "public/config/tree.json";
// TO DO: split these two variables into their own file
const OWNER = "Prozilla";
const REPO = "ProzillaOS";
const BRANCH = "main";
interface ReponseType {
sha: string;
url: string;
@ -24,7 +19,7 @@ interface ReponseType {
}
function fetchRepositoryTree(callback: (tree: string) => void) {
const treeUrl = `${API_URL}repos/${OWNER}/${REPO}/git/trees/${BRANCH}?recursive=true`;
const treeUrl = `${API_URL}repos/${REPO.owner}/${REPO.name}/git/trees/${REPO.branch}?recursive=true`;
console.log(`${ANSI.fg.yellow}Fetching: ${ANSI.fg.cyan + treeUrl + ANSI.reset}`);
@ -62,4 +57,6 @@ try {
});
} catch (error) {
console.error(error);
console.log(`${ANSI.fg.red}⚠ Failed to fetch repository${ANSI.reset}`);
process.exit(1);
}

View file

@ -1,16 +1,14 @@
import fs from "node:fs";
import { APP_DESCRIPTIONS, APP_NAMES, APPS } from "../config/apps.config";
import { ANSI } from "../config/apps/terminal.config";
import { BASE_URL, DOMAIN, NAME, TAG_LINE } from "../config/branding.config";
import { WALLPAPERS } from "../config/desktop.config";
const BUILD_DIR = "dist";
import { APP_DESCRIPTIONS, APP_NAMES, APPS } from "../src/config/apps.config";
import { ANSI } from "../src/config/apps/terminal.config";
import { NAME, TAG_LINE } from "../src/config/branding.config";
import { WALLPAPERS } from "../src/config/desktop.config";
import { BASE_URL, BUILD_DIR } from "../src/config/deploy.config";
const PATHS = {
sitemapXml: BUILD_DIR + "/sitemap.xml",
robotsTxt: BUILD_DIR + "/robots.txt",
indexHtml: BUILD_DIR + "/index.html",
cname: BUILD_DIR + "/CNAME",
};
function generateSitemapXml() {
@ -55,10 +53,6 @@ Disallow:
Sitemap: ${sitemapUrl}`;
}
function generateCname() {
return DOMAIN;
}
function generateTemplate(html: string) {
const baseUrlRegex = /(?<=")https?:\/\/[a-z0-9-]+(\.)([a-zA-Z0-9-]+(\.))*[a-z]{2,3}\/(?=.*")/gi;
html = html.replaceAll(baseUrlRegex, BASE_URL);
@ -120,34 +114,39 @@ function generateAppPages(template: string) {
}
}
try {
console.log(`${ANSI.fg.yellow}Staging build...${ANSI.reset}`);
function stage() {
try {
console.log(`${ANSI.fg.yellow}Staging build...${ANSI.reset}`);
const files: [string, () => string][] = [
[PATHS.sitemapXml, generateSitemapXml],
[PATHS.robotsTxt, generateRobotsTxt],
];
files.forEach(([path, generateContent]) => {
const directory = path.substring(0, path.lastIndexOf("/"));
if (directory != "" && !fs.existsSync(directory)){
fs.mkdirSync(directory, { recursive: true });
}
fs.writeFileSync(path, generateContent(), { flag: "w+" });
console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`);
});
console.log(`\n${ANSI.fg.yellow}Generating pages...${ANSI.reset}`);
const html = fs.readFileSync(PATHS.indexHtml, "utf-8");
const template = generateTemplate(html);
generate404Page(template);
generateAppPages(template);
console.log(`\n${ANSI.fg.green}✓ Staging complete${ANSI.reset}`);
} catch (error) {
console.error(error);
console.log(`${ANSI.fg.red}⚠ Staging failed${ANSI.reset}`);
process.exit(1);
}
}
const files: [string, () => string][] = [
[PATHS.sitemapXml, generateSitemapXml],
[PATHS.robotsTxt, generateRobotsTxt],
[PATHS.cname, generateCname],
];
files.forEach(([path, generateContent]) => {
const directory = path.substring(0, path.lastIndexOf("/"));
if (directory != "" && !fs.existsSync(directory)){
fs.mkdirSync(directory, { recursive: true });
}
fs.writeFileSync(path, generateContent(), { flag: "w+" });
console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`);
});
console.log(`\n${ANSI.fg.yellow}Generating pages...${ANSI.reset}`);
const html = fs.readFileSync(PATHS.indexHtml, "utf-8");
const template = generateTemplate(html);
generate404Page(template);
generateAppPages(template);
console.log(`\n${ANSI.fg.green}✓ Staging complete${ANSI.reset}`);
} catch (error) {
console.error(error);
}
stage();

View file

@ -72,7 +72,6 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
actionId,
children: iterateOverChildren((child.props as ActionProps).children),
onTrigger: onTriggerOverride
} as ActionProps);
});

View file

@ -9,15 +9,13 @@ import { useHistory } from "../../../hooks/_utils/history";
import { WindowProps } from "../../windows/WindowView";
interface BrowserProps extends WindowProps {
startUrl?: string;
url?: string;
}
export function Browser({ startUrl, focus }: BrowserProps) {
const initialUrl = startUrl ?? HOME_URL;
const [url, setUrl] = useState<string>(initialUrl);
const [input, setInput] = useState(initialUrl);
const { history, pushState, stateIndex, undo, redo, undoAvailable, redoAvailable } = useHistory(initialUrl);
export function Browser({ url: startUrl = HOME_URL, focus }: BrowserProps) {
const [url, setUrl] = useState<string>(startUrl);
const [input, setInput] = useState(startUrl);
const { history, pushState, stateIndex, undo, redo, undoAvailable, redoAvailable } = useHistory(startUrl);
const ref = useRef<HTMLIFrameElement>(null);
useEffect(() => {

View file

@ -31,14 +31,14 @@ import { useAlert } from "../../../hooks/modals/alert";
import { VirtualRoot } from "../../../features/virtual-drive/root/virtualRoot";
interface FileExplorerProps extends WindowProps {
startPath?: string;
path?: string;
selectorMode?: number;
Footer: FC;
onSelectionChange: (params: OnSelectionChangeParams) => void;
onSelectionFinish: Function;
}
export function FileExplorer({ startPath, selectorMode, Footer, onSelectionChange, onSelectionFinish }: FileExplorerProps) {
export function FileExplorer({ path: startPath, selectorMode, Footer, onSelectionChange, onSelectionFinish }: FileExplorerProps) {
const isSelector = (Footer != null && selectorMode != null && selectorMode !== SELECTOR_MODE.NONE);
const virtualRoot = useVirtualRoot();

View file

@ -21,7 +21,7 @@ export function MediaViewer({ file, close, setTitle }: MediaViewerProps) {
if (file == null) {
setTimeout(() => {
windowsManager.open(APPS.FILE_EXPLORER, { startPath: "~/Pictures" });
windowsManager.open(APPS.FILE_EXPLORER, { path: "~/Pictures" });
close();
}, 10);
return;

View file

@ -21,12 +21,12 @@ export function AboutSettings() {
event.preventDefault();
windowsManager.open("text-editor", {
mode: "view",
file: virtualRoot.navigate("~/Documents/info.md"),
file: virtualRoot.navigate("~/Documents/Info.md"),
size: new Vector2(575, 675),
});
}}
>
Open info.md
Open Info.md
</Button>
<Button
className={`${styles.Button} ${utilStyles.TextBold}`}

View file

@ -16,7 +16,7 @@ import { APP_NAMES } from "../../../config/apps.config";
import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext";
interface TerminalProps extends WindowProps {
startPath: string;
path: string;
input: string;
}
@ -27,7 +27,7 @@ interface HistoryEntry {
clear?: boolean;
}
export function Terminal({ startPath, input, setTitle, close: exit, active, focus }: TerminalProps) {
export function Terminal({ path: startPath, input, setTitle, close: exit, active, focus }: TerminalProps) {
const [inputKey, setInputKey] = useState(0);
const [inputValue, setInputValue] = useState(input ?? "");
const [history, setHistory] = useState<HistoryEntry[]>([{
@ -328,7 +328,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active, focu
};
const onMouseDown = (event: MouseEvent) => {
focus(event);
focus?.(event);
if (event.button === 2) {
event.preventDefault();

View file

@ -27,35 +27,145 @@
overflow: auto;
}
.View img {
.View {
z-index: 2;
}
.MarkdownImage {
max-width: 100%;
object-fit: contain;
border-radius: var(--border-radius-0);
margin-right: 0.5rem;
margin: 1rem 0.5rem 1rem 0;
}
.View blockquote {
margin-left: 0;
padding: 0.5rem;
padding-left: 1.5rem;
border-left: 1rem solid var(--background-color-0);
background-color: var(--background-color-1);
border-radius: var(--border-radius-1);
}
.View blockquote > p {
width: fit-content;
.MarkdownBlockquote {
display: flex;
gap: 1rem;
flex-direction: column;
margin: 0;
}
.AlertContainer {
--color-0: var(--color-1);
--color-1: var(--background-color-0);
--color-2: var(--background-color-1);
margin: 0;
padding: 1rem 1.5rem;
border-left: 1rem solid var(--color-1);
background-color: var(--color-2);
border-radius: var(--border-radius-1);
}
.Alert {
display: inline-flex;
gap: 0.5rem;
justify-content: flex-start;
align-items: center;
margin-bottom: 0.5rem;
color: var(--color-0);
font-weight: 600;
}
.Alert > svg * {
fill: var(--color-0);
}
.ImportantAlert {
--color-0: var(--purple-0);
--color-1: var(--purple-1);
--color-2: var(--purple-2);
}
.NoteAlert {
--color-0: var(--blue-0);
--color-1: var(--blue-1);
--color-2: var(--blue-2);
}
.TipAlert {
--color-0: var(--green-0);
--color-1: var(--green-1);
--color-2: var(--green-2);
}
.WarningAlert {
--color-0: var(--yellow-0);
--color-1: var(--yellow-1);
--color-2: var(--yellow-2);
}
.CautionAlert {
--color-0: var(--red-0);
--color-1: var(--red-1);
--color-2: var(--red-2);
}
.View code {
position: relative;
font-family: var(--mono-font-family);
border-radius: var(--border-radius-1);
padding: 0.125rem 0.25rem;
font-size: 0.875rem;
letter-spacing: -0.01rem;
}
.View code::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--background-color-1);
padding: 0.25rem;
border-radius: inherit;
z-index: -1;
}
.MarkdownLink {
position: relative;
display: inline-block;
color: var(--light-blue-0);
transition: color 200ms ease-in-out;
}
.MarkdownLink:hover, .MarkdownLink:focus-visible {
color: var(--light-blue-1);
}
.MarkdownLink > * {
color: inherit;
}
div > .MarkdownLink {
margin: 1rem 0;
}
.MarkdownLink > .MarkdownImage {
margin: 0;
}
.View h1,
.View h2,
.View h3,
.View h4,
.View h5,
.View h6 {
margin-top: 1.75rem;
margin-bottom: -0.25rem;
}
.View p {
line-height: 1.375rem;
}
.View li > ul {
margin: 1rem 0;
}
.View table {
margin: 1rem 0;
border-collapse: collapse;
}

View file

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { ReactNode, useEffect, useRef, useState } from "react";
import styles from "./TextEditor.module.css";
import { HeaderMenu } from "../_utils/header-menu/HeaderMenu";
import Markdown from "markdown-to-jsx";
@ -20,10 +20,13 @@ 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";
const OVERRIDES = {
a: MarkdownLink,
img: MarkdownImage,
blockquote: MarkdownBlockquote,
};
export interface MarkdownProps {
@ -32,17 +35,21 @@ export interface MarkdownProps {
currentFile: VirtualFile;
app: App;
windowsManager: WindowsManager;
children?: ReactNode;
}
interface TextEditorProps extends WindowProps {
file?: VirtualFile;
mode?: "view" | "edit";
path?: string;
}
export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modalsManager }: TextEditorProps) {
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);
const [currentMode, setCurrentMode] = useState(mode);
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);
@ -67,6 +74,9 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
const iconUrl = currentFile.getIconUrl();
if (iconUrl)
setIconUrl(iconUrl);
if (newContent?.trim() === "")
setCurrentMode("edit");
} else {
setIconUrl(AppsManager.getAppIconUrl(app.id));
}
@ -92,6 +102,18 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
setTitle(`${label} ${TITLE_SEPARATOR} ${app.name}`);
}, [currentFile, setTitle, unsavedChanges, currentMode, app.name]);
useEffect(() => {
if (currentFile == null && path != null) {
const newFile = virtualRoot.navigate(path);
if (newFile == null || !newFile.isFile())
return;
setCurrentFile(newFile as VirtualFile);
path = null;
}
}, [path, currentFile]);
const newText = () => {
setCurrentFile(null);
setCurrentMode("edit");

View file

@ -0,0 +1,86 @@
import { MarkdownProps } from "../TextEditor";
import { sanitizeProps } from "../../../../features/apps/text-editor/_utils/sanitizeProps";
import { Attributes, Children, cloneElement, DetailedHTMLProps, isValidElement, ReactNode } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationCircle, faExclamationTriangle, faInfoCircle, faLightbulb } from "@fortawesome/free-solid-svg-icons";
import styles from "../TextEditor.module.css";
type Props = Partial<unknown> & Attributes & { children: ReactNode, className?: string };
export function MarkdownBlockquote({ children, ...props }: MarkdownProps) {
sanitizeProps(props);
const formatContent = (children: ReactNode): [ReactNode, string] => {
if (!children)
return [null, null];
let alertType: string = null;
children = Children.map(children, (child) => {
if (isValidElement(child)) {
const { children, ...props } = child.props as Props;
const [newChildren, childAlertType] = formatContent(children);
if (childAlertType != null)
props.className = `${styles.AlertContainer} ${styles[childAlertType + "Alert"]}`;
return cloneElement(child, {
...props,
children: newChildren,
} as Props);
} else if (typeof child !== "string") {
return child;
}
switch (child) {
case "[!IMPORTANT]":
child = <span className={styles.Alert}>
<FontAwesomeIcon icon={faExclamationCircle}/>
Important
</span>;
alertType = "Important";
break;
case "[!NOTE]":
child = <span className={styles.Alert}>
<FontAwesomeIcon icon={faInfoCircle}/>
Note
</span>;
alertType = "Note";
break;
case "[!TIP]":
child = <span className={styles.Alert}>
<FontAwesomeIcon icon={faLightbulb}/>
Tip
</span>;
alertType = "Tip";
break;
case "[!WARNING]":
child = <span className={styles.Alert}>
<FontAwesomeIcon icon={faExclamationTriangle}/>
Warning
</span>;
alertType = "Warning";
break;
case "[!CAUTION]":
child = <span className={styles.Alert}>
<FontAwesomeIcon icon={faExclamationTriangle}/>
Caution
</span>;
alertType = "Caution";
break;
}
return child;
});
return [children, alertType];
};
return <blockquote
{...(props as DetailedHTMLProps<React.BlockquoteHTMLAttributes<HTMLQuoteElement>, HTMLQuoteElement>)}
className={styles.MarkdownBlockquote}
>
{formatContent(children)[0]}
</blockquote>;
}

View file

@ -1,6 +1,7 @@
import { useMemo } from "react";
import { MarkdownProps } from "../TextEditor";
import { sanitizeProps } from "../../../../features/apps/text-editor/_utils/sanitizeProps";
import styles from "../TextEditor.module.css";
interface MarkdownImageProps extends MarkdownProps {
src: string;
@ -19,8 +20,9 @@ export function MarkdownImage({ alt, src, ...props }: MarkdownImageProps) {
sanitizeProps(props as MarkdownProps);
return <img
alt={alt}
{...props}
src={source}
className={styles.MarkdownImage}
alt={alt}
/>;
}

View file

@ -13,6 +13,7 @@ import { MarkdownProps } from "../TextEditor";
import { sanitizeProps } from "../../../../features/apps/text-editor/_utils/sanitizeProps";
import { copyToClipboard, removeUrlProtocol } from "../../../../features/_utils/browser.utils";
import { TextDisplay } from "../../../actions/actions/TextDisplay";
import styles from "../TextEditor.module.css";
interface MarkdownLinkProps extends MarkdownProps {
href: string;
@ -31,7 +32,7 @@ export function MarkdownLink({ href, children, windowsManager, currentFile, setC
if (target.isFile()) {
setCurrentFile(target);
} else {
windowsManager.open(APPS.FILE_EXPLORER, { startPath: target.path });
windowsManager.open(APPS.FILE_EXPLORER, { path: target.path });
}
} else {
openWindowedModal({
@ -73,12 +74,13 @@ export function MarkdownLink({ href, children, windowsManager, currentFile, setC
sanitizeProps(props as MarkdownProps);
return <a
{...props}
className={styles.MarkdownLink}
target="_blank"
rel="noreferrer"
href={href}
onContextMenu={onContextMenu}
onClick={onClick as unknown as MouseEventHandler}
{...props}
title={title}
>
{children}

View file

@ -91,15 +91,15 @@ export const Desktop = memo(() => {
}}/>
<Divider/>
<ClickAction label={`Open in ${APP_NAMES.FILE_EXPLORER}`} icon={APP_ICONS.FILE_EXPLORER} onTrigger={() => {
windowsManager.open(APPS.FILE_EXPLORER, { startPath: directory.path });
windowsManager.open(APPS.FILE_EXPLORER, { path: directory.path });
}}/>
<ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={APP_ICONS.TERMINAL} onTrigger={() => {
windowsManager.open(APPS.TERMINAL, { startPath: directory.path });
windowsManager.open(APPS.TERMINAL, { path: directory.path });
}}/>
<Divider/>
<ClickAction label={"Share"} icon={ModalsManager.getModalIconUrl("share")} onTrigger={() => {
openWindowedModal({
size: new Vector2(350, 400),
size: new Vector2(350, 350),
Modal: (props) => <Share {...props}/>
});
}}/>
@ -124,7 +124,7 @@ export const Desktop = memo(() => {
folder.open(windowsManager);
}}/>
<ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={faTerminal} onTrigger={(event, folder: VirtualFolder) => {
windowsManager.open(APPS.TERMINAL, { startPath: folder.path });
windowsManager.open(APPS.TERMINAL, { path: folder.path });
}}/>
<ClickAction label={`Reveal in ${APP_NAMES.FILE_EXPLORER}`} icon={faFolder} onTrigger={(event, folder: VirtualFolder) => {
folder.parent.open(windowsManager);
@ -182,7 +182,7 @@ export const Desktop = memo(() => {
(event as Event).preventDefault();
const options: Record<string, unknown> = {};
if (file.name === "info.md")
if (file.name === "Info.md")
options.size = new Vector2(575, 675);
if (file.extension === "md")
options.mode = "view";
@ -190,7 +190,7 @@ export const Desktop = memo(() => {
windowsManager.openFile(file, options);
}}
onOpenFolder={(event, { linkedPath, path }: VirtualFolderLink & VirtualFolder) => {
windowsManager.open(APPS.FILE_EXPLORER, { startPath: linkedPath ?? path });
windowsManager.open(APPS.FILE_EXPLORER, { path: linkedPath ?? path });
}}
onContextMenuFile={onContextMenuFile}
onContextMenuFolder={onContextMenuFolder}

View file

@ -4,36 +4,55 @@
justify-content: space-between;
width: 100%;
height: 100%;
padding: 1rem;
overflow-y: auto;
padding: 1rem 0;
overflow: hidden;
pointer-events: none;
}
.Share > div {
.Top, .Bottom {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
overflow: hidden;
}
.Share > div:last-child {
gap: 0.5rem;
flex-direction: row;
justify-content: space-between;
align-items: center;
.Share > .Top {
flex-grow: 1;
pointer-events: none;
}
.Title {
margin-top: 0;
margin-left: 1rem;
font-size: 1.5rem;
pointer-events: auto;
}
.Title:first-child {
margin-top: 0.5rem;
.FormContainer {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none;
}
.Form {
--margin: 1rem;
position: absolute;
display: flex;
gap: 0.5rem;
flex-direction: column;
width: 100%;
top: 0;
left: 0;
width: calc(100% - var(--margin) * 2);
height: 100%;
margin: 0 var(--margin);
overflow-x: hidden;
overflow-y: auto;
pointer-events: auto;
z-index: -1;
}
.Label {
@ -43,18 +62,23 @@
gap: var(--gap);
align-items: center;
width: 100%;
height: 1.75rem;
min-height: 1.75rem;
z-index: 1;
}
.Label > p {
width: calc(40% - var(--gap));
min-width: 40%;
margin: 0;
text-align: start;
}
.Input {
width: auto;
max-width: calc(60% - var(--gap));
padding: 0.5rem 1rem;
padding: 0.25rem 0.5rem;
height: 100%;
color: var(--text-color);
background-color: var(--background-color-1);
border: none;
@ -73,8 +97,8 @@ select.Input > * {
font-size: inherit;
}
.Input:disabled + div {
opacity: 0.5;
.Input:disabled ~ div, .Input:disabled ~ p {
opacity: 0.25;
}
.Input[type=checkbox] {
@ -85,8 +109,9 @@ select.Input > * {
display: flex;
justify-content: center;
align-items: center;
width: 1.25rem;
height: 1.25rem;
width: auto;
height: 87.5%;
aspect-ratio: 1;
cursor: pointer;
}
@ -94,16 +119,28 @@ select.Input > * {
width: 100%;
height: 100%;
object-fit: contain;
fill: var(--foreground-color-0);
fill: var(--background-color-0);
}
.Checkbox > svg > * {
fill: inherit;
}
.Share > .Bottom {
gap: 0.5rem;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin: 0.75rem 1rem 0;
pointer-events: auto;
}
.Url {
margin: 0;
text-align: start;
word-break: break-all;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.Button {

View file

@ -1,4 +1,4 @@
import { ChangeEventHandler, useEffect, useState } from "react";
import { ChangeEventHandler, ReactEventHandler, UIEventHandler, useEffect, useRef, useState } from "react";
import ModalsManager from "../../../features/modals/modalsManager";
import { WindowedModal } from "../_utils/WindowedModal";
import styles from "./Share.module.css";
@ -12,26 +12,37 @@ import { faSquare } from "@fortawesome/free-regular-svg-icons";
import { faSquareCheck } from "@fortawesome/free-solid-svg-icons";
import { useAlert } from "../../../hooks/modals/alert";
import { ModalProps } from "../ModalView";
import { useScrollWithShadow } from "../../../hooks/_utils/scrollWithShadows";
const APP_OPTIONS: Record<string, Record<string, string>[]> = {
const APP_OPTIONS: Record<string, { label: string, name: string }[]> = {
"terminal": [
{
label: "Command",
name: "input"
},
{
label: "Path",
name: "path"
}
],
"browser": [
{
label: "Website",
name: "startUrl"
name: "url"
},
],
"file-explorer": [
{
label: "Path",
name: "startPath"
name: "path"
}
]
],
"text-editor": [
{
label: "Path",
name: "path"
}
],
};
export function Share({ modal, params, ...props }: ModalProps) {
@ -41,6 +52,18 @@ export function Share({ modal, params, ...props }: ModalProps) {
const [options, setOptions] = useState({});
const [url, setUrl] = useState<string | null>(null);
const { alert } = useAlert();
const formRef = useRef(null);
const { boxShadow, onUpdate } = useScrollWithShadow({
ref: formRef,
horizontal: false,
dynamicOffsetFactor: 1,
shadow: {
offset: 20,
blurRadius: 10,
spreadRadius: -10,
color: { a: 25 },
}
});
useEffect(() => {
setUrl(generateUrl({
@ -51,6 +74,10 @@ export function Share({ modal, params, ...props }: ModalProps) {
}));
}, [appId, fullscreen, standalone, options]);
useEffect(() => {
onUpdate({ target: formRef.current as HTMLElement });
}, [appId]);
const onAppIdChange = (event: Event) => {
const newAppId = (event.target as HTMLInputElement).value;
@ -58,17 +85,6 @@ export function Share({ modal, params, ...props }: ModalProps) {
return;
setAppId(newAppId);
const appOptions = APP_OPTIONS[appId];
if (!appOptions) {
setOptions({});
} else {
const newOptions = {};
appOptions.forEach(({ key }) => {
newOptions[key] = "";
});
setOptions(newOptions);
}
};
const onFullscreenChange = (event: Event) => {
@ -94,59 +110,66 @@ export function Share({ modal, params, ...props }: ModalProps) {
title: "Share",
iconUrl: ModalsManager.getModalIconUrl("share"),
}} {...props}>
<div>
<div className={styles.Top}>
<h1 className={styles.Title}>Share options</h1>
<form className={styles.Form}>
<label className={styles.Label}>
<p>App:</p>
<select className={styles.Input} name="app" value={appId} onChange={onAppIdChange as unknown as ChangeEventHandler}>
<option value={""}>(None)</option>
{AppsManager.APPS.map(({ name, id }) =>
<option key={id} value={id}>{name}</option>
)}
</select>
</label>
{appId !== "" ? <label className={styles.Label}>
<p>Standalone:</p>
<input
className={styles.Input}
name="standalone"
type="checkbox"
checked={standalone}
value={standalone.toString()}
onChange={onStandaloneChange as unknown as ChangeEventHandler}
/>
<div className={styles.Checkbox}>
{standalone
? <FontAwesomeIcon icon={faSquareCheck}/>
: <FontAwesomeIcon icon={faSquare}/>
}
</div>
</label> : null}
{appId !== "" ? <label className={styles.Label}>
<p>Fullscreen:</p>
<input
className={styles.Input}
name="fullscreen"
type="checkbox"
checked={fullscreen}
disabled={standalone}
value={fullscreen.toString()}
onChange={onFullscreenChange as unknown as ChangeEventHandler}
/>
<div className={styles.Checkbox}>
{fullscreen
? <FontAwesomeIcon icon={faSquareCheck}/>
: <FontAwesomeIcon icon={faSquare}/>
}
</div>
</label> : null}
{APP_OPTIONS[appId]?.map(({ label, name }) =>
<Option key={name} name={name} label={label} setOption={setOption}/>
)}
</form>
<div className={styles.FormContainer} style={{ boxShadow }}>
<form
className={styles.Form}
onScroll={onUpdate as unknown as UIEventHandler}
onResize={onUpdate as unknown as ReactEventHandler}
ref={formRef}
>
<label className={styles.Label}>
<p>App:</p>
<select className={styles.Input} name="app" value={appId} onChange={onAppIdChange as unknown as ChangeEventHandler}>
<option value={""}>(None)</option>
{AppsManager.APPS.map(({ name, id }) =>
<option key={id} value={id}>{name}</option>
)}
</select>
</label>
{appId !== "" ? <label className={styles.Label}>
<p>Standalone:</p>
<input
className={styles.Input}
name="standalone"
type="checkbox"
checked={standalone}
value={standalone.toString()}
onChange={onStandaloneChange as unknown as ChangeEventHandler}
/>
<div className={styles.Checkbox}>
{standalone
? <FontAwesomeIcon icon={faSquareCheck}/>
: <FontAwesomeIcon icon={faSquare}/>
}
</div>
</label> : null}
{appId !== "" ? <label className={styles.Label}>
<input
className={styles.Input}
name="fullscreen"
type="checkbox"
checked={fullscreen}
disabled={standalone}
value={fullscreen.toString()}
onChange={onFullscreenChange as unknown as ChangeEventHandler}
/>
<p>Fullscreen:</p>
<div className={styles.Checkbox}>
{fullscreen
? <FontAwesomeIcon icon={faSquareCheck}/>
: <FontAwesomeIcon icon={faSquare}/>
}
</div>
</label> : null}
{APP_OPTIONS[appId]?.map(({ label, name }) =>
<Option key={name} name={name} label={label} setOption={setOption}/>
)}
</form>
</div>
</div>
<div>
<div className={styles.Bottom}>
<p className={`${styles.Url} ${utilStyles.TextLight}`}>{url}</p>
<Button
className={`${styles.Button} ${utilStyles.TextBold}`}
@ -166,7 +189,7 @@ export function Share({ modal, params, ...props }: ModalProps) {
});
}}
>
Copy URL
Copy
</Button>
</div>
</WindowedModal>;

View file

@ -77,7 +77,7 @@ export function HomeMenu({ active, setActive, search }: HomeMenuProps) {
setActive(false);
windowsManager.open("text-editor", {
mode: "view",
file: virtualRoot.navigate("~/Documents/info.md"),
file: virtualRoot.navigate("~/Documents/Info.md"),
size: new Vector2(575, 675),
});
}}>
@ -85,13 +85,13 @@ export function HomeMenu({ active, setActive, search }: HomeMenuProps) {
</button>
<button title="Images" tabIndex={tabIndex} onClick={() => {
setActive(false);
windowsManager.open(APPS.FILE_EXPLORER, { startPath: "~/Pictures" });
windowsManager.open(APPS.FILE_EXPLORER, { path: "~/Pictures" });
}}>
<FontAwesomeIcon icon={faImage}/>
</button>
<button title="Documents" tabIndex={tabIndex} onClick={() => {
setActive(false);
windowsManager.open(APPS.FILE_EXPLORER, { startPath: "~/Documents" }); }
windowsManager.open(APPS.FILE_EXPLORER, { path: "~/Documents" }); }
}>
<FontAwesomeIcon icon={faFileLines}/>
</button>

View file

@ -69,7 +69,7 @@ export const WindowView: FC<WindowProps> = memo(({ id, app, size, position, onIn
openWindowedModal({
appId: app.id,
fullscreen: maximized,
size: new Vector2(350, 400),
size: new Vector2(350, 350),
Modal: (props) => <Share {...props}/>
});
}}/>

View file

@ -2,8 +2,6 @@ import { ANSI } from "./apps/terminal.config";
export const NAME = "ProzillaOS";
export const TAG_LINE = "Web-based Operating System";
export const DOMAIN = "os.prozilla.dev";
export const BASE_URL = `https://${DOMAIN}/`;
export const ASCII_LOGO = `
:.

View file

@ -0,0 +1,9 @@
export const BUILD_DIR = "dist";
export const DOMAIN = "os.prozilla.dev";
export const BASE_URL = `https://${DOMAIN}/`;
export const REPO = { owner: "Prozilla", name: "ProzillaOS", branch: "main" };
export const REPO_URL = `https://github.com/${REPO.owner}/${REPO.name}`;
export const COMMIT_MESSAGE = "Deployed build to GitHub Pages";

View file

@ -289,7 +289,7 @@ export class VirtualFolder extends VirtualBase {
* Opens this folder in file explorer
*/
open(windowsManager: WindowsManager) {
return windowsManager.open(APPS.FILE_EXPLORER, { startPath: this.path });
return windowsManager.open(APPS.FILE_EXPLORER, { path: this.path });
}
/**

View file

@ -37,6 +37,8 @@ export function loadDefaultData(virtualRoot: VirtualRoot) {
}
}).createFile("ProzillaOS", "png", (file) => {
file.setSource("/assets/banner-logo-title.png");
}).createFile("Icon", "svg", (file) => {
file.setSource("/icon.svg");
}).createFolder("Crumbling City", (folder) => {
folder.createFile("Japan", "png", (file) => {
file.setSource("https://daisygames.org/media/Games/Crumbling%20City/CrumblingCityRelease.png");
@ -52,22 +54,22 @@ export function loadDefaultData(virtualRoot: VirtualRoot) {
folder.setIconUrl(AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "folder-text"));
folder.createFile("text", "txt", (file) => {
file.setContent("Hello world!");
}).createFile("info", "md", (file) => {
}).createFile("Info", "md", (file) => {
file.setProtected(true)
.setSource("/documents/info.md")
.setSource("/documents/Info.md")
.setIconUrl(AppsManager.getAppIconUrl(APPS.FILE_EXPLORER, "file-info"));
linkedPaths.info = file.path;
}).createFile("links", "md", (file) => {
}).createFile("Prozilla", "md", (file) => {
file.setProtected(true)
.setSource("/documents/links.md");
.setSource("/documents/Prozilla.md");
linkedPaths.links = file.path;
});
linkedPaths.documents = folder.path;
})
.createFolder("Desktop", (folder) => {
folder.createFileLink("info.md", (fileLink: VirtualFileLink) => {
folder.createFileLink("Info.md", (fileLink: VirtualFileLink) => {
fileLink.setLinkedPath(linkedPaths.info);
}).createFileLink("links.md", (fileLink: VirtualFileLink) => {
}).createFileLink("Prozilla.md", (fileLink: VirtualFileLink) => {
fileLink.setLinkedPath(linkedPaths.links);
}).createFolderLink("Pictures", (folderLink: VirtualFolderLink) => {
folderLink.setLinkedPath(linkedPaths.images);

View file

@ -26,7 +26,7 @@ interface UseScrollWithShadowParams {
export function useScrollWithShadow(params: UseScrollWithShadowParams): {
boxShadow: string;
onUpdate: (event: Event) => void;
onUpdate: (event: Event | { target: HTMLElement }) => void;
} {
const [initiated, setInitiated] = useState(false);
const [scrollStart, setScrollStart] = useState(0);
@ -67,7 +67,7 @@ export function useScrollWithShadow(params: UseScrollWithShadowParams): {
setClientLength(horizontal ? element.clientWidth : element.clientHeight);
}, [horizontal]);
const onUpdate = (event: Event) => {
const onUpdate = (event: Event | { target: HTMLElement }) => {
updateValues(event.target as HTMLElement);
};

View file

@ -20,4 +20,10 @@ root.render(<React.StrictMode>
const asciiLogoWidth = ASCII_LOGO.split("\n")[1].length;
const welcomeMessage = `Welcome to ${NAME}`;
const space = "\n\n" + " ".repeat(Math.ceil((asciiLogoWidth - welcomeMessage.length) / 2));
console.info(ASCII_LOGO + space + welcomeMessage);
console.info(ASCII_LOGO + space + welcomeMessage);
// Remove trailing slash
window.onload = () => {
if (window.location.pathname.endsWith("/"))
window.history.pushState({}, null, window.location.pathname.replace(/\/+$/, ""));
};

View file

@ -28,13 +28,14 @@
},
"include": [
"src/**/*",
"vite.config.ts"
"vite.config.ts",
"scripts/**/*",
],
"exclude": [
"./node_modules",
"./build",
"./dist",
"./.vscode"
"./.vscode",
"./.github"
],
"ts-node": {
"experimentalSpecifierResolution": "node",

View file

@ -1,8 +1,16 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import { BUILD_DIR } from "./src/config/deploy.config";
// https://vitejs.dev/config/
export default defineConfig({
base: "/",
plugins: [react()]
plugins: [react()],
build: {
outDir: BUILD_DIR
},
server: {
port: 3000,
host: true
}
});