Updated taskbar
This commit is contained in:
parent
5263cfd2b6
commit
861851872a
25 changed files with 231 additions and 108 deletions
|
|
@ -1,5 +1,3 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE xml>
|
||||
<options>
|
||||
<wallpaper>/media/wallpapers/mesh-gradient-wallpaper-red-purple.png</wallpaper>
|
||||
</options>
|
||||
3
public/config/taskbar.xml
Normal file
3
public/config/taskbar.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<options>
|
||||
<pins>terminal,settings,media-viewer,text-editor,file-explorer,wordle,balls,minesweeper</pins>
|
||||
</options>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import styles from "./App.module.css";
|
||||
import { Taskbar } from "./components/task-bar/TaskBar.jsx";
|
||||
import { Taskbar } from "./components/taskbar/Taskbar.jsx";
|
||||
import { WindowsManagerProvider } from "./hooks/windows/windowsManagerContext.js";
|
||||
import { WindowsView } from "./components/windows/WindowsView.jsx";
|
||||
import { VirtualRootProvider } from "./hooks/virtual-drive/virtualRootContext.js";
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from "react";
|
||||
import { useShortcuts } from "../../hooks/utils/keyboard.js";
|
||||
import style from "./Actions.module.css";
|
||||
import styles from "./Actions.module.css";
|
||||
import { useScreenDimensions } from "../../hooks/utils/screen.js";
|
||||
import { TASK_BAR_HEIGHT } from "../../constants/taskBar.js";
|
||||
import { TASKBAR_HEIGHT } from "../../constants/taskBar.js";
|
||||
|
||||
export const STYLES = {
|
||||
CONTEXT_MENU: style["Context-menu"],
|
||||
SHORTCUTS_LISTENER: style["Shortcuts-listener"],
|
||||
CONTEXT_MENU: styles["Context-menu"],
|
||||
SHORTCUTS_LISTENER: styles["Shortcuts-listener"],
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -16,7 +16,7 @@ export const STYLES = {
|
|||
* @param {Function} props.onAnyTrigger
|
||||
* @param {import("react").ElementType} props.children
|
||||
* @param {*} props.triggerParams
|
||||
* @param {boolean} props.avoidTaskBar
|
||||
* @param {boolean} props.avoidTaskbar
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -31,7 +31,7 @@ export const STYLES = {
|
|||
* }}
|
||||
* />
|
||||
*/
|
||||
export function Actions({ children, className, onAnyTrigger, triggerParams, avoidTaskBar = true }) {
|
||||
export function Actions({ children, className, onAnyTrigger, triggerParams, avoidTaskbar = true }) {
|
||||
const isListener = (className === STYLES.SHORTCUTS_LISTENER);
|
||||
|
||||
const ref = useRef(null);
|
||||
|
|
@ -88,8 +88,8 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
|||
const maxX = screenWidth;
|
||||
let maxY = screenHeight;
|
||||
|
||||
if (avoidTaskBar)
|
||||
maxY -= TASK_BAR_HEIGHT;
|
||||
if (avoidTaskbar)
|
||||
maxY -= TASKBAR_HEIGHT;
|
||||
|
||||
const isOverflowingRight = (rect.x + rect.width > maxX);
|
||||
const isOverflowingBottom = (rect.y + rect.height > maxY);
|
||||
|
|
@ -100,20 +100,20 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
|
|||
setAlignTop(true);
|
||||
|
||||
setInitiated(true);
|
||||
}, [alignLeft, avoidTaskBar, screenHeight, screenWidth]);
|
||||
}, [alignLeft, avoidTaskbar, screenHeight, screenWidth]);
|
||||
|
||||
if (isListener)
|
||||
return iterateOverChildren(children);
|
||||
|
||||
const classNames = [style.Container];
|
||||
const classNames = [styles.Container];
|
||||
if (className != null)
|
||||
classNames.push(className);
|
||||
if (alignLeft)
|
||||
classNames.push(style["Align-left"]);
|
||||
classNames.push(styles["Align-left"]);
|
||||
if (alignTop)
|
||||
classNames.push(style["Align-top"]);
|
||||
classNames.push(styles["Align-top"]);
|
||||
if (!initiated)
|
||||
classNames.push(style.Uninitiated);
|
||||
classNames.push(styles.Uninitiated);
|
||||
|
||||
return (<div ref={ref} className={classNames.join(" ")}>
|
||||
{iterateOverChildren(children)}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
import { memo, useEffect, useState } from "react";
|
||||
import Application from "../../features/applications/application.js";
|
||||
import { useWindows } from "../../hooks/windows/windowsContext.js";
|
||||
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
|
||||
import styles from "./TaskBar.module.css";
|
||||
import { ReactSVG } from "react-svg";
|
||||
|
||||
/**
|
||||
* @param {object} props
|
||||
* @param {Application} props.app
|
||||
*/
|
||||
export const AppButton = memo(({ app }) => {
|
||||
const [active, setActive] = useState(false);
|
||||
const windows = useWindows();
|
||||
const windowsManager = useWindowsManager();
|
||||
|
||||
useEffect(() => {
|
||||
setActive(windowsManager.isAppActive(app.id));
|
||||
}, [app.id, windows, windowsManager]);
|
||||
|
||||
const classNames = [styles["App-icon"]];
|
||||
if (active)
|
||||
classNames.push(styles.Active);
|
||||
|
||||
return (
|
||||
<button
|
||||
key={app.id}
|
||||
tabIndex={0}
|
||||
className={classNames.join(" ")}
|
||||
onClick={() => { windowsManager.open(app.id); }}
|
||||
title={app.name}
|
||||
>
|
||||
<ReactSVG src={`${process.env.PUBLIC_URL}/media/applications/icons/${app.id}.svg`}/>
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { memo, useRef, useState } from "react";
|
||||
import styles from "./TaskBar.module.css";
|
||||
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
||||
import styles from "./Taskbar.module.css";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCog, faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||
import AppsManager from "../../features/applications/applications.js";
|
||||
|
|
@ -12,7 +12,7 @@ import { Volume } from "./indicators/Volume.jsx";
|
|||
import { SearchMenu } from "./menus/SearchMenu.jsx";
|
||||
import { Calendar } from "./indicators/Calendar.jsx";
|
||||
import { useScrollWithShadow } from "../../hooks/utils/scrollWithShadows.js";
|
||||
import { AppButton } from "./AppButton.jsx";
|
||||
import { AppButton } from "./app-icon/AppIcon.jsx";
|
||||
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
|
||||
import { Actions } from "../actions/Actions.jsx";
|
||||
import { useModals } from "../../hooks/modals/modals.js";
|
||||
|
|
@ -20,13 +20,17 @@ import { ClickAction } from "../actions/actions/ClickAction.jsx";
|
|||
import { APPS, APP_NAMES } from "../../constants/applications.js";
|
||||
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
|
||||
import { ModalsView } from "../modals/ModalsView.jsx";
|
||||
import { TASK_BAR_HEIGHT } from "../../constants/taskBar.js";
|
||||
import { TASKBAR_HEIGHT } from "../../constants/taskBar.js";
|
||||
import { useSettingsManager } from "../../hooks/settings/settingsManagerContext.js";
|
||||
import { SettingsManager } from "../../features/settings/settingsManager.js";
|
||||
import { useWindows } from "../../hooks/windows/windowsContext.js";
|
||||
|
||||
export const Taskbar = memo(() => {
|
||||
const ref = useRef(null);
|
||||
const settingsManager = useSettingsManager();
|
||||
const [showHome, setShowHome] = useState(false);
|
||||
const [showSearch, setShowSearch] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const ref = useRef(null);
|
||||
const { boxShadow, onUpdate } = useScrollWithShadow({ ref, shadow: {
|
||||
offset: 20,
|
||||
blurRadius: 10,
|
||||
|
|
@ -36,13 +40,49 @@ export const Taskbar = memo(() => {
|
|||
const inputRef = useRef(null);
|
||||
const [modalsManager, modals] = useModals();
|
||||
const windowsManager = useWindowsManager();
|
||||
const windows = useWindows();
|
||||
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||
<Actions avoidTaskBar={false} {...props}>
|
||||
<Actions avoidTaskbar={false} {...props}>
|
||||
<ClickAction label={`Open ${APP_NAMES.SETTINGS}`} icon={faCog} onTrigger={() => {
|
||||
windowsManager.open(APPS.SETTINGS);
|
||||
}}/>
|
||||
</Actions>
|
||||
});
|
||||
const [pins, setPins] = useState([]);
|
||||
|
||||
const apps = useMemo(() => AppsManager.APPLICATIONS.sort((appA, appB) => {
|
||||
const indexA = pins.indexOf(appA.id);
|
||||
const indexB = pins.indexOf(appB.id);
|
||||
if (indexA < 0 && indexB > 0) {
|
||||
return 1;
|
||||
} else if (indexA > 0 && indexB < 0) {
|
||||
return -1;
|
||||
} else if (indexA < 0 && indexB < 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return indexA - indexB;
|
||||
}
|
||||
}).map((app) => {
|
||||
const isActive = windows.map((window) => window.app.id).includes(app.id);
|
||||
const shouldBeShown = (pins.includes(app.id) || isActive);
|
||||
return (<AppButton
|
||||
modalsManager={modalsManager}
|
||||
pins={pins}
|
||||
app={app}
|
||||
key={app.id}
|
||||
active={isActive}
|
||||
visible={shouldBeShown}
|
||||
/>);
|
||||
}), [modalsManager, pins, windows]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
||||
settings.get("pins", (pins) => {
|
||||
setPins(pins.split(","));
|
||||
});
|
||||
})();
|
||||
}, [settingsManager]);
|
||||
|
||||
const updateShowHome = (value) => {
|
||||
setShowHome(value);
|
||||
|
|
@ -82,8 +122,8 @@ export const Taskbar = memo(() => {
|
|||
return (<>
|
||||
<ModalsView modalsManager={modalsManager} modals={modals}/>
|
||||
<div
|
||||
style={{ "--task-bar-height": `${TASK_BAR_HEIGHT}px` }}
|
||||
className={styles["Task-bar"]}
|
||||
style={{ "--taskbar-height": `${TASKBAR_HEIGHT}px` }}
|
||||
className={styles["Taskbar"]}
|
||||
data-allow-context-menu={true}
|
||||
onContextMenu={(event) => {
|
||||
if (event.target.getAttribute("data-allow-context-menu"))
|
||||
|
|
@ -132,9 +172,7 @@ export const Taskbar = memo(() => {
|
|||
onResize={onUpdate}
|
||||
ref={ref}
|
||||
>
|
||||
{AppsManager.APPLICATIONS.map((app) =>
|
||||
<AppButton app={app} key={app.id}/>
|
||||
)}
|
||||
{apps}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["Util-icons"]}>
|
||||
|
|
@ -142,7 +180,7 @@ export const Taskbar = memo(() => {
|
|||
<Network/>
|
||||
<Volume/>
|
||||
<Calendar/>
|
||||
<button title="View Desktop" id="desktop-button"/>
|
||||
<button title="Show Desktop" id="desktop-button"/>
|
||||
</div>
|
||||
</div>
|
||||
</>);
|
||||
|
|
@ -1,22 +1,22 @@
|
|||
:root {
|
||||
--task-bar-height: 3rem;
|
||||
--task-bar-color: rgba(0, 0, 0, 75%);
|
||||
--task-bar-button-hover-color: rgba(255, 255, 255, 5%);
|
||||
--taskbar-height: 3rem;
|
||||
--taskbar-color: rgba(0, 0, 0, 75%);
|
||||
--taskbar-button-hover-color: rgba(255, 255, 255, 5%);
|
||||
}
|
||||
|
||||
.Task-bar {
|
||||
.Taskbar {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: var(--task-bar-height);
|
||||
background-color: var(--task-bar-color);
|
||||
height: var(--taskbar-height);
|
||||
background-color: var(--taskbar-color);
|
||||
backdrop-filter: blur(15px);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.Task-bar button {
|
||||
.Taskbar button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--foreground-color-a);
|
||||
|
|
@ -28,9 +28,9 @@
|
|||
z-index: -1;
|
||||
}
|
||||
|
||||
.Task-bar button:hover,
|
||||
.Task-bar button:focus-visible {
|
||||
background-color: var(--task-bar-button-hover-color);
|
||||
.Taskbar button:hover,
|
||||
.Taskbar button:focus-visible {
|
||||
background-color: var(--taskbar-button-hover-color);
|
||||
}
|
||||
|
||||
.Home-container,
|
||||
|
|
@ -94,35 +94,6 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
.App-icon {
|
||||
position: relative;
|
||||
width: var(--task-bar-height);
|
||||
height: var(--task-bar-height);
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.App-icon::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
width: 90%;
|
||||
height: 0.15rem;
|
||||
background-color: var(--grey-a);
|
||||
transition: height 200ms ease-in-out, width 200ms ease-in-out;
|
||||
transform: translateX(-50%);
|
||||
-webkit-transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.App-icon:hover::after,
|
||||
.App-icon:focus-visible::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.App-icon:not(.Active)::after {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.Util-icons {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
66
src/components/taskbar/app-icon/AppIcon.jsx
Normal file
66
src/components/taskbar/app-icon/AppIcon.jsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { memo } from "react";
|
||||
import Application from "../../../features/applications/application.js";
|
||||
import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext.js";
|
||||
import styles from "./AppIcon.module.css";
|
||||
import { ReactSVG } from "react-svg";
|
||||
import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext.js";
|
||||
import { useContextMenu } from "../../../hooks/modals/contextMenu.js";
|
||||
import ModalsManager from "../../../features/modals/modals.js";
|
||||
import { Actions } from "../../actions/Actions.jsx";
|
||||
import { ClickAction } from "../../actions/actions/ClickAction.jsx";
|
||||
import { faThumbTack, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
import { SettingsManager } from "../../../features/settings/settingsManager.js";
|
||||
import { removeFromArray } from "../../../features/utils/array.js";
|
||||
|
||||
/**
|
||||
* @param {object} props
|
||||
* @param {Application} props.app
|
||||
* @param {ModalsManager} props.modalsManager
|
||||
* @param {string[]} props.pins
|
||||
*/
|
||||
export const AppButton = memo(({ app, modalsManager, pins, active, visible }) => {
|
||||
const isPinned = pins.includes(app.id);
|
||||
|
||||
const windowsManager = useWindowsManager();
|
||||
const settingsManager = useSettingsManager();
|
||||
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
|
||||
<Actions avoidTaskbar={false} {...props}>
|
||||
<ClickAction label={isPinned ? "Unpin from taskbar" : "Pin to taskbar"} icon={faThumbTack} onTrigger={() => {
|
||||
const newPins = [...pins];
|
||||
if (isPinned) {
|
||||
removeFromArray(app.id, pins);
|
||||
} else {
|
||||
newPins.push(app.id);
|
||||
}
|
||||
|
||||
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
|
||||
settings.set("pins", newPins.join(","));
|
||||
}}/>
|
||||
{active && <ClickAction label="Close window" icon={faTimes} onTrigger={() => {
|
||||
windowsManager.close(windowsManager.getAppWindowId(app.id));
|
||||
}}/>}
|
||||
</Actions>
|
||||
});
|
||||
|
||||
const classNames = [styles["App-icon"]];
|
||||
if (active)
|
||||
classNames.push(styles.Active);
|
||||
if (!visible)
|
||||
classNames.push(styles.Hidden);
|
||||
|
||||
return (
|
||||
<button
|
||||
key={app.id}
|
||||
tabIndex={0}
|
||||
className={classNames.join(" ")}
|
||||
onClick={() => { windowsManager.open(app.id); }}
|
||||
onContextMenu={(event) => {
|
||||
if (visible)
|
||||
onContextMenu(event);
|
||||
}}
|
||||
title={app.name}
|
||||
>
|
||||
<ReactSVG src={`${process.env.PUBLIC_URL}/media/applications/icons/${app.id}.svg`}/>
|
||||
</button>
|
||||
);
|
||||
});
|
||||
58
src/components/taskbar/app-icon/AppIcon.module.css
Normal file
58
src/components/taskbar/app-icon/AppIcon.module.css
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
.App-icon {
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
width: var(--taskbar-height);
|
||||
height: var(--taskbar-height);
|
||||
aspect-ratio: 1;
|
||||
animation: appear 100ms ease-out forwards;
|
||||
}
|
||||
|
||||
.App-icon.Hidden {
|
||||
animation: disappear 400ms ease-in-out forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes disappear {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
margin-right: calc(var(--taskbar-height) * -1);
|
||||
}
|
||||
}
|
||||
|
||||
.App-icon::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
width: 90%;
|
||||
height: 0.15rem;
|
||||
background-color: var(--grey-a);
|
||||
transition: height 200ms ease-in-out, width 200ms ease-in-out;
|
||||
transform: translateX(-50%);
|
||||
-webkit-transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.App-icon:hover::after,
|
||||
.App-icon:focus-visible::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.App-icon:not(.Active)::after {
|
||||
height: 0;
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
.Window-container.Maximized {
|
||||
width: 100%;
|
||||
height: calc(100% - var(--task-bar-height));
|
||||
height: calc(100% - var(--taskbar-height));
|
||||
border-radius: 0;
|
||||
transform: none !important;
|
||||
resize: none;
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const TASK_BAR_HEIGHT = 3 * 16;
|
||||
export const TASKBAR_HEIGHT = 3 * 16;
|
||||
|
|
@ -6,7 +6,8 @@ export class SettingsManager {
|
|||
* @type {Object<string, string>}
|
||||
*/
|
||||
static VIRTUAL_PATHS = {
|
||||
"desktop": "~/.config/desktop.xml"
|
||||
desktop: "~/.config/desktop.xml",
|
||||
taskbar: "~/.config/taskbar.xml"
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ export class VirtualRoot extends VirtualFolder {
|
|||
.createFolder(".config", (folder) => {
|
||||
folder.createFile("desktop", "xml", (file) => {
|
||||
file.setSource("/config/desktop.xml");
|
||||
}).createFile("taskbar", "xml", (file) => {
|
||||
file.setSource("/config/taskbar.xml");
|
||||
});
|
||||
})
|
||||
.createFolder("Images", (folder) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TASK_BAR_HEIGHT } from "../../constants/taskBar.js";
|
||||
import { TASKBAR_HEIGHT } from "../../constants/taskBar.js";
|
||||
import { SCREEN_MARGIN } from "../../constants/windows.js";
|
||||
import AppsManager from "../applications/applications.js";
|
||||
import { randomRange } from "../math/random.js";
|
||||
|
|
@ -26,7 +26,7 @@ export default class WindowsManager {
|
|||
|
||||
const size = options?.size ?? app.windowOptions?.size ?? new Vector2(700, 400);
|
||||
const position = new Vector2(randomRange(SCREEN_MARGIN, window.innerWidth - size.x - SCREEN_MARGIN),
|
||||
randomRange(SCREEN_MARGIN, window.innerHeight - size.y - SCREEN_MARGIN - TASK_BAR_HEIGHT));
|
||||
randomRange(SCREEN_MARGIN, window.innerHeight - size.y - SCREEN_MARGIN - TASKBAR_HEIGHT));
|
||||
|
||||
let id = 0;
|
||||
while (this.windowIds.includes(id.toString())) {
|
||||
|
|
@ -46,6 +46,8 @@ export default class WindowsManager {
|
|||
lastInteraction: new Date().valueOf()
|
||||
};
|
||||
|
||||
app.isActive = true;
|
||||
|
||||
this.updateWindows(this.windows);
|
||||
return this.windows[id];
|
||||
// console.log(this);
|
||||
|
|
@ -72,6 +74,9 @@ export default class WindowsManager {
|
|||
console.warn(`Failed to close window ${windowId}: window not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { app } = this.windows[windowId];
|
||||
app.isActive = this.isAppActive(app.id);
|
||||
|
||||
console.info(`Closing window ${windowId}`);
|
||||
delete this.windows[windowId];
|
||||
|
|
@ -114,6 +119,23 @@ export default class WindowsManager {
|
|||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} appId
|
||||
* @returns {string}
|
||||
*/
|
||||
getAppWindowId(appId) {
|
||||
let windowId = null;
|
||||
|
||||
Object.values(this.windows).forEach((window) => {
|
||||
if (window.app.id === appId) {
|
||||
windowId = window.id;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return windowId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} updateWindows
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue