Added z-index helpers + updated docs

This commit is contained in:
Prozilla 2023-12-30 11:11:55 +01:00
parent 01c5d0be93
commit af5a1526fa
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
16 changed files with 234 additions and 31 deletions

View file

@ -5,7 +5,9 @@
## Table of Contents
- [Features](features/README.md)
- [Configuration](configuration/README.md)
- [Design](design/README.md)
- [FAQ](faq/README.md)
- [Roadmap](roadmap/README.md)
- [Contributing](contributing/README.md)
@ -28,7 +30,7 @@
#### Deploying to GitHub Pages
1. Open [deploy.sh](../deploy.sh) and change `DOMAIN` to your domain, `COMMIT_MESSAGE` to your commit message and `REPO_URL` to the URL of your repo. Make sure you don't add or remove any quotes when editing these values.
1. Open [deploy.sh](../deploy.sh) and change `DOMAIN` to your domain, `COMMIT_MESSAGE` to your commit message and `REPO_URL` to the URL of your repo. Make sure you don't accidentally add or remove any quotes when editing these values.
2. Run `npm run deploy` - This will create a new build by running the redeploy script, add a CNAME pointing to your domain and then push the build folder to a branch called `gh-pages`.
3. Configure your GitHub Pages to deploy the `gh-pages` branch to your domain.
@ -36,6 +38,10 @@ Once the initial setup is complete, you can simply run `npm run deploy` and your
> Note: When the deployment process is complete, you won't see the changes reflected on your website until a few moments later. This is because GitHub Pages needs to run its own build step before the website updates. You can view details about this process in the `Actions` tab on GitHub.
### Configuration
See [docs/configuration](configuration/README.md) for more information.
### Structure
- [src](../src) directory

View file

@ -0,0 +1,11 @@
[← Back](../README.md)
# Configuration
Prozilla OS can be configured in numerous ways. The most important one being via the application itself, by going to the Settings app or directly changing the config files in the `~/.config` folder.
As a developer, there are also the following ways to configure Prozilla OS:
- `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 Prozilla oS.
- `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.

11
docs/faq/README.md Normal file
View file

@ -0,0 +1,11 @@
[← Back](../README.md)
# FAQ
### Q: What's the purpose of Prozilla OS?
A: Obviously Prozilla OS doesn't solve any issues or anything like that. It's just a fun project I started because I wanted to refine my web development and design skills, as well as my React.js proficiency. At the same time, I also wanted to create an interesting and innovative web application and share it with the community via open source.
### Q: What tools did you use to make Prozilla OS?
A: I used Visual Studio Code for coding, Figma for designing, Notion for project management, GitHub for source control. I didn't use any libraries for the main parts of the OS. The libraries I used for specific purposes like certain applications or utilities, can be found in the `package.json` file. I got the icons from Font Awesome and the fonts from Google Fonts.

View file

@ -17,4 +17,5 @@ To see the status and to-do's of each feature, check the [task board](https://pr
- [Storage](storage/README.md)
- [Taskbar](taskbar/README.md)
- [Virtual Drive](virtual-drive//README.md)
- [Windows](windows/README.md)
- [Windows](windows/README.md)
- [Z-index](z-index/README.md)

View file

@ -0,0 +1,5 @@
[← Back](../README.md)
# Z-index
A collection of helper classes to generate z-indexes that prevent overlapping and make sure everything is displayed in the correct order. One example use case is the windows that all need to be displayed in order and should always be displayed under the taskbar, this is made possible by these helper classes. It works by creating groups that can contain any amount of indexes, each group's offset is defined by the combined length of all preceding groups.

View file

@ -6,18 +6,14 @@ This is a list of planned/in progress features.
For more detailed information, check the [task board](https://prozilla.notion.site/8325fabca1fb4f9885b6d6dfd5aa64c8?v=1a59f7ce50914f5ea711fe6460e52868&pvs=4) on Notion.
## Visual Studio Code clone
### Visual Studio Code clone
A fully functional VSC clone called Code Editor.
## Modals
Modular windows, including alerts, confirmation dialogs and a file selector.
## Calculator App
### Calculator App
Simple calculator that can do basic equations.
## App centre
### App centre
Allows user to download additional apps

View file

@ -7,6 +7,7 @@ import { Desktop } from "./components/desktop/Desktop.jsx";
import { SettingsManagerProvider } from "./hooks/settings/settingsManagerContext.js";
import { ModalsView } from "./components/modals/ModalsView.jsx";
import { useEffect } from "react";
import { ZIndexManagerProvider } from "./hooks/z-index/zIndexManagerContext.js";
function App() {
useEffect(() => {
@ -23,16 +24,18 @@ function App() {
return (
<VirtualRootProvider>
<WindowsManagerProvider>
<SettingsManagerProvider>
<div className={styles.App}>
<Taskbar/>
<WindowsView/>
<ModalsView/>
<Desktop/>
</div>
</SettingsManagerProvider>
</WindowsManagerProvider>
<ZIndexManagerProvider>
<WindowsManagerProvider>
<SettingsManagerProvider>
<div className={styles.App}>
<Taskbar/>
<WindowsView/>
<ModalsView/>
<Desktop/>
</div>
</SettingsManagerProvider>
</WindowsManagerProvider>
</ZIndexManagerProvider>
</VirtualRootProvider>
);
}

View file

@ -120,8 +120,6 @@ export function TextEditor({ file, setTitle, setIconUrl, close, mode, app, modal
};
}
console.log(currentFile);
return (
<div className={styles.Container} style={{ fontSize: zoom }}>
<HeaderMenu

View file

@ -24,6 +24,8 @@ import { TASKBAR_HEIGHT } from "../../config/taskbar.config.js";
import { useSettingsManager } from "../../hooks/settings/settingsManagerContext.js";
import { SettingsManager } from "../../features/settings/settingsManager.js";
import { useWindows } from "../../hooks/windows/windowsContext.js";
import { ZIndexManager } from "../../features/z-index/zIndexManager.js";
import { useZIndex } from "../../hooks/z-index/zIndex.js";
export const Taskbar = memo(() => {
const ref = useRef(null);
@ -49,6 +51,8 @@ export const Taskbar = memo(() => {
</Actions>
});
const [pins, setPins] = useState([]);
const zIndex = useZIndex({ groupIndex: ZIndexManager.GROUPS.TASKBAR, index: 0 });
const modalsZIndex = useZIndex({ groupIndex: ZIndexManager.GROUPS.TASKBAR, index: 1 });
const apps = useMemo(() => AppsManager.APPS.sort((appA, appB) => {
const indexA = pins.indexOf(appA.id);
@ -119,9 +123,9 @@ export const Taskbar = memo(() => {
};
return (<>
<ModalsView modalsManager={modalsManager} modals={modals}/>
<ModalsView modalsManager={modalsManager} modals={modals} style={{ zIndex: modalsZIndex }}/>
<div
style={{ "--taskbar-height": `${TASKBAR_HEIGHT}px` }}
style={{ "--taskbar-height": `${TASKBAR_HEIGHT}px`, zIndex }}
className={styles["Taskbar"]}
data-allow-context-menu={true}
onContextMenu={(event) => {

View file

@ -14,7 +14,6 @@
height: var(--taskbar-height);
background-color: var(--taskbar-color);
backdrop-filter: var(--taskbar-filter);
z-index: 10;
}
.Taskbar button {

View file

@ -18,6 +18,8 @@ import { Actions } from "../actions/Actions.jsx";
import { useScreenDimensions } from "../../hooks/_utils/screen.js";
import { NAME } from "../../config/branding.config.js";
import { setViewportIcon, setViewportTitle } from "../../features/_utils/browser.utils.js";
import { ZIndexManager } from "../../features/z-index/zIndexManager.js";
import { useZIndex } from "../../hooks/z-index/zIndex.js";
/**
* @typedef {object} windowProps
@ -43,7 +45,7 @@ import { setViewportIcon, setViewportTitle } from "../../features/_utils/browser
* @param {boolean} props.minimized
* @param {Function} props.toggleMinimized
*/
export const WindowView = memo(({ id, app, size, position, onInteract, options, active, minimized, toggleMinimized }) => {
export const WindowView = memo(({ id, app, size, position, onInteract, options, active, minimized, toggleMinimized, index }) => {
const windowsManager = useWindowsManager();
const nodeRef = useRef(null);
const [modalsManager, modals] = useModals();
@ -54,6 +56,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
const [screenWidth, screenHeight] = useScreenDimensions();
const [title, setTitle] = useState(app.name);
const [iconUrl, setIconUrl] = useState(AppsManager.getAppIconUrl(app.id));
const zIndex = useZIndex({ groupIndex: ZIndexManager.GROUPS.WINDOWS, index });
const { onContextMenu, ShortcutsListener } = useContextMenu({ modalsManager, Actions: (props) =>
<Actions {...props}>
@ -123,7 +126,7 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
if (minimized)
classNames.push(styles.Minimized);
return (<>
return (<div style={{ zIndex, position: "relative" }}>
<ShortcutsListener/>
<ModalsView modalsManager={modalsManager} modals={modals} style={{ zIndex: 1 }}/>
<Draggable
@ -200,5 +203,5 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
</div>
</div>
</Draggable>
</>);
</div>);
});

View file

@ -48,14 +48,17 @@ export const WindowsView = memo(() => {
// TO DO: prevent windows from being rerendered when order is changed
return (<div>
{sortedWindows.map(({ id, app, size, position, options, minimized }, index) =>
<WindowView
{windows.map((window) => {
const { id, app, size, position, options, minimized } = window;
const index = sortedWindows.indexOf(window);
return <WindowView
key={id}
onInteract={() => { windowsManager.focus(id); }}
active={index === sortedWindows.length - 1}
id={id}
app={app}
size={size}
index={index}
position={position}
options={options}
minimized={minimized}
@ -64,7 +67,7 @@ export const WindowsView = memo(() => {
event.stopPropagation();
windowsManager.setMinimized(id, !minimized);
}}
/>
)}
/>;
})}
</div>);
});

View file

@ -0,0 +1,67 @@
import { ZIndexManager } from "./zIndexManager.js";
export class ZIndexGroup {
/** @type {number} */
length = 0;
/** @type {number} */
offset = 0;
/** @type {number} */
groupIndex = 0;
/** @type {ZIndexManager} */
zIndexManager = null;
/**
* @param {ZIndexManager} zIndexManager
* @param {number} initialLength
*/
constructor(zIndexManager, initialLength) {
this.setManager(zIndexManager);
this.setLength(initialLength ?? 0);
}
/**
* @param {ZIndexManager} zIndexManager
* @returns {ZIndexGroup}
*/
setManager(zIndexManager) {
this.zIndexManager = zIndexManager;
return this;
}
/**
* @param {number} offset
* @returns {ZIndexGroup}
*/
setOffset(offset) {
this.offset = offset;
return this;
}
/**
* @param {number} length
* @returns {ZIndexGroup}
*/
setLength(length) {
if (this.length === length)
return;
this.length = length;
this.zIndexManager.update();
return this;
}
/**
* @param {number} index
* @returns {number}
*/
getIndex(index) {
if (this.length < index + 1) {
this.setLength(index + 1);
}
return this.offset + index;
}
}

View file

@ -0,0 +1,45 @@
import { EventEmitter } from "../_utils/event.utils.js";
import { ZIndexGroup } from "./zIndexGroup.js";
export class ZIndexManager extends EventEmitter {
static GROUPS = {
WINDOWS: 0,
TASKBAR: 1,
MODALS: 2,
};
static EVENT_NAMES = {
INDEX_CHANGE: "indexchange"
};
/** @type {ZIndexGroup[]} */
groups = [];
constructor() {
super();
for (let i = 0; i < Object.keys(ZIndexManager.GROUPS).length; i++) {
const group = new ZIndexGroup(this);
this.groups.push(group);
}
}
update() {
let offset = 0;
let changed = false;
for (let i = 0; i < this.groups.length; i++) {
const group = this.groups[i];
if (group.offset !== offset) {
changed = true;
group.setOffset(offset);
}
offset += group.length;
}
if (changed) {
this.emit(ZIndexManager.EVENT_NAMES.INDEX_CHANGE);
}
}
getIndex(groupIndex, index) {
return this.groups[groupIndex].getIndex(index);
}
}

View file

@ -0,0 +1,27 @@
import { useEffect, useState } from "react";
import { useZIndexManager } from "./zIndexManagerContext.js";
import { ZIndexManager } from "../../features/z-index/zIndexManager.js";
export function useZIndex({ groupIndex, index }) {
const initialIndex = (groupIndex * 10) + index;
const [zIndex, setZIndex] = useState(initialIndex);
const zIndexManager = useZIndexManager();
useEffect(() => {
const updateIndex = () => {
const newZIndex = zIndexManager.getIndex(groupIndex, index);
if (zIndex !== newZIndex)
setZIndex(newZIndex);
};
updateIndex();
zIndexManager.on(ZIndexManager.EVENT_NAMES.INDEX_CHANGE, updateIndex);
return () => {
zIndexManager.off(ZIndexManager.EVENT_NAMES.INDEX_CHANGE, updateIndex);
};
}, [groupIndex, index, zIndex, zIndexManager]);
return zIndex;
}

View file

@ -0,0 +1,24 @@
import { createContext, useContext } from "react";
import { ZIndexManager } from "../../features/z-index/zIndexManager.js";
const ZIndexManagerContext = createContext();
/**
* @param {object} props
* @param {import("react").ElementType} props.children
* @returns {import("react").Provider<any>}
*/
export function ZIndexManagerProvider({ children }) {
const zIndexManager = new ZIndexManager();
return <ZIndexManagerContext.Provider value={zIndexManager}>
{children}
</ZIndexManagerContext.Provider>;
}
/**
* @returns {ZIndexManager}
*/
export function useZIndexManager() {
return useContext(ZIndexManagerContext);
}