This thread contains a patchset. You're looking at the original emails,
but you may wish to use the patch review UI.
Review patch
3
2
[PATCH crankshaft v2 1/3] Integrate plugins into gamepadui
---
.../menu-manager/deck/menu-injector-deck.css | 13 +---
.../menu-manager/deck/menu-injector-deck.tsx | 70 +++++--------------
injected/src/smm.ts | 18 +++++
injected/src/tab-observer.ts | 5 ++
injected/src/types/global.d.ts | 2 +
5 files changed, 46 insertions(+), 62 deletions(-)
diff --git a/injected/src/menu-manager/deck/menu-injector-deck.css b/injected/src/menu-manager/deck/menu-injector-deck.css
index 4f78a23..2621f37 100644
--- a/injected/src/menu-manager/deck/menu-injector-deck.css
@@ -1,21 +1,12 @@
[data-smm-menu-page-container] {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: auto;
- z-index: 999;
-
width: 100%;
- height: calc(100% - 40px);
+ height: 100%;
+ padding-top: calc(0px + var(--basicui-header-height));
display: flex;
overflow: auto;
background-color: #23262e;
- opacity: 0;
-
- pointer-events: none;
}
[data-smm-menu-page-container] > ul:first-child {
diff --git a/injected/src/menu-manager/deck/menu-injector-deck.tsx b/injected/src/menu-manager/deck/menu-injector-deck.tsx
index 67b7497..f6949a9 100644
--- a/injected/src/menu-manager/deck/menu-injector-deck.tsx
@@ -34,6 +34,7 @@ export class MenuInjectorDeck implements MenuInjector {
this.createPageContainer();
this.addPluginsMenuItem();
this.listenToClickEvents();
+ this.listenToNavigationChanges();
}
private injectMenuStyles() {
@@ -56,21 +57,24 @@ export class MenuInjectorDeck implements MenuInjector {
{this.menuPage}
</div>
);
-
- document
- .querySelector<HTMLDivElement>(DECK_SELECTORS.mainNavMenu)
- ?.appendChild(this.pageContainer);
}
private listenToClickEvents() {
this.smm.IPC.on<{ id: string }>(
'csMenuItemClicked',
async ({ data: { id: _id } }) => {
- this.openPluginsPage();
+ window.coolClass.Navigate('/blank/cs-plugins');
+ window.coolClass.CloseSideMenus();
}
);
}
+ private listenToNavigationChanges() {
+ this.smm.addEventListener('switchToPlugins', () => {
+ this.openPluginsPage();
+ });
+ }
+
private addPluginsMenuItem() {
window.csMenuItems = [
{
@@ -82,23 +86,11 @@ export class MenuInjectorDeck implements MenuInjector {
}
private openPluginsPage() {
- window.csMenuActiveItem = 'plugins';
-
- // Close menu
- window.coolClass.OpenSideMenu();
+ if (this.pageContainer.isConnected) return;
- // Make sure we're on a page where we can show the plugin page
- // (we'll navigate back when the page is closed)
- if (
- document.querySelector(DECK_SELECTORS.topLevelTransitionSwitch)?.children
- ?.length === 0
- ) {
- window.coolClass.NavigateToLibraryTab();
- this.enteredWithNavigate = true;
- }
+ window.csMenuActiveItem = 'plugins';
this.showPageContainer();
-
this.menuListGamepad = new GamepadHandler({
smm: this.smm,
root: this.menuList,
@@ -110,44 +102,20 @@ export class MenuInjectorDeck implements MenuInjector {
});
}
- private async closePluginsPage() {
- // Fade out the plugin page before removing it
- const animation = await this.pageContainer.animate([{ opacity: 0 }], {
- duration: 300,
- fill: 'forwards',
- }).finished;
- this.hidePageContainer();
- animation.cancel();
-
+ private async closePluginsPage(forward = false) {
// Clear active menu item
window.csMenuActiveItem = undefined;
window.csMenuUpdate?.();
- if (this.enteredWithNavigate) {
- window.coolClass.NavigateBackOrOpenMenu();
- }
- }
+ deleteAll('[data-smm-menu-page-container]');
- private showPageContainer() {
- this.pageContainer.style.opacity = '1';
- this.pageContainer.style.pointerEvents = 'all';
- const header = document.querySelector<HTMLDivElement>(
- DECK_SELECTORS.header
- );
- if (header) {
- header.style.display = 'none';
- }
+ if (!forward) window.coolClass.NavigateBackOrOpenMenu();
}
- private hidePageContainer() {
- this.pageContainer.style.opacity = '0';
- this.pageContainer.style.pointerEvents = 'none';
- const header = document.querySelector<HTMLDivElement>(
- DECK_SELECTORS.header
- );
- if (header) {
- header.style.display = 'flex';
- }
+ private showPageContainer() {
+ document
+ .querySelector<HTMLDivElement>(DECK_SELECTORS.topLevelTransitionSwitch)
+ ?.appendChild(this.pageContainer);
}
createMenuItem({ id, label, render }: MenuItem) {
@@ -181,7 +149,7 @@ export class MenuInjectorDeck implements MenuInjector {
closeActivePage() {
this.activePluginGamepad?.cleanup();
this.menuListGamepad?.cleanup();
- this.closePluginsPage();
+ this.closePluginsPage(true);
}
private openPluginPage(render: MenuItem['render']) {
diff --git a/injected/src/smm.ts b/injected/src/smm.ts
index a375f9b..2689405 100644
--- a/injected/src/smm.ts
+++ b/injected/src/smm.ts
@@ -29,6 +29,7 @@ type SMMEventType =
| typeof eventTypeSwitchToCollections
| typeof eventTypeSwitchToAppDetails
| typeof eventTypeSwitchToAppProperties
+ | typeof eventTypeSwitchToPlugins
| typeof eventTypeLockScreenOpened
| typeof eventTypeLockScreenClosed;
@@ -38,6 +39,7 @@ type SMMEvent =
| EventSwitchToCollections
| EventSwitchToAppDetails
| EventSwitchToAppProperties
+ | EventSwitchToPlugins
| EventLockScreenOpened
| EventLockScreenClosed;
@@ -77,6 +79,13 @@ class EventSwitchToAppProperties extends CustomEvent<AppPropsApp> {
}
}
+const eventTypeSwitchToPlugins = 'switchToPlugins' as const;
+class EventSwitchToPlugins extends CustomEvent<void> {
+ constructor() {
+ super(eventTypeSwitchToPlugins);
+ }
+}
+
const eventTypeLockScreenOpened = 'lockScreenOpened' as const;
class EventLockScreenOpened extends CustomEvent<void> {
constructor() {
@@ -275,6 +284,15 @@ export class SMM extends EventTarget {
this.dispatchEvent(new EventSwitchToAppProperties(app));
}
+ /**
+ * @internal
+ */
+ switchToPlugins() {
+ info('Switched to plugins');
+
+ this.dispatchEvent(new EventSwitchToPlugins());
+ }
+
/**
* @internal
*/
diff --git a/injected/src/tab-observer.ts b/injected/src/tab-observer.ts
index c66c3eb..d8ea9ee 100644
--- a/injected/src/tab-observer.ts
+++ b/injected/src/tab-observer.ts
@@ -81,6 +81,11 @@ export const createTabObserver = (smm: SMM, mainLibraryEl: HTMLElement) => {
}
}
+ if (location.pathname === '/routes/blank/cs-plugins') {
+ smm.switchToPlugins();
+ return;
+ }
+
smm.switchToUnknownPage();
});
diff --git a/injected/src/types/global.d.ts b/injected/src/types/global.d.ts
index 0b0c628..f082cbf 100644
--- a/injected/src/types/global.d.ts
+++ b/injected/src/types/global.d.ts
@@ -68,9 +68,11 @@ declare global {
// other/none = close menu
OpenSideMenu: (menu?: number) => void;
ToggleSideMenu: (menu?: number) => void;
+ CloseSideMenus: () => void;
// Currently open menu (same number values as above)
m_eOpenSideMenu?: number;
+ Navigate: (target: string) => void;
NavigateToLibraryTab: () => void;
NavigateBackOrOpenMenu: () => void;
--
2.37.3
[PATCH crankshaft v2 2/3] Restyle plugins page to match Steam Deck
---
injected/src/gamepad/gamepad.ts | 16 +-
.../menu-manager/deck/menu-injector-deck.css | 138 +++++++++++++++---
.../menu-manager/deck/menu-injector-deck.tsx | 98 +++++++++++--
injected/src/ui/buttons.tsx | 2 +-
injected/src/ui/index.ts | 3 +-
5 files changed, 217 insertions(+), 40 deletions(-)
diff --git a/injected/src/gamepad/gamepad.ts b/injected/src/gamepad/gamepad.ts
index a7577c5..fbd2b67 100644
--- a/injected/src/gamepad/gamepad.ts
+++ b/injected/src/gamepad/gamepad.ts
@@ -79,23 +79,27 @@ export class GamepadHandler {
async cleanup() {
await this.smm.ButtonInterceptors.removeAfter(gamepadRoot(this.id));
await this.smm.ButtonInterceptors.removeInterceptor(gamepadRoot(this.id));
- this.root
- .querySelectorAll('.cs-gp-focus')
- .forEach((node) => node.classList.remove('cs-gp-focus'));
+ this.root.querySelectorAll<HTMLElement>('.cs-gp-focus').forEach((node) => {
+ node.classList.remove('cs-gp-focus');
+ node.blur();
+ });
}
updateFocused(newFocusPath: string) {
const curFocus = this.tree[this.focusPath];
if (curFocus) {
curFocus.el.classList.remove('cs-gp-focus');
+ curFocus.el.blur();
}
- document
- .querySelectorAll('.cs-gp-focus')
- .forEach((node) => node.classList.remove('cs-gp-focus'));
+ document.querySelectorAll<HTMLElement>('.cs-gp-focus').forEach((node) => {
+ node.classList.remove('cs-gp-focus');
+ node.blur();
+ });
const newFocusEl = this.tree[newFocusPath].el;
newFocusEl.classList.add('cs-gp-focus');
+ newFocusEl.focus();
this.focusPath = newFocusPath;
diff --git a/injected/src/menu-manager/deck/menu-injector-deck.css b/injected/src/menu-manager/deck/menu-injector-deck.css
index 2621f37..c2f0150 100644
--- a/injected/src/menu-manager/deck/menu-injector-deck.css
@@ -9,47 +9,110 @@
background-color: #23262e;
}
-[data-smm-menu-page-container] > ul:first-child {
- flex: 1 0 auto;
+[data-smm-menu-page-container].animate-in {
+ transition-property: opacity, transform;
+ transition-duration: 400ms;
+ transition-timing-function: cubic-bezier(0, 0, 0.1, 1);
+ transition-delay: 100ms;
+}
+[data-smm-menu-page-container].animate-out {
+ transition-property: opacity, transform;
+ transition-duration: 100ms;
+ transition-timing-function: cubic-bezier(0.6, 0, 1, 1);
+}
+[data-smm-menu-page-container].animate-in-end,
+[data-smm-menu-page-container].animate-out-start {
+ opacity: 1;
+ transform: scale(1);
+}
+[data-smm-menu-page-container].animate-out-end,
+[data-smm-menu-page-container].animate-in-start {
+ opacity: 0;
+ transform: scale(0.9);
+}
- max-width: 200px;
- height: 100%;
+[data-smm-menu-page-container] h1 {
+ font-size: 22px !important;
+}
+
+[data-smm-menu-page-container] h2 {
+ font-size: 16px;
+}
+
+[data-smm-menu-page-container] > ul:first-child {
+ min-width: 240px;
+ max-width: 40%;
+ height: calc(100% - 16px * 2);
margin: 0;
- padding: 0;
+ padding: 16px 0;
list-style: none;
- background-color: rgba(255, 255, 255, 2%);
+
+ background: #23262e;
+ border-right: 1px solid #0e141b;
+
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ overflow-x: hidden;
}
[data-smm-menu-page-container] > ul:first-child > li {
cursor: pointer;
border: solid 1px transparent;
-
- transition: all 200ms;
}
[data-smm-menu-page-container] > ul:first-child > li.cs-gp-focus {
outline: none;
- border-color: white;
+}
+
+[data-smm-menu-page-container]
+ > ul:first-child
+ > li.cs-gp-focus
+ > .smm-menu-item-button {
+ transform: scale(1.1);
+ background: #3d4450;
}
.smm-menu-item-button {
width: 100%;
- padding: 8px 24px;
+ border: none;
+ background: none;
- background-color: rgba(255, 255, 255, 2%);
- color: rgba(255, 255, 255, 90%);
+ font-weight: normal;
font-size: 16px;
- border: none;
+ font-style: normal;
+ line-height: 20px;
+ text-align: left;
+ text-decoration: none;
+ text-indent: 0;
+ text-shadow: none;
+ text-transform: none;
+ letter-spacing: 0px;
+ color: #fff;
+ padding: 10px calc(12px + 1.4vw);
+ color: #b8bcbf;
cursor: pointer;
-
- transition: all 150ms;
+ display: flex;
+ flex-direction: row;
+ transform: scale(1) rotateX(1deg);
+ line-height: 22px;
+ scroll-margin: 2.5em 0;
+ transition-property: transform, background-color;
+ transition-duration: 0.32s, 0s;
+ transition-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83);
+ transform-origin: 12% 50%;
+ animation-timing-function: cubic-bezier(0.17, 0.45, 0.14, 0.83);
+ animation-duration: 0.5s;
+ animation-fill-mode: forwards;
+ transform: scale(1) rotateX(1deg);
}
.smm-menu-item-button:hover {
- background-color: rgba(255, 255, 255, 4%);
+ background-color: #23262e;
}
.smm-menu-item-button.active {
@@ -60,5 +123,46 @@
width: 100%;
height: 100%;
overflow: auto;
- background-color: #23262e;
+ background: #0e141b;
+ position: relative;
+}
+
+[data-smm-plugin-page] {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+[data-smm-plugin-page].animate-out-top,
+[data-smm-plugin-page].animate-out-bottom {
+ transition-property: opacity, transform;
+ transition-duration: 80ms;
+ transition-timing-function: cubic-bezier(0.6, 0, 1, 1);
+}
+[data-smm-plugin-page].animate-in-top,
+[data-smm-plugin-page].animate-in-bottom {
+ transition-property: opacity, transform;
+ transition-duration: 320ms;
+ transition-timing-function: cubic-bezier(0, 0, 0.1, 1);
+ transition-delay: 80ms;
+}
+
+[data-smm-plugin-page].animate-out-top-start,
+[data-smm-plugin-page].animate-out-bottom-start,
+[data-smm-plugin-page].animate-in-top-end,
+[data-smm-plugin-page].animate-in-bottom-end {
+ opacity: 1;
+ transform: translateY(0);
+}
+[data-smm-plugin-page].animate-out-bottom-end,
+[data-smm-plugin-page].animate-in-top-start {
+ opacity: 0;
+ transform: translateY(-8%);
+}
+[data-smm-plugin-page].animate-out-top-end,
+[data-smm-plugin-page].animate-in-bottom-start {
+ opacity: 0;
+ transform: translateY(8%);
}
diff --git a/injected/src/menu-manager/deck/menu-injector-deck.tsx b/injected/src/menu-manager/deck/menu-injector-deck.tsx
index f6949a9..f8a82a8 100644
--- a/injected/src/menu-manager/deck/menu-injector-deck.tsx
@@ -8,6 +8,21 @@ import styles from './menu-injector-deck.css';
// @use-dom-chef
+const animate = (
+ node: HTMLElement,
+ animationName: string,
+ duration: number
+) => {
+ node.classList.add(animationName, `${animationName}-start`);
+ setTimeout(() => {
+ node.classList.remove(`${animationName}-start`);
+ node.classList.add(`${animationName}-end`);
+ }, 0);
+ setTimeout(() => {
+ node.classList.remove(animationName, `${animationName}-end`);
+ }, duration);
+};
+
export class MenuInjectorDeck implements MenuInjector {
private readonly smm: SMM;
private readonly menuManager: MenuManager;
@@ -18,17 +33,21 @@ export class MenuInjectorDeck implements MenuInjector {
// List of plugins in page
private menuList!: HTMLUListElement;
private menuItemNodes: Record<string, HTMLLIElement>;
+ private menuItems: MenuItem[];
// Root to render plugin contents
private menuPage!: HTMLDivElement;
private menuListGamepad?: GamepadHandler;
private activePluginGamepad?: GamepadHandler;
+ private activePluginId?: string;
+ private activePluginEl?: HTMLDivElement;
constructor(smm: SMM, menuManager: MenuManager) {
this.smm = smm;
this.menuManager = menuManager;
this.enteredWithNavigate = false;
this.menuItemNodes = {};
+ this.menuItems = [];
this.injectMenuStyles();
this.createPageContainer();
@@ -103,11 +122,13 @@ export class MenuInjectorDeck implements MenuInjector {
}
private async closePluginsPage(forward = false) {
+ this.activePluginId = undefined;
+ this.activePluginEl = undefined;
// Clear active menu item
window.csMenuActiveItem = undefined;
window.csMenuUpdate?.();
- deleteAll('[data-smm-menu-page-container]');
+ this.hidePageContainer();
if (!forward) window.coolClass.NavigateBackOrOpenMenu();
}
@@ -116,11 +137,19 @@ export class MenuInjectorDeck implements MenuInjector {
document
.querySelector<HTMLDivElement>(DECK_SELECTORS.topLevelTransitionSwitch)
?.appendChild(this.pageContainer);
+ animate(this.pageContainer, 'animate-in', 600);
}
- createMenuItem({ id, label, render }: MenuItem) {
+ private hidePageContainer() {
+ animate(this.pageContainer, 'animate-out', 100);
+ setTimeout(() => deleteAll('[data-smm-menu-page-container]'), 100);
+ }
+
+ createMenuItem(menuItem: MenuItem) {
+ const { id, label, render } = menuItem;
const newMenuItem = dcCreateElement<HTMLLIElement>(
<li
+ tabIndex={0}
smm-menu-item={id}
data-cs-gp-in-group="root"
data-cs-gp-item={id}
@@ -128,7 +157,10 @@ export class MenuInjectorDeck implements MenuInjector {
Object.values(this.menuItemNodes).length === 0 ? 'true' : 'false'
}
onClick={() => {
- this.openPluginPage(render);
+ this.openPluginPage(id, render, true);
+ }}
+ onFocus={() => {
+ this.openPluginPage(id, render);
}}
>
<button className="smm-menu-item-button" data-smm-menu-item-button={id}>
@@ -139,11 +171,13 @@ export class MenuInjectorDeck implements MenuInjector {
this.menuItemNodes[id] = newMenuItem;
this.menuList.appendChild(newMenuItem);
+ this.menuItems.push(menuItem);
}
removeMenuItem(id: string) {
this.menuItemNodes[id]?.remove();
delete this.menuItemNodes[id];
+ this.menuItems = this.menuItems.filter((item) => item.id !== id);
}
closeActivePage() {
@@ -152,17 +186,51 @@ export class MenuInjectorDeck implements MenuInjector {
this.closePluginsPage(true);
}
- private openPluginPage(render: MenuItem['render']) {
- [...this.menuPage.children].forEach((node) => node.remove());
- render(this.smm, this.menuPage);
- this.activePluginGamepad = new GamepadHandler({
- smm: this.smm,
- root: this.menuPage,
- rootExitCallback: () => {
- this.activePluginGamepad?.cleanup();
- [...this.menuPage.children].forEach((node) => node.remove());
- this.menuListGamepad?.updateFocused(this.menuListGamepad.focusPath);
- },
- });
+ private openPluginPage(
+ id: string,
+ render: MenuItem['render'],
+ navigateIntoView = false
+ ) {
+ if (this.activePluginId !== id || !this.activePluginEl) {
+ const oldIndex = this.menuItems.findIndex(
+ (item) => item.id === this.activePluginId
+ );
+ const newIndex = this.menuItems.findIndex((item) => item.id === id);
+ const animationDirection = newIndex > oldIndex ? 'bottom' : 'top';
+
+ ([...this.menuPage.children] as HTMLElement[]).forEach(
+ (node: HTMLElement) => {
+ animate(node, `animate-out-${animationDirection}`, 80);
+ setTimeout(() => {
+ // reset scroll after out animation is over
+ this.menuPage.scrollTop = 0;
+ node.remove();
+ }, 80);
+ }
+ );
+
+ const shouldAnimate = this.activePluginId != null;
+ this.activePluginId = id;
+
+ this.activePluginEl = dcCreateElement<HTMLDivElement>(
+ <div data-smm-plugin-page />
+ );
+ render(this.smm, this.activePluginEl);
+ this.menuPage.appendChild(this.activePluginEl);
+
+ if (shouldAnimate)
+ animate(this.activePluginEl, `animate-in-${animationDirection}`, 500);
+ }
+
+ if (navigateIntoView) {
+ this.activePluginGamepad = new GamepadHandler({
+ smm: this.smm,
+ root: this.activePluginEl,
+ rootExitCallback: () => {
+ this.activePluginGamepad?.cleanup();
+ this.menuListGamepad?.updateFocused(this.menuListGamepad.focusPath);
+ },
+ });
+ }
}
}
diff --git a/injected/src/ui/buttons.tsx b/injected/src/ui/buttons.tsx
index 6103d97..dfd89d5 100644
--- a/injected/src/ui/buttons.tsx
+++ b/injected/src/ui/buttons.tsx
@@ -25,7 +25,7 @@ const buttonStyles = `
}
.cs-button:focus {
- outline: none;
+ outline: white solid 2px;
}
.cs-button:focus-visible {
diff --git a/injected/src/ui/index.ts b/injected/src/ui/index.ts
index 0eb5849..1eb9864 100644
--- a/injected/src/ui/index.ts
+++ b/injected/src/ui/index.ts
@@ -9,7 +9,8 @@ export const registerCustomElements = async () => {
// TODO: load these styles from a .css file
// Had an issue with esbuild loading the text from the file
let styleSheetContents = `
-.cs-gp-focus {
+.cs-gp-focus,
+.cs-gp-focus:focus {
outline: solid 2px white;
}
`;
--
2.37.3
[PATCH crankshaft v2 3/3] Add types for events and callbacks
smm.addEventListener can now automatically determine the event arg type based on the string supplied to it.
---
.../menu-manager/deck/menu-injector-deck.tsx | 12 +-
injected/src/smm.ts | 124 ++++++++++--------
injected/src/tab-observer.ts | 7 +-
3 files changed, 82 insertions(+), 61 deletions(-)
diff --git a/injected/src/menu-manager/deck/menu-injector-deck.tsx b/injected/src/menu-manager/deck/menu-injector-deck.tsx
index f8a82a8..0cdef73 100644
--- a/injected/src/menu-manager/deck/menu-injector-deck.tsx
@@ -1,7 +1,7 @@
import { dcCreateElement } from '../../dom-chef';
import { GamepadHandler } from '../../gamepad';
import { DECK_SELECTORS } from '../../selectors';
-import { SMM } from '../../smm';
+import { eventTypeSwitchToLocation, SMM } from '../../smm';
import { deleteAll } from '../../util';
import { MenuInjector, MenuItem, MenuManager } from '../menu-manager';
import styles from './menu-injector-deck.css';
@@ -89,8 +89,12 @@ export class MenuInjectorDeck implements MenuInjector {
}
private listenToNavigationChanges() {
- this.smm.addEventListener('switchToPlugins', () => {
- this.openPluginsPage();
+ this.smm.addEventListener(eventTypeSwitchToLocation, (e) => {
+ if (e.detail.pathname === '/routes/blank/cs-plugins') {
+ this.openPluginsPage();
+ } else {
+ this.closeActivePage();
+ }
});
}
@@ -122,6 +126,8 @@ export class MenuInjectorDeck implements MenuInjector {
}
private async closePluginsPage(forward = false) {
+ if (!this.pageContainer.isConnected) return;
+
this.activePluginId = undefined;
this.activePluginEl = undefined;
// Clear active menu item
diff --git a/injected/src/smm.ts b/injected/src/smm.ts
index 2689405..32d255a 100644
--- a/injected/src/smm.ts
+++ b/injected/src/smm.ts
@@ -19,52 +19,28 @@ import { info } from './util';
type PluginId = string;
-type AddEventListenerArgs = Parameters<EventTarget['addEventListener']>;
-
-// TODO: there's probably a better way to do these EventTarget types
-
-type SMMEventType =
- | typeof eventTypeSwitchToUnknownPage
- | typeof eventTypeSwitchToHome
- | typeof eventTypeSwitchToCollections
- | typeof eventTypeSwitchToAppDetails
- | typeof eventTypeSwitchToAppProperties
- | typeof eventTypeSwitchToPlugins
- | typeof eventTypeLockScreenOpened
- | typeof eventTypeLockScreenClosed;
-
-type SMMEvent =
- | EventSwitchToUnknownPage
- | EventSwitchToHome
- | EventSwitchToCollections
- | EventSwitchToAppDetails
- | EventSwitchToAppProperties
- | EventSwitchToPlugins
- | EventLockScreenOpened
- | EventLockScreenClosed;
-
-const eventTypeSwitchToUnknownPage = 'switchToUnknownPage' as const;
+export const eventTypeSwitchToUnknownPage = 'switchToUnknownPage' as const;
class EventSwitchToUnknownPage extends CustomEvent<void> {
constructor() {
super(eventTypeSwitchToUnknownPage);
}
}
-const eventTypeSwitchToHome = 'switchToHome' as const;
+export const eventTypeSwitchToHome = 'switchToHome' as const;
class EventSwitchToHome extends CustomEvent<void> {
constructor() {
super(eventTypeSwitchToHome);
}
}
-const eventTypeSwitchToCollections = 'switchToCollections' as const;
+export const eventTypeSwitchToCollections = 'switchToCollections' as const;
class EventSwitchToCollections extends CustomEvent<void> {
constructor() {
super(eventTypeSwitchToCollections);
}
}
-const eventTypeSwitchToAppDetails = 'switchToAppDetails' as const;
+export const eventTypeSwitchToAppDetails = 'switchToAppDetails' as const;
type eventDetailsSwitchToAppDetails = { appId: string; appName: string };
class EventSwitchToAppDetails extends CustomEvent<eventDetailsSwitchToAppDetails> {
constructor(detail: eventDetailsSwitchToAppDetails) {
@@ -72,34 +48,79 @@ class EventSwitchToAppDetails extends CustomEvent<eventDetailsSwitchToAppDetails
}
}
-const eventTypeSwitchToAppProperties = 'switchToAppProperties' as const;
+export const eventTypeSwitchToAppProperties = 'switchToAppProperties' as const;
class EventSwitchToAppProperties extends CustomEvent<AppPropsApp> {
constructor(detail: AppPropsApp) {
super(eventTypeSwitchToAppProperties, { detail });
}
}
-const eventTypeSwitchToPlugins = 'switchToPlugins' as const;
-class EventSwitchToPlugins extends CustomEvent<void> {
- constructor() {
- super(eventTypeSwitchToPlugins);
+export const eventTypeSwitchToLocation = 'switchToLocation' as const;
+type eventDetailsSwitchToLocation = Location;
+class EventSwitchToLocation extends CustomEvent<eventDetailsSwitchToLocation> {
+ constructor(detail: eventDetailsSwitchToLocation) {
+ super(eventTypeSwitchToLocation, { detail });
}
}
-const eventTypeLockScreenOpened = 'lockScreenOpened' as const;
+export const eventTypeLockScreenOpened = 'lockScreenOpened' as const;
class EventLockScreenOpened extends CustomEvent<void> {
constructor() {
super(eventTypeLockScreenOpened);
}
}
-const eventTypeLockScreenClosed = 'lockScreenClosed' as const;
+export const eventTypeLockScreenClosed = 'lockScreenClosed' as const;
class EventLockScreenClosed extends CustomEvent<void> {
constructor() {
super(eventTypeLockScreenClosed);
}
}
+interface EventListenerObjectFor<T extends Event> {
+ handleEvent(evt: T): void;
+}
+interface EventListenerFor<T extends Event> {
+ (evt: T): void;
+}
+type EventListenerOrEventListenerObjectFor<T extends Event> =
+ | EventListenerFor<T>
+ | EventListenerObjectFor<T>;
+
+type SMMEventGroup<T extends string, E extends Event> = {
+ type: T;
+ event: E;
+};
+
+type SMMEvent =
+ | SMMEventGroup<typeof eventTypeSwitchToUnknownPage, EventSwitchToUnknownPage>
+ | SMMEventGroup<typeof eventTypeSwitchToHome, EventSwitchToHome>
+ | SMMEventGroup<typeof eventTypeSwitchToCollections, EventSwitchToCollections>
+ | SMMEventGroup<typeof eventTypeSwitchToAppDetails, EventSwitchToAppDetails>
+ | SMMEventGroup<
+ typeof eventTypeSwitchToAppProperties,
+ EventSwitchToAppProperties
+ >
+ | SMMEventGroup<typeof eventTypeSwitchToLocation, EventSwitchToLocation>
+ | SMMEventGroup<typeof eventTypeLockScreenOpened, EventLockScreenOpened>
+ | SMMEventGroup<typeof eventTypeLockScreenClosed, EventLockScreenClosed>;
+
+type MapToCallbacks<U extends SMMEvent> = U extends any
+ ? {
+ type: U['type'];
+ callback: EventListenerOrEventListenerObjectFor<U['event']> | null;
+ options: Parameters<EventTarget['addEventListener']>[2];
+ }
+ : never;
+type SMMEventCallback = MapToCallbacks<SMMEvent>;
+
+type SMMCallbackMap = {
+ [P in SMMEventCallback['type']]: Extract<
+ SMMEventCallback,
+ { type: P }
+ >['callback'];
+};
+
/**
* @public
*/
@@ -141,14 +162,7 @@ export class SMM extends EventTarget {
// that plugin.
private currentPlugin?: string;
// Events attached by plugins
- private attachedEvents: Record<
- PluginId,
- {
- type: AddEventListenerArgs[0];
- callback: AddEventListenerArgs[1];
- options: AddEventListenerArgs[2];
- }[]
- >;
+ private attachedEvents: Record<PluginId, SMMEventCallback[]>;
constructor(entry: Entry) {
super();
@@ -287,10 +301,10 @@ export class SMM extends EventTarget {
/**
* @internal
*/
- switchToPlugins() {
- info('Switched to plugins');
+ switchToLocation(loc: Location) {
+ info('Switched to location');
- this.dispatchEvent(new EventSwitchToPlugins());
+ this.dispatchEvent(new EventSwitchToLocation(loc));
}
/**
@@ -383,15 +397,15 @@ export class SMM extends EventTarget {
if (this.attachedEvents[pluginId]) {
for (const { type, callback, options } of this.attachedEvents[pluginId]) {
- this.removeEventListener(type, callback, options);
+ this.removeEventListener(type, callback as EventListener, options);
}
delete this.attachedEvents[pluginId];
}
}
- addEventListener(
- type: SMMEventType,
- callback: EventListenerOrEventListenerObject | null,
+ addEventListener<K extends keyof SMMCallbackMap>(
+ type: K,
+ callback: SMMCallbackMap[K],
options?: boolean | AddEventListenerOptions
): void {
if (this.currentPlugin) {
@@ -399,13 +413,17 @@ export class SMM extends EventTarget {
this.attachedEvents[this.currentPlugin] = [];
}
- this.attachedEvents[this.currentPlugin].push({ type, callback, options });
+ this.attachedEvents[this.currentPlugin].push({
+ type,
+ callback,
+ options,
+ } as SMMEventCallback);
}
- super.addEventListener(type, callback, options);
+ super.addEventListener(type, callback as EventListener, options);
}
- dispatchEvent(event: SMMEvent): boolean {
+ dispatchEvent(event: SMMEvent['event']): boolean {
return super.dispatchEvent(event);
}
diff --git a/injected/src/tab-observer.ts b/injected/src/tab-observer.ts
index d8ea9ee..d9bfad9 100644
--- a/injected/src/tab-observer.ts
+++ b/injected/src/tab-observer.ts
@@ -14,6 +14,8 @@ export const createTabObserver = (smm: SMM, mainLibraryEl: HTMLElement) => {
}
const observer = new MutationObserver((_mutationsList) => {
+ smm.switchToLocation({ ...location });
+
if (window.smmUIMode === 'deck') {
if (document.querySelector(DECK_SELECTORS.lockScreenContainer)) {
if (!smm.onLockScreen) {
@@ -81,11 +83,6 @@ export const createTabObserver = (smm: SMM, mainLibraryEl: HTMLElement) => {
}
}
- if (location.pathname === '/routes/blank/cs-plugins') {
- smm.switchToPlugins();
- return;
- }
-
smm.switchToUnknownPage();
});
--
2.37.3
[crankshaft/patches] build failed