Added share modal
This commit is contained in:
parent
0df2efffbf
commit
c8f3993238
18 changed files with 402 additions and 27 deletions
4
public/assets/modals/icons/share.svg
Normal file
4
public/assets/modals/icons/share.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.5714 43.125C25.4286 43.125 22.8571 45.6844 22.8571 48.8125V162.562C22.8571 165.691 25.4286 168.25 28.5714 168.25H142.857C146 168.25 148.571 165.691 148.571 162.562V145.5C148.571 139.208 153.679 134.125 160 134.125C166.321 134.125 171.429 139.208 171.429 145.5V162.562C171.429 178.274 158.643 191 142.857 191H28.5714C12.7857 191 0 178.274 0 162.562V48.8125C0 33.1008 12.7857 20.375 28.5714 20.375H45.7143C52.0357 20.375 57.1429 25.4582 57.1429 31.75C57.1429 38.0418 52.0357 43.125 45.7143 43.125H28.5714Z" fill="white"/>
|
||||
<path d="M109.107 88.625H125.714V111.375C125.714 117.667 130.821 122.75 137.143 122.75H138.464C141.286 122.75 144 121.719 146.107 119.835L195.75 75.366C198.464 72.9488 200 69.5008 200 65.875C200 62.2492 198.464 58.8012 195.75 56.384L146.393 12.1637C144.107 10.1375 141.179 9 138.107 9C131.25 9 125.714 14.5098 125.714 21.3348V43.125H97.1428C68.7499 43.125 45.7142 66.0527 45.7142 94.3125C45.7142 127.513 75.2857 142.23 81.6428 145.038C82.4642 145.393 83.3214 145.536 84.1785 145.536H85.0714C88.5714 145.536 91.4285 142.692 91.4285 139.208C91.4285 136.258 89.2857 133.663 86.8571 131.992C83.6785 129.788 79.9999 125.523 79.9999 117.596C79.9999 101.6 93.0357 88.625 109.107 88.625Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -60,8 +60,7 @@ export function FileExplorer({ startPath, selectorMode, Footer, modalsManager, o
|
|||
title: `${file.id} ${TITLE_SEPARATOR} Properties`,
|
||||
iconUrl: file.getIconUrl(),
|
||||
size: new Vector2(400, 500),
|
||||
Modal: (props) =>
|
||||
<FileProperties file={file} {...props}/>
|
||||
Modal: (props) => <FileProperties file={file} {...props}/>
|
||||
});
|
||||
}}/>
|
||||
</Actions>
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ import { AppsSettings } from "./tabs/AppsSettings.jsx";
|
|||
/**
|
||||
* @param {import("../../windows/WindowView.jsx").windowProps} props
|
||||
*/
|
||||
export function Settings({ initialTabIndex, modalsManager }) {
|
||||
export function Settings({ tab, modalsManager }) {
|
||||
return (
|
||||
<Tabs
|
||||
defaultIndex={initialTabIndex ?? 0}
|
||||
defaultIndex={tab ?? 0}
|
||||
className={styles.Container}
|
||||
selectedTabClassName={styles["Active-tab"]}
|
||||
selectedTabPanelClassName={styles["Active-panel"]}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ import Stream from "../../../features/apps/terminal/stream.js";
|
|||
/**
|
||||
* @param {import("../../windows/WindowView.jsx").windowProps} props
|
||||
*/
|
||||
export function Terminal({ startPath, setTitle, close: exit, active }) {
|
||||
export function Terminal({ startPath, input, setTitle, close: exit, active }) {
|
||||
const [inputKey, setInputKey] = useState(0);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const [inputValue, setInputValue] = useState(input ?? "");
|
||||
const [history, setHistory] = useState([]);
|
||||
const virtualRoot = useVirtualRoot();
|
||||
const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate(startPath ?? "~"));
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ import { DropdownAction } from "../actions/actions/DropdownAction.jsx";
|
|||
import { RadioAction } from "../actions/actions/RadioAction.jsx";
|
||||
import { Divider } from "../actions/actions/Divider.jsx";
|
||||
import { isValidInteger } from "../../features/_utils/number.utils.js";
|
||||
import { useWindowedModal } from "../../hooks/modals/windowedModal.js";
|
||||
import { Share } from "../modals/share/Share.jsx";
|
||||
import ModalsManager from "../../features/modals/modalsManager.js";
|
||||
|
||||
export const Desktop = memo(() => {
|
||||
const settingsManager = useSettingsManager();
|
||||
|
|
@ -30,6 +33,7 @@ export const Desktop = memo(() => {
|
|||
const virtualRoot = useVirtualRoot();
|
||||
const [showIcons, setShowIcons] = useState(false);
|
||||
const [iconSize, setIconSize] = useState(FALLBACK_ICON_SIZE);
|
||||
const { openWindowedModal } = useWindowedModal({ modalsManager });
|
||||
|
||||
const directory = virtualRoot.navigate("~/Desktop");
|
||||
|
||||
|
|
@ -54,7 +58,7 @@ export const Desktop = memo(() => {
|
|||
reloadViewport();
|
||||
}}/>
|
||||
<ClickAction label="Change appearance" icon={faPaintBrush} onTrigger={() => {
|
||||
windowsManager.open("settings", { initialTabIndex: 0 });
|
||||
windowsManager.open("settings", { tab: 0 });
|
||||
}}/>
|
||||
<Divider/>
|
||||
<ClickAction label={`Open in ${APP_NAMES.FILE_EXPLORER}`} icon={APP_ICONS.FILE_EXPLORER} onTrigger={() => {
|
||||
|
|
@ -63,6 +67,13 @@ export const Desktop = memo(() => {
|
|||
<ClickAction label={`Open in ${APP_NAMES.TERMINAL}`} icon={APP_ICONS.TERMINAL} onTrigger={() => {
|
||||
windowsManager.open(APPS.TERMINAL, { startPath: directory.path });
|
||||
}}/>
|
||||
<Divider/>
|
||||
<ClickAction label={"Share"} icon={ModalsManager.getModalIconUrl("share")} onTrigger={() => {
|
||||
openWindowedModal({
|
||||
size: new Vector2(350, 350),
|
||||
Modal: (props) => <Share {...props}/>
|
||||
});
|
||||
}}/>
|
||||
</Actions>
|
||||
});
|
||||
const { onContextMenu: onContextMenuFile } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,14 @@ import OutsideClickListener from "../../hooks/_utils/outsideClick.js";
|
|||
import styles from "./ModalView.module.css";
|
||||
import { useEffect } from "react";
|
||||
|
||||
/**
|
||||
* @typedef {object} modalProps
|
||||
* @param {object} props
|
||||
* @param {ModalType} props.modal
|
||||
* @param {*} props.params
|
||||
* @param {Function} props.onFinish
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {object} root
|
||||
* @param {ModalType} root.modal
|
||||
|
|
@ -11,13 +19,11 @@ import { useEffect } from "react";
|
|||
export const ModalView = memo(({ modal }) => {
|
||||
useEffect(() => {
|
||||
const onDismiss = (event) => {
|
||||
if (event.key === "Escape" && modal.dismissible)
|
||||
if (event.key === "Escape")
|
||||
modal.close();
|
||||
};
|
||||
|
||||
if (modal.dismissible) {
|
||||
document.addEventListener("keydown", onDismiss);
|
||||
}
|
||||
document.addEventListener("keydown", onDismiss);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onDismiss);
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ export function FileSelector({ modal, params, type, allowedFormats, onFinish, ..
|
|||
const [selection, setSelection] = useState(multi ? [] : null);
|
||||
const [directory, setDirectory] = useState(null);
|
||||
|
||||
params.title = multi ? "Select files" : "Select a file";
|
||||
params.iconUrl = APP_ICONS.FILE_EXPLORER;
|
||||
|
||||
const finish = (event) => {
|
||||
event?.preventDefault();
|
||||
|
||||
|
|
@ -56,7 +53,11 @@ export function FileSelector({ modal, params, type, allowedFormats, onFinish, ..
|
|||
onFinish?.(multi ? files : files[0]);
|
||||
};
|
||||
|
||||
return <WindowedModal modal={modal} params={params} {...props}>
|
||||
return <WindowedModal modal={modal} params={{
|
||||
title: multi ? "Select files" : "Select a file",
|
||||
iconUrl: APP_ICONS.FILE_EXPLORER,
|
||||
...params,
|
||||
}} {...props}>
|
||||
<ModalsView modalsManager={modalsManager} modals={modals}/>
|
||||
<FileExplorer
|
||||
modalsManager={modalsManager}
|
||||
|
|
|
|||
17
src/components/modals/share/Option.jsx
Normal file
17
src/components/modals/share/Option.jsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { useState } from "react";
|
||||
import styles from "./Share.module.css";
|
||||
|
||||
export default function Option({ name, label, setOption }) {
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
const onChange = (event) => {
|
||||
const newValue = event.target.value;
|
||||
setValue(newValue);
|
||||
setOption(name, newValue);
|
||||
};
|
||||
|
||||
return <label className={styles.Label}>
|
||||
<p>{label}:</p>
|
||||
<input className={styles.Input} name={name} type="text" value={value} onChange={onChange}/>
|
||||
</label>;
|
||||
}
|
||||
122
src/components/modals/share/Share.jsx
Normal file
122
src/components/modals/share/Share.jsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import ModalsManager from "../../../features/modals/modalsManager.js";
|
||||
import { WindowedModal } from "../_utils/WindowedModal.jsx";
|
||||
import styles from "./Share.module.css";
|
||||
import { copyToClipboard, generateUrl } from "../../../features/_utils/browser.utils.js";
|
||||
import AppsManager from "../../../features/apps/appsManager.js";
|
||||
import utilStyles from "../../../styles/utils.module.css";
|
||||
import { Button } from "../../_utils/button/Button.jsx";
|
||||
import Option from "./Option.jsx";
|
||||
|
||||
const APP_OPTIONS = {
|
||||
"terminal": [
|
||||
{
|
||||
label: "Command",
|
||||
name: "input"
|
||||
},
|
||||
],
|
||||
"browser": [
|
||||
{
|
||||
label: "Website",
|
||||
name: "startUrl"
|
||||
},
|
||||
],
|
||||
"file-explorer": [
|
||||
{
|
||||
label: "Path",
|
||||
name: "startPath"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/** @type {import("../ModalView.jsx").modalProps} */
|
||||
export function Share({ modal, params, ...props }) {
|
||||
const [appId, setAppId] = useState(params.appId ?? "");
|
||||
const [fullscreen, setFullscreen] = useState(params.fullscreen ?? false);
|
||||
const [options, setOptions] = useState({});
|
||||
const [url, setUrl] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setUrl(generateUrl({
|
||||
appId: appId !== "" ? appId : null,
|
||||
fullscreen,
|
||||
...options
|
||||
}));
|
||||
}, [appId, fullscreen, options]);
|
||||
|
||||
const onAppIdChange = (event) => {
|
||||
const newAppId = event.target.value;
|
||||
|
||||
if (newAppId === appId)
|
||||
return;
|
||||
|
||||
setAppId(newAppId);
|
||||
|
||||
const appOptions = APP_OPTIONS[appId];
|
||||
if (!appOptions) {
|
||||
setOptions({});
|
||||
} else {
|
||||
const newOptions = {};
|
||||
appOptions.forEach(({ key }) => {
|
||||
newOptions[key] = "";
|
||||
});
|
||||
setOptions(newOptions);
|
||||
}
|
||||
};
|
||||
|
||||
const onFullscreenChange = (event) => {
|
||||
const newFullscreen = event.target.checked;
|
||||
setFullscreen(newFullscreen);
|
||||
};
|
||||
|
||||
const setOption = (name, value) => {
|
||||
setOptions((options = {}) => {
|
||||
options = { ...options };
|
||||
options[name] = value;
|
||||
return options;
|
||||
});
|
||||
};
|
||||
|
||||
return <WindowedModal className={styles.Container} modal={modal} params={{
|
||||
...params,
|
||||
title: "Share",
|
||||
iconUrl: ModalsManager.getModalIconUrl("share"),
|
||||
}} {...props}>
|
||||
<div>
|
||||
<h1 className={styles.Title}>Share options</h1>
|
||||
<form className={styles.Form}>
|
||||
<label className={styles.Label}>
|
||||
<p>App:</p>
|
||||
<select className={styles.Input} name="app" value={appId} onChange={onAppIdChange}>
|
||||
<option value={""}>(None)</option>
|
||||
{AppsManager.APPS.map(({ name, id }) =>
|
||||
<option key={id} value={id}>{name}</option>
|
||||
)}
|
||||
</select>
|
||||
</label>
|
||||
<label className={styles.Label}>
|
||||
<p>Fullscreen:</p>
|
||||
<input className={styles.Input} name="fullscreen" type="checkbox" checked={fullscreen} value={fullscreen} onChange={onFullscreenChange}/>
|
||||
</label>
|
||||
{APP_OPTIONS[appId]?.map(({ label, name }) =>
|
||||
<Option key={name} name={name} label={label} setOption={setOption}/>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<p className={`${styles.Url} ${utilStyles["Text-light"]}`}>{url}</p>
|
||||
<Button
|
||||
className={`${styles.Button} ${utilStyles["Text-bold"]}`}
|
||||
onClick={() => {
|
||||
copyToClipboard(url, () => {
|
||||
alert("Copied to clipboard!");
|
||||
}, () => {
|
||||
alert("Failed to copy");
|
||||
});
|
||||
}}
|
||||
>
|
||||
Copy URL
|
||||
</Button>
|
||||
</div>
|
||||
</WindowedModal>;
|
||||
}
|
||||
93
src/components/modals/share/Share.module.css
Normal file
93
src/components/modals/share/Share.module.css
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
.Container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.Container > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.Container > div:last-child {
|
||||
gap: 0.5rem;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.Title:first-child {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.Form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.Label {
|
||||
--gap: 0.5rem;
|
||||
|
||||
display: flex;
|
||||
gap: var(--gap);
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.Label > p {
|
||||
width: calc(40% - var(--gap));
|
||||
min-width: 40%;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.Input {
|
||||
width: auto;
|
||||
max-width: calc(60% - var(--gap));
|
||||
padding: 0.5rem 1rem;
|
||||
color: var(--text-color);
|
||||
background-color: var(--background-color-b);
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
outline: none;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
select.Input > * {
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.Input[type=checkbox] {
|
||||
background-color: var(--background-color-b);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.Url {
|
||||
text-align: start;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.Button {
|
||||
--normal-color: var(--background-color-a) !important;
|
||||
--hover-color: var(--background-color-b) !important;
|
||||
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -20,6 +20,10 @@ 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";
|
||||
import { useWindowedModal } from "../../hooks/modals/windowedModal.js";
|
||||
import { Divider } from "../actions/actions/Divider.jsx";
|
||||
import ModalsManager from "../../features/modals/modalsManager.js";
|
||||
import { Share } from "../modals/share/Share.jsx";
|
||||
|
||||
/**
|
||||
* @typedef {object} windowProps
|
||||
|
|
@ -45,14 +49,15 @@ import { useZIndex } from "../../hooks/z-index/zIndex.js";
|
|||
* @param {boolean} props.minimized
|
||||
* @param {Function} props.toggleMinimized
|
||||
*/
|
||||
export const WindowView = memo(({ id, app, size, position, onInteract, options, active, minimized, toggleMinimized, index }) => {
|
||||
export const WindowView = memo(({ id, app, size, position, onInteract, options, active, fullscreen, minimized, toggleMinimized, index }) => {
|
||||
const windowsManager = useWindowsManager();
|
||||
const nodeRef = useRef(null);
|
||||
const [modalsManager, modals] = useModals();
|
||||
const { openWindowedModal } = useWindowedModal({ modalsManager });
|
||||
|
||||
const [startSize, setStartSize] = useState(size);
|
||||
const [startPosition, setStartPosition] = useState(position);
|
||||
const [maximized, setMaximized] = useState(false);
|
||||
const [maximized, setMaximized] = useState(fullscreen ?? false);
|
||||
const [screenWidth, screenHeight] = useScreenDimensions();
|
||||
const [title, setTitle] = useState(app.name);
|
||||
const [iconUrl, setIconUrl] = useState(AppsManager.getAppIconUrl(app.id));
|
||||
|
|
@ -67,6 +72,15 @@ export const WindowView = memo(({ id, app, size, position, onInteract, options,
|
|||
<ClickAction label="Close" icon={faTimes} shortcut={["Control", "q"]} onTrigger={() => {
|
||||
close();
|
||||
}}/>
|
||||
<Divider/>
|
||||
<ClickAction label={"Share"} icon={ModalsManager.getModalIconUrl("share")} onTrigger={() => {
|
||||
openWindowedModal({
|
||||
appId: app.id,
|
||||
fullscreen: maximized,
|
||||
size: new Vector2(350, 350),
|
||||
Modal: (props) => <Share {...props}/>
|
||||
});
|
||||
}}/>
|
||||
</Actions>
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { WindowView } from "./WindowView.jsx";
|
|||
import { useSettingsManager } from "../../hooks/settings/settingsManagerContext.js";
|
||||
import { SettingsManager } from "../../features/settings/settingsManager.js";
|
||||
import { NAME, TAG_LINE } from "../../config/branding.config.js";
|
||||
import { setViewportIcon, setViewportTitle } from "../../features/_utils/browser.utils.js";
|
||||
import { getViewportParams, setViewportIcon, setViewportTitle } from "../../features/_utils/browser.utils.js";
|
||||
import { removeDuplicatesFromArray } from "../../features/_utils/array.utils.js";
|
||||
|
||||
export const WindowsView = memo(() => {
|
||||
const settingsManager = useSettingsManager();
|
||||
|
|
@ -38,18 +39,33 @@ export const WindowsView = memo(() => {
|
|||
|
||||
// Launch startup apps
|
||||
useEffect(() => {
|
||||
if (windowsManager.startupComplete)
|
||||
return;
|
||||
|
||||
let startupAppNames = [];
|
||||
|
||||
// Get app name and params from URL query
|
||||
const params = getViewportParams();
|
||||
const appName = params.app;
|
||||
if (appName)
|
||||
startupAppNames.push(appName);
|
||||
delete params.app;
|
||||
|
||||
// Get list of app names from settings file
|
||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.apps);
|
||||
settings.get("startup", (value) => {
|
||||
if (value !== "")
|
||||
windowsManager.startup(value?.split(","));
|
||||
if (value !== "") {
|
||||
startupAppNames = value?.split(",").concat(startupAppNames);
|
||||
startupAppNames = removeDuplicatesFromArray(startupAppNames);
|
||||
}
|
||||
|
||||
windowsManager.startup(startupAppNames, params);
|
||||
});
|
||||
}, [settingsManager, windowsManager]);
|
||||
|
||||
// TO DO: prevent windows from being rerendered when order is changed
|
||||
|
||||
return (<div>
|
||||
{windows.map((window) => {
|
||||
const { id, app, size, position, options, minimized } = window;
|
||||
const { id, app, size, position, options, minimized, fullscreen } = window;
|
||||
const index = sortedWindows.indexOf(window);
|
||||
return <WindowView
|
||||
key={id}
|
||||
|
|
@ -67,6 +83,7 @@ export const WindowsView = memo(() => {
|
|||
event.stopPropagation();
|
||||
windowsManager.setMinimized(id, !minimized);
|
||||
}}
|
||||
fullscreen={fullscreen}
|
||||
/>;
|
||||
})}
|
||||
</div>);
|
||||
|
|
|
|||
|
|
@ -15,4 +15,12 @@ export function removeFromArray(item, array) {
|
|||
*/
|
||||
export function randomFromArray(array) {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*[]} array
|
||||
* @returns {*[]}
|
||||
*/
|
||||
export function removeDuplicatesFromArray(array) {
|
||||
return array.filter((item, index) => array.indexOf(item) === index);
|
||||
}
|
||||
|
|
@ -51,4 +51,63 @@ export function setViewportIcon(url) {
|
|||
document.head.appendChild(link);
|
||||
}
|
||||
link.href = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {URLSearchParams}
|
||||
*/
|
||||
export function getViewportParams() {
|
||||
const query = window.location.search.slice(1);
|
||||
|
||||
const params = {};
|
||||
query.split("&").forEach((param) => {
|
||||
// For some reason, URI components only decode when decoded twice
|
||||
// Please find a fix, or create a custom function
|
||||
const [key, value] = param.split("=").map((item) => decodeURIComponent(decodeURIComponent(item)));
|
||||
params[key] = value;
|
||||
});
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} options
|
||||
* @param {string} options.appId
|
||||
* @param {boolean} options.fullscreen
|
||||
*/
|
||||
export function generateUrl(options) {
|
||||
const baseUrl = window.location.origin + "/";
|
||||
|
||||
if (!options || Object.keys(options).length === 0)
|
||||
return baseUrl;
|
||||
|
||||
const { appId, fullscreen, ...extraOptions } = options;
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (appId)
|
||||
params.set("app", appId);
|
||||
if (fullscreen)
|
||||
params.set("fullscreen", fullscreen.toString());
|
||||
|
||||
if (extraOptions && Object.keys(extraOptions).length > 0) {
|
||||
Object.entries(extraOptions).forEach(([key, value]) => {
|
||||
if (key && value)
|
||||
params.set(key, encodeURIComponent(value));
|
||||
});
|
||||
}
|
||||
|
||||
if (params.size === 0)
|
||||
return baseUrl;
|
||||
|
||||
const url = `${baseUrl}?${params.toString()}`;
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} string
|
||||
* @param {Function} onSuccess
|
||||
* @param {Function} onFail
|
||||
*/
|
||||
export function copyToClipboard(string, onSuccess, onFail) {
|
||||
navigator.clipboard.writeText(string).then(onSuccess, onFail);
|
||||
}
|
||||
|
|
@ -83,4 +83,12 @@ export default class ModalsManager {
|
|||
get modalIds() {
|
||||
return Object.keys(this.modals);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @returns {string}
|
||||
*/
|
||||
static getModalIconUrl(name) {
|
||||
return `${process.env.PUBLIC_URL}/assets/modals/icons/${name}.svg`;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,17 @@ export default class WindowsManager {
|
|||
const position = new Vector2(randomRange(SCREEN_MARGIN, window.innerWidth - size.x - SCREEN_MARGIN),
|
||||
randomRange(SCREEN_MARGIN, window.innerHeight - size.y - SCREEN_MARGIN - TASKBAR_HEIGHT));
|
||||
|
||||
let fullscreen = false;
|
||||
if (options?.fullscreen) {
|
||||
if (typeof(options.fullscreen) == String) {
|
||||
fullscreen = options.fullscreen.toLowerCase() === "true";
|
||||
} else {
|
||||
fullscreen = options.fullscreen;
|
||||
}
|
||||
|
||||
delete options.fullscreen;
|
||||
}
|
||||
|
||||
let id = 0;
|
||||
while (this.windowIds.includes(id.toString())) {
|
||||
id++;
|
||||
|
|
@ -50,6 +61,7 @@ export default class WindowsManager {
|
|||
app,
|
||||
size,
|
||||
position,
|
||||
fullscreen,
|
||||
options,
|
||||
};
|
||||
|
||||
|
|
@ -192,12 +204,16 @@ export default class WindowsManager {
|
|||
this.updateWindows = updateWindows;
|
||||
}
|
||||
|
||||
startup(appIds) {
|
||||
/**
|
||||
* @param {string[]} appIds
|
||||
* @param {{}} options
|
||||
*/
|
||||
startup(appIds, options) {
|
||||
if (appIds == null || this.startupComplete)
|
||||
return;
|
||||
|
||||
appIds.forEach((appId) => {
|
||||
this.open(appId);
|
||||
this.open(appId, options);
|
||||
});
|
||||
|
||||
this.startupComplete = true;
|
||||
|
|
|
|||
|
|
@ -24,4 +24,4 @@ console.info(ASCII_LOGO + space + welcomeMessage);
|
|||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
reportWebVitals();
|
||||
|
|
@ -29,7 +29,7 @@ body {
|
|||
text-rendering: optimizelegibility;
|
||||
}
|
||||
|
||||
p, a, button, input, h1, h2, h3, h4, h5, h6 {
|
||||
p, a, button, input, select, h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--body-font-family);
|
||||
letter-spacing: normal;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue