From fcce477d151ae5404018b660a3ae600d67c0abd8 Mon Sep 17 00:00:00 2001 From: Prozilla Date: Sat, 29 Jul 2023 15:10:48 +0200 Subject: [PATCH] Added media viewer --- package-lock.json | 25 ++++++++++++-- package.json | 1 + .../file-explorer/FileExplorer.jsx | 21 +++++++++--- .../applications/media-viewer/MediaViewer.jsx | 26 ++++++++++++++ .../media-viewer/MediaViewer.module.css | 14 ++++++++ .../applications/terminal/Terminal.jsx | 12 +++++++ src/components/task-bar/TaskBar.jsx | 6 ++++ src/components/windows/Window.jsx | 17 ++++++++-- src/components/windows/WindowsView.jsx | 3 +- src/features/applications/application.js | 15 ++++++-- src/features/applications/applications.js | 34 +++++++++++++++---- src/features/windows/windows.js | 18 ++++++++-- src/hooks/virtual-drive/VirtualRootContext.js | 8 ++--- 13 files changed, 174 insertions(+), 26 deletions(-) create mode 100644 src/components/applications/media-viewer/MediaViewer.jsx create mode 100644 src/components/applications/media-viewer/MediaViewer.module.css diff --git a/package-lock.json b/package-lock.json index 288b22a..954fabb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 7fe3edb..e6c2594 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ ] }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "gh-pages": "^5.0.0" } } diff --git a/src/components/applications/file-explorer/FileExplorer.jsx b/src/components/applications/file-explorer/FileExplorer.jsx index 180ffb5..8a2ef8e 100644 --- a/src/components/applications/file-explorer/FileExplorer.jsx +++ b/src/components/applications/file-explorer/FileExplorer.jsx @@ -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() {
{currentDirectory.files.map((file, index) => - )} {currentDirectory.subFolders.map(({ name }, index) => - diff --git a/src/components/applications/media-viewer/MediaViewer.jsx b/src/components/applications/media-viewer/MediaViewer.jsx new file mode 100644 index 0000000..bc87ab2 --- /dev/null +++ b/src/components/applications/media-viewer/MediaViewer.jsx @@ -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 (

No file to render.

); + + if (!["png"].includes(file.extension)) + return (

Invalid file format.

); + + if (file.source == null) + return (

File failed to load.

); + + return ( +
+ {file.id}/ +
+ ); +} \ No newline at end of file diff --git a/src/components/applications/media-viewer/MediaViewer.module.css b/src/components/applications/media-viewer/MediaViewer.module.css new file mode 100644 index 0000000..f2b297e --- /dev/null +++ b/src/components/applications/media-viewer/MediaViewer.module.css @@ -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; +} \ No newline at end of file diff --git a/src/components/applications/terminal/Terminal.jsx b/src/components/applications/terminal/Terminal.jsx index fe1c6e6..cea3619 100644 --- a/src/components/applications/terminal/Terminal.jsx +++ b/src/components/applications/terminal/Terminal.jsx @@ -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 (

{text}

); } +/** + * @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 ( diff --git a/src/components/task-bar/TaskBar.jsx b/src/components/task-bar/TaskBar.jsx index 198bed6..48442e0 100644 --- a/src/components/task-bar/TaskBar.jsx +++ b/src/components/task-bar/TaskBar.jsx @@ -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(); diff --git a/src/components/windows/Window.jsx b/src/components/windows/Window.jsx index 683ab49..34dce75 100644 --- a/src/components/windows/Window.jsx +++ b/src/components/windows/Window.jsx @@ -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 })
- {app.windowContent} +
diff --git a/src/components/windows/WindowsView.jsx b/src/components/windows/WindowsView.jsx index c14c8e1..377d027 100644 --- a/src/components/windows/WindowsView.jsx +++ b/src/components/windows/WindowsView.jsx @@ -11,7 +11,7 @@ export function WindowsView() { return (
{windows.sort((windowA, windowB) => windowA.lastInteraction - windowB.lastInteraction - ).map(({ id, app, size, position }, index) => + ).map(({ id, app, size, position, options }, index) => { windowsManager.focus(windows[index]); }} id={id} @@ -19,6 +19,7 @@ export function WindowsView() { app={app} size={size} position={position} + options={options} /> )}
); diff --git a/src/features/applications/application.js b/src/features/applications/application.js index 39e023e..4af341a 100644 --- a/src/features/applications/application.js +++ b/src/features/applications/application.js @@ -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 ; } } \ No newline at end of file diff --git a/src/features/applications/applications.js b/src/features/applications/applications.js index 95fcd86..0d38b1d 100644 --- a/src/features/applications/applications.js +++ b/src/features/applications/applications.js @@ -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", ), + new Application("Terminal", "terminal", Terminal), // new Application("Browser", "browser"), new Application("Code Editor", "code-editor"), - new Application("File Explorer", "file-explorer", ), - new Application("Media Viewer", "media-viewer"), - new Application("Wordle", "wordle", ), - new Application("Balls", "balls", ), - new Application("Minesweeper", "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; + } } \ No newline at end of file diff --git a/src/features/windows/windows.js b/src/features/windows/windows.js index 4929c45..09341ba 100644 --- a/src/features/windows/windows.js +++ b/src/features/windows/windows.js @@ -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(); diff --git a/src/hooks/virtual-drive/VirtualRootContext.js b/src/hooks/virtual-drive/VirtualRootContext.js index 49a8371..fbfa972 100644 --- a/src/hooks/virtual-drive/VirtualRootContext.js +++ b/src/hooks/virtual-drive/VirtualRootContext.js @@ -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")