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" "web-vitals": "^2.1.4"
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"gh-pages": "^5.0.0" "gh-pages": "^5.0.0"
} }
}, },
@ -638,9 +639,16 @@
} }
}, },
"node_modules/@babel/plugin-proposal-private-property-in-object": { "node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2", "version": "7.21.11",
"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", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz",
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "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": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
}, },
@ -1898,6 +1906,17 @@
"@babel/core": "^7.0.0-0" "@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": { "node_modules/@babel/preset-env/node_modules/semver": {
"version": "6.3.1", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",

View file

@ -49,6 +49,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"gh-pages": "^5.0.0" "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 styles from "./FileExplorer.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowUp, faCaretLeft, faCaretRight, faCog, faDesktop, faFile, faFileLines, faFolder, faHouse, faImage, faSearch } from "@fortawesome/free-solid-svg-icons"; 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 }) { function FilePreview({ file }) {
let preview; let preview = null;
console.log(file);
switch (file.extension) { switch (file.extension) {
case "png": case "png":
@ -25,6 +30,7 @@ export function FileExplorer() {
const virtualRoot = useVirtualRoot(); const virtualRoot = useVirtualRoot();
const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate("~")); const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate("~"));
const [path, setPath] = useState(currentDirectory.path); const [path, setPath] = useState(currentDirectory.path);
const windowsManager = useWindowsManager();
const changeDirectory = (path, absolute = false) => { const changeDirectory = (path, absolute = false) => {
const directory = absolute ? virtualRoot.navigate(path) : currentDirectory.navigate(path); const directory = absolute ? virtualRoot.navigate(path) : currentDirectory.navigate(path);
@ -96,13 +102,18 @@ export function FileExplorer() {
</div> </div>
<div className={styles.Main}> <div className={styles.Main}>
{currentDirectory.files.map((file, index) => {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}/> <FilePreview file={file}/>
<p>{file.id}</p> <p>{file.id}</p>
</button> </button>
)} )}
{currentDirectory.subFolders.map(({ name }, index) => {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}/> <FontAwesomeIcon icon={faFolder}/>
<p>{name}</p> <p>{name}</p>
</button> </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 USERNAME = "user";
const HOSTNAME = "prozilla-os"; const HOSTNAME = "prozilla-os";
/**
* @param {Object} props
* @param {String} props.text
*/
function OutputLine({ text }) { function OutputLine({ text }) {
return ( return (
<p className={styles.Output}>{text}</p> <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 }) { function InputLine({ value, prefix, onChange, onKeyUp, onKeyDown }) {
return ( return (
<span className={styles.Input}> <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 { useWindows } from "../../hooks/windows/WindowsContext.js";
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js"; import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
import { ReactSVG } from "react-svg"; 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 }) { function AppButton({ app }) {
const [active, setActive] = useState(false); const [active, setActive] = useState(false);
const windows = useWindows(); const windows = useWindows();

View file

@ -6,8 +6,21 @@ import { ReactSVG } from "react-svg";
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js"; import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
import Draggable from "react-draggable"; import Draggable from "react-draggable";
import { useEffect, useRef, useState } from "react"; 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 windowsManager = useWindowsManager();
const nodeRef = useRef(null); const nodeRef = useRef(null);
const [maximized, setMaximized] = useState(false); const [maximized, setMaximized] = useState(false);
@ -72,7 +85,7 @@ export function Window({ id, app, size, position, focused = false, onInteract })
</button> </button>
</div> </div>
<div className={styles["Window-content"]}> <div className={styles["Window-content"]}>
{app.windowContent} <app.WindowContent {...options}/>
</div> </div>
</div> </div>
</Draggable> </Draggable>

View file

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

View file

@ -4,7 +4,18 @@ export default class Application {
* @param {String} id * @param {String} id
* @param {React.ReactElement} windowContent * @param {React.ReactElement} windowContent
*/ */
constructor(name, id, windowContent) { constructor(name, id, windowContent, windowOptions) {
Object.assign(this, { name, id, windowContent }); 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 */ /* eslint-disable eqeqeq */
import { FileExplorer } from "../../components/applications/file-explorer/FileExplorer.jsx"; 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 { WebView } from "../../components/applications/templates/WebView.jsx";
import { Terminal } from "../../components/applications/terminal/Terminal.jsx"; import { Terminal } from "../../components/applications/terminal/Terminal.jsx";
import Application from "./application.js"; import Application from "./application.js";
export default class ApplicationsManager { export default class ApplicationsManager {
static APPLICATIONS = [ static APPLICATIONS = [
new Application("Terminal", "terminal", <Terminal/>), new Application("Terminal", "terminal", Terminal),
// new Application("Browser", "browser"), // new Application("Browser", "browser"),
new Application("Code Editor", "code-editor"), new Application("Code Editor", "code-editor"),
new Application("File Explorer", "file-explorer", <FileExplorer/>), new Application("File Explorer", "file-explorer", FileExplorer),
new Application("Media Viewer", "media-viewer"), new Application("Media Viewer", "media-viewer", MediaViewer),
new Application("Wordle", "wordle", <WebView source="https://prozilla.dev/wordle"/>), new Application("Wordle", "wordle", WebView, { source: "https://prozilla.dev/wordle" }),
new Application("Balls", "balls", <WebView source="https://prozilla.dev/ball-maze"/>), new Application("Balls", "balls", WebView, { source: "https://prozilla.dev/ball-maze" }),
new Application("Minesweeper", "minesweeper", <WebView source="https://prozilla.dev/minesweeper"/>), new Application("Minesweeper", "minesweeper", WebView, { source: "https://prozilla.dev/minesweeper" }),
] ]
/**
* @param {String} id
* @returns {Application}
*/
static getApplication(id) { static getApplication(id) {
let application = null; let application = null;
@ -28,4 +33,21 @@ export default class ApplicationsManager {
return application; 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 ApplicationsManager from "../applications/applications.js";
import { randomRange } from "../math/random.js"; import { randomRange } from "../math/random.js";
import Vector2 from "../math/vector2.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 { export default class WindowsManager {
constructor() { constructor() {
this.windows = {}; this.windows = {};
this.updateWindows = () => {}; this.updateWindows = () => {};
console.log("Windows manager init");
} }
open(appId) { open(appId, options) {
const app = ApplicationsManager.getApplication(appId); const app = ApplicationsManager.getApplication(appId);
const size = new Vector2(700, 400); const size = new Vector2(700, 400);
const position = new Vector2(randomRange(50, 600), randomRange(50, 450)); const position = new Vector2(randomRange(50, 600), randomRange(50, 450));
@ -19,13 +20,16 @@ export default class WindowsManager {
id++; id++;
} }
id = id.toString();
console.log(`Opening window ${id}:${app.id}`); console.log(`Opening window ${id}:${app.id}`);
this.windows[id.toString()] = { this.windows[id] = {
id, id,
app, app,
size, size,
position, position,
options,
lastInteraction: new Date().valueOf() lastInteraction: new Date().valueOf()
}; };
@ -33,6 +37,14 @@ export default class WindowsManager {
// console.log(this); // console.log(this);
} }
/**
* @param {VirtualFile} file
*/
openFile(file) {
const app = ApplicationsManager.getFileApplication(file.extension);
this.open(app.id, { file });
}
close(windowId) { close(windowId) {
windowId = windowId.toString(); windowId = windowId.toString();

View file

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