Added media viewer

This commit is contained in:
Prozilla 2023-07-29 15:10:48 +02:00
parent a4054f134a
commit fcce477d15
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
13 changed files with 174 additions and 26 deletions

25
package-lock.json generated
View file

@ -25,6 +25,7 @@
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"gh-pages": "^5.0.0"
}
},
@ -638,9 +639,16 @@
}
},
"node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
"version": "7.21.11",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz",
"integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.18.6",
"@babel/helper-create-class-features-plugin": "^7.21.0",
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
},
@ -1898,6 +1906,17 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-env/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",

View file

@ -49,6 +49,7 @@
]
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"gh-pages": "^5.0.0"
}
}

View file

@ -3,11 +3,16 @@ import { useVirtualRoot } from "../../../hooks/virtual-drive/VirtualRootContext.
import styles from "./FileExplorer.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowUp, faCaretLeft, faCaretRight, faCog, faDesktop, faFile, faFileLines, faFolder, faHouse, faImage, faSearch } from "@fortawesome/free-solid-svg-icons";
// eslint-disable-next-line no-unused-vars
import { VirtualFile } from "../../../features/virtual-drive/virtual-file.js";
import { useWindowsManager } from "../../../hooks/windows/WindowsManagerContext.js";
/**
* @param {Object} props
* @param {VirtualFile} props.file
*/
function FilePreview({ file }) {
let preview;
console.log(file);
let preview = null;
switch (file.extension) {
case "png":
@ -25,6 +30,7 @@ export function FileExplorer() {
const virtualRoot = useVirtualRoot();
const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate("~"));
const [path, setPath] = useState(currentDirectory.path);
const windowsManager = useWindowsManager();
const changeDirectory = (path, absolute = false) => {
const directory = absolute ? virtualRoot.navigate(path) : currentDirectory.navigate(path);
@ -96,13 +102,18 @@ export function FileExplorer() {
</div>
<div className={styles.Main}>
{currentDirectory.files.map((file, index) =>
<button key={index} className={styles["File-button"]}>
<button key={index} className={styles["File-button"]} onClick={(event) => {
event.preventDefault();
windowsManager.openFile(file);
}}>
<FilePreview file={file}/>
<p>{file.id}</p>
</button>
)}
{currentDirectory.subFolders.map(({ name }, index) =>
<button key={index} className={styles["Folder-button"]} onClick={() => { changeDirectory(name) }}>
<button key={index} className={styles["Folder-button"]} onClick={() => {
changeDirectory(name);
}}>
<FontAwesomeIcon icon={faFolder}/>
<p>{name}</p>
</button>

View file

@ -0,0 +1,26 @@
// eslint-disable-next-line no-unused-vars
import { VirtualFile } from "../../../features/virtual-drive/virtual-file.js"
import styles from "./MediaViewer.module.css";
/**
*
* @param {Object} props
* @param {VirtualFile} props.file
* @returns
*/
export function MediaViewer({ file }) {
if (file == null)
return (<p>No file to render.</p>);
if (!["png"].includes(file.extension))
return (<p>Invalid file format.</p>);
if (file.source == null)
return (<p>File failed to load.</p>);
return (
<div className={styles.Container}>
<img src={file.source} alt={file.id}/>
</div>
);
}

View file

@ -0,0 +1,14 @@
.Container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
padding: 2rem;
}
.Container img {
width: 100%;
height: 100%;
object-fit: contain;
}

View file

@ -6,12 +6,24 @@ import { Command } from "../../../features/applications/terminal/commands.js";
const USERNAME = "user";
const HOSTNAME = "prozilla-os";
/**
* @param {Object} props
* @param {String} props.text
*/
function OutputLine({ text }) {
return (
<p className={styles.Output}>{text}</p>
);
}
/**
* @param {Object} props
* @param {String} props.value
* @param {String} props.prefix
* @param {Function} props.onChange
* @param {Function} props.onKeyUp
* @param {Function} props.onKeyDown
*/
function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown }) {
return (
<span className={styles.Input}>

View file

@ -6,7 +6,13 @@ import ApplicationsManager from "../../features/applications/applications.js";
import { useWindows } from "../../hooks/windows/WindowsContext.js";
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
import { ReactSVG } from "react-svg";
// eslint-disable-next-line no-unused-vars
import Application from "../../features/applications/application.js";
/**
* @param {Object} props
* @param {Application} props.app
*/
function AppButton({ app }) {
const [active, setActive] = useState(false);
const windows = useWindows();

View file

@ -6,8 +6,21 @@ import { ReactSVG } from "react-svg";
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
import Draggable from "react-draggable";
import { useEffect, useRef, useState } from "react";
// eslint-disable-next-line no-unused-vars
import Application from "../../features/applications/application.js";
// eslint-disable-next-line no-unused-vars
import Vector2 from "../../features/math/vector2.js";
export function Window({ id, app, size, position, focused = false, onInteract }) {
/**
* @param {Object} props
* @param {String} props.id
* @param {Application} props.app
* @param {Vector2} props.size
* @param {Vector2} props.position
* @param {boolean} props.focused
* @param {Function} props.onInteract
*/
export function Window({ id, app, size, position, focused = false, onInteract, options }) {
const windowsManager = useWindowsManager();
const nodeRef = useRef(null);
const [maximized, setMaximized] = useState(false);
@ -72,7 +85,7 @@ export function Window({ id, app, size, position, focused = false, onInteract })
</button>
</div>
<div className={styles["Window-content"]}>
{app.windowContent}
<app.WindowContent {...options}/>
</div>
</div>
</Draggable>

View file

@ -11,7 +11,7 @@ export function WindowsView() {
return (<div>
{windows.sort((windowA, windowB) =>
windowA.lastInteraction - windowB.lastInteraction
).map(({ id, app, size, position }, index) =>
).map(({ id, app, size, position, options }, index) =>
<Window
onInteract={() => { windowsManager.focus(windows[index]); }}
id={id}
@ -19,6 +19,7 @@ export function WindowsView() {
app={app}
size={size}
position={position}
options={options}
/>
)}
</div>);

View file

@ -4,7 +4,18 @@ export default class Application {
* @param {String} id
* @param {React.ReactElement} windowContent
*/
constructor(name, id, windowContent) {
Object.assign(this, { name, id, windowContent });
constructor(name, id, windowContent, windowOptions) {
Object.assign(this, { name, id, windowContent, windowOptions });
}
WindowContent = (props) => {
props = {...props, ...this.windowOptions};
if (this.windowContent == null) {
console.warn(`App (${this.id}) is missing the windowContent property.`);
return null;
}
return <this.windowContent {...props}/>;
}
}

View file

@ -1,21 +1,26 @@
/* eslint-disable eqeqeq */
import { FileExplorer } from "../../components/applications/file-explorer/FileExplorer.jsx";
import { MediaViewer } from "../../components/applications/media-viewer/MediaViewer.jsx";
import { WebView } from "../../components/applications/templates/WebView.jsx";
import { Terminal } from "../../components/applications/terminal/Terminal.jsx";
import Application from "./application.js";
export default class ApplicationsManager {
static APPLICATIONS = [
new Application("Terminal", "terminal", <Terminal/>),
new Application("Terminal", "terminal", Terminal),
// new Application("Browser", "browser"),
new Application("Code Editor", "code-editor"),
new Application("File Explorer", "file-explorer", <FileExplorer/>),
new Application("Media Viewer", "media-viewer"),
new Application("Wordle", "wordle", <WebView source="https://prozilla.dev/wordle"/>),
new Application("Balls", "balls", <WebView source="https://prozilla.dev/ball-maze"/>),
new Application("Minesweeper", "minesweeper", <WebView source="https://prozilla.dev/minesweeper"/>),
new Application("File Explorer", "file-explorer", FileExplorer),
new Application("Media Viewer", "media-viewer", MediaViewer),
new Application("Wordle", "wordle", WebView, { source: "https://prozilla.dev/wordle" }),
new Application("Balls", "balls", WebView, { source: "https://prozilla.dev/ball-maze" }),
new Application("Minesweeper", "minesweeper", WebView, { source: "https://prozilla.dev/minesweeper" }),
]
/**
* @param {String} id
* @returns {Application}
*/
static getApplication(id) {
let application = null;
@ -28,4 +33,21 @@ export default class ApplicationsManager {
return application;
}
/**
* @param {String} fileExtension
* @returns {Application}
*/
static getFileApplication(fileExtension) {
let app = null;
// eslint-disable-next-line default-case
switch (fileExtension) {
case "png":
app = this.getApplication("media-viewer");
break;
}
return app;
}
}

View file

@ -1,15 +1,16 @@
import ApplicationsManager from "../applications/applications.js";
import { randomRange } from "../math/random.js";
import Vector2 from "../math/vector2.js";
// eslint-disable-next-line no-unused-vars
import { VirtualFile } from "../virtual-drive/virtual-file.js";
export default class WindowsManager {
constructor() {
this.windows = {};
this.updateWindows = () => {};
console.log("Windows manager init");
}
open(appId) {
open(appId, options) {
const app = ApplicationsManager.getApplication(appId);
const size = new Vector2(700, 400);
const position = new Vector2(randomRange(50, 600), randomRange(50, 450));
@ -19,13 +20,16 @@ export default class WindowsManager {
id++;
}
id = id.toString();
console.log(`Opening window ${id}:${app.id}`);
this.windows[id.toString()] = {
this.windows[id] = {
id,
app,
size,
position,
options,
lastInteraction: new Date().valueOf()
};
@ -33,6 +37,14 @@ export default class WindowsManager {
// console.log(this);
}
/**
* @param {VirtualFile} file
*/
openFile(file) {
const app = ApplicationsManager.getFileApplication(file.extension);
this.open(app.id, { file });
}
close(windowId) {
windowId = windowId.toString();

View file

@ -38,13 +38,13 @@ function initVirtualRoot(virtualRoot) {
folder.setAlias("~")
.createFolder("Images", (folder) => {
folder.createFile("Wallpaper_1", "png", (file) => {
file.setSource("/public/media/wallpapers/wallpaper-1.png")
file.setSource("/media/wallpapers/wallpaper1.png")
}).createFile("Wallpaper_2", "png", (file) => {
file.setSource("/public/media/wallpapers/wallpaper-2.png")
file.setSource("/media/wallpapers/wallpaper2.png")
}).createFile("Wallpaper_3", "png", (file) => {
file.setSource("/public/media/wallpapers/wallpaper-3.png")
file.setSource("/media/wallpapers/wallpaper3.png")
}).createFile("Wallpaper_4", "png", (file) => {
file.setSource("/public/media/wallpapers/wallpaper-4.png")
file.setSource("/media/wallpapers/wallpaper4.png")
})
})
.createFolder("Documents")