Added media viewer
This commit is contained in:
parent
a4054f134a
commit
fcce477d15
13 changed files with 174 additions and 26 deletions
25
package-lock.json
generated
25
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"gh-pages": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
26
src/components/applications/media-viewer/MediaViewer.jsx
Normal file
26
src/components/applications/media-viewer/MediaViewer.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>);
|
||||
|
|
|
|||
|
|
@ -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}/>;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in a new issue