Skip to content

Commit

Permalink
Merge pull request #10865 from DestinyItemManager/stream-deck-chunks
Browse files Browse the repository at this point in the history
Clean up StreamDeck module
  • Loading branch information
bhollis authored Dec 21, 2024
2 parents 7115f4b + f613900 commit c428486
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 107 deletions.
2 changes: 1 addition & 1 deletion config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export default (env: Env) => {
optimization: {
// We always want the chunk name, otherwise it's just numbers
// chunkIds: 'named',
// Extract the runtime into a separate chunk
// Extract the runtime into a separate chunk.
runtimeChunk: 'single',
splitChunks: {
chunks(chunk) {
Expand Down
15 changes: 2 additions & 13 deletions src/app/item-actions/ItemAccessoryButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DimItem } from 'app/inventory/item-types';
import { ItemActionsModel } from 'app/item-popup/item-popup-actions';
import OpenOnStreamDeckButton from 'app/stream-deck/OpenOnStreamDeckButton/OpenOnStreamDeckButton';
import { streamDeckEnabledSelector } from 'app/stream-deck/selectors';
import { lazy, Suspense } from 'react';
import { useSelector } from 'react-redux';
import {
CompareActionButton,
Expand All @@ -13,13 +13,6 @@ import {
TagActionButton,
} from './ActionButtons';

const OpenOnStreamDeckButton = lazy(
() =>
import(
/* webpackChunkName: "send-to-stream-deck-button" */ 'app/stream-deck/OpenOnStreamDeckButton/OpenOnStreamDeckButton'
),
);

/**
* "Accessory" buttons like tagging, locking, comparing, adding to loadout. Displayed separately on mobile, but together with the move actions on desktop.
*/
Expand Down Expand Up @@ -58,11 +51,7 @@ export default function ItemAccessoryButtons({
{actionsModel.infusable && (
<InfuseActionButton item={item} label={showLabel} actionModel={actionsModel} />
)}
{streamDeckEnabled && (
<Suspense>
<OpenOnStreamDeckButton label={showLabel} item={item} />
</Suspense>
)}
{streamDeckEnabled && <OpenOnStreamDeckButton label={showLabel} item={item} />}
</>
);
}
4 changes: 1 addition & 3 deletions src/app/loadout/LoadoutsRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ export default memo(function LoadoutRow({
const streamDeckDeepLink = $featureFlags.elgatoStreamDeck
? // eslint-disable-next-line
useStreamDeckSelection({
type: 'loadout',
loadout,
store,
options: { type: 'loadout' as const, loadout, store },
equippable,
})
: undefined;
Expand Down
6 changes: 4 additions & 2 deletions src/app/loadout/ingame/InGameLoadoutStrip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,11 @@ function InGameLoadoutTile({
const streamDeckDeepLink = $featureFlags.elgatoStreamDeck
? // eslint-disable-next-line
useStreamDeckSelection({
type: 'in-game-loadout',
options: {
type: 'in-game-loadout' as const,
loadout: gameLoadout,
},
equippable: true,
loadout: gameLoadout,
})
: undefined;

Expand Down
16 changes: 3 additions & 13 deletions src/app/shell/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { accountRoute } from 'app/routes';
import { SearchFilterRef } from 'app/search/SearchBar';
import DimApiWarningBanner from 'app/storage/DimApiWarningBanner';
import { useThunkDispatch } from 'app/store/thunk-dispatch';
import StreamDeckButton from 'app/stream-deck/StreamDeckButton/StreamDeckButton';
import { streamDeckEnabledSelector } from 'app/stream-deck/selectors';
import { isiOSBrowser } from 'app/utils/browsers';
import { compact } from 'app/utils/collections';
Expand All @@ -18,7 +19,7 @@ import { infoLog } from 'app/utils/log';
import clsx from 'clsx';
import logo from 'images/logo-type-right-light.svg';
import { AnimatePresence, Spring, Variants, motion } from 'motion/react';
import React, { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { Link, NavLink, useLocation } from 'react-router';
import { useSubscription } from 'use-subscription';
Expand Down Expand Up @@ -53,13 +54,6 @@ const menuAnimateVariants: Variants = {
};
const menuAnimateTransition: Spring = { type: 'spring', duration: 0.3, bounce: 0 };

const StreamDeckButton = lazy(
() =>
import(
/* webpackChunkName: "stream-deck-button" */ 'app/stream-deck/StreamDeckButton/StreamDeckButton'
),
);

// TODO: finally time to hack apart the header styles!

export default function Header() {
Expand Down Expand Up @@ -373,11 +367,7 @@ export default function Header() {
<SearchFilter onClear={hideSearch} ref={searchFilter} />
</span>
)}
{streamDeckEnabled && (
<Suspense>
<StreamDeckButton />
</Suspense>
)}
{streamDeckEnabled && <StreamDeckButton />}
<RefreshButton className={styles.menuItem} />
{!isPhonePortrait && (
<Link className={styles.menuItem} to="/settings" title={t('Settings.Settings')}>
Expand Down
7 changes: 4 additions & 3 deletions src/app/store/observerMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export interface StoreObserver<T> {
*/
getObserved: (rootState: RootState) => T;
/**
* Runs the side effect providing both the previous and current version of the derrived state.
* Runs the side effect providing both the previous and current version of the derived state.
* When the `runInitially` flag is true, previous will be undefined on first run.
*/
sideEffect: (states: { previous: T | undefined; current: T }) => void;
sideEffect: (states: { previous: T | undefined; current: T; rootState: RootState }) => void;
}

/**
Expand Down Expand Up @@ -70,6 +70,7 @@ export function observerMiddleware<D extends Dispatch>(
storeObserver.sideEffect({
previous: undefined,
current: storeObserver.getObserved(api.getState()),
rootState: api.getState(),
});
}

Expand Down Expand Up @@ -97,7 +98,7 @@ export function observerMiddleware<D extends Dispatch>(
const current = observer.getObserved(currentRootState);
const equals = observer.equals || Object.is;
if (!equals(previous, current)) {
observer.sideEffect({ previous, current });
observer.sideEffect({ previous, current, rootState: currentRootState });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ import { DimItem } from 'app/inventory/item-types';
import ActionButton from 'app/item-actions/ActionButton';
import { BucketHashes } from 'data/d2/generated-enums';
import streamDeckIcon from 'images/streamDeck.svg';
import { useMemo } from 'react';
import { useStreamDeckSelection } from '../stream-deck';
import styles from './OpenOnStreamDeckButton.m.scss';

export default function OpenOnStreamDeckButton({ item, label }: { item: DimItem; label: boolean }) {
const options = useMemo(
() => ({
type: 'item' as const,
item,
isSubClass: item.bucket.hash === BucketHashes.Subclass,
}),
[item],
);

const deepLink = useStreamDeckSelection({
type: 'item',
item,
isSubClass: item.bucket.hash === BucketHashes.Subclass,
options,
equippable: !item.notransfer,
});

Expand Down
59 changes: 28 additions & 31 deletions src/app/stream-deck/StreamDeckButton/StreamDeckButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,23 @@ import { PressTip } from 'app/dim-ui/PressTip';
import { t } from 'app/i18next-t';
import { AppIcon, banIcon } from 'app/shell/icons';
import { useThunkDispatch } from 'app/store/thunk-dispatch';
import { EventBus } from 'app/utils/observable';
import streamDeckIcon from 'images/streamDeck.svg';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { streamDeckSelector } from '../selectors';
import { streamDeckAuthorizationInit } from '../util/authorization';
import { STREAM_DECK_MINIMUM_VERSION, checkStreamDeckVersion } from '../util/version';
import styles from './StreamDeckButton.m.scss';

const version$ = new EventBus<undefined>();

const usePluginVersion = () => {
const [version, setVersion] = useState<string | undefined>(undefined);

useEffect(() => {
version$.subscribe(() =>
fetch('http://localhost:9120/version', {
mode: 'cors',
})
.then((response) => response.text())
.then((text) => setVersion(text))
.catch(() => setVersion(undefined)),
);
version$.next(undefined);
}, []);
return version;
};

interface StreamDeckTooltipProps {
function StreamDeckTooltip({
version,
error,
needSetup,
}: {
version?: string;
error?: boolean;
needSetup?: boolean;
}

function StreamDeckTooltip({ version, error, needSetup }: StreamDeckTooltipProps) {
}) {
return (
<div>
<div className={styles.tooltipTitle}>{t('StreamDeck.Tooltip.Title')}</div>
Expand Down Expand Up @@ -73,18 +55,35 @@ function StreamDeckTooltip({ version, error, needSetup }: StreamDeckTooltipProps
);
}

function StreamDeckButton() {
export default function StreamDeckButton() {
const { connected, auth } = useSelector(streamDeckSelector);
const version = usePluginVersion();
const error = useMemo(() => !checkStreamDeckVersion(version), [version]);
const [version, setVersion] = useState<string | undefined>(undefined);

const updateVersion = async () => {
try {
const resp = await fetch('http://localhost:9120/version', {
mode: 'cors',
});
const text = await resp.text();
setVersion(text);
} catch {
setVersion(undefined);
}
};

useEffect(() => {
updateVersion();
}, []);

const error = !checkStreamDeckVersion(version);
const needSetup = auth === undefined;
const dispatch = useThunkDispatch();

return (
<PressTip tooltip={<StreamDeckTooltip version={version} error={error} needSetup={needSetup} />}>
<button
onClick={() => {
version$.next(undefined);
updateVersion();
needSetup && dispatch(streamDeckAuthorizationInit());
}}
type="button"
Expand All @@ -103,5 +102,3 @@ function StreamDeckButton() {
</PressTip>
);
}

export default StreamDeckButton;
10 changes: 5 additions & 5 deletions src/app/stream-deck/async-module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// async module
import { currentStoreSelector } from 'app/inventory/selectors';
import { observe, unobserve } from 'app/store/observerMiddleware';
import store from 'app/store/store';
import { ThunkResult } from 'app/store/types';
import { RootState, ThunkResult } from 'app/store/types';
import { streamDeckConnected, streamDeckDisconnected } from 'app/stream-deck/actions';
import { SendToStreamDeckArgs, StreamDeckMessage } from 'app/stream-deck/interfaces';
import { handleStreamDeckMessage } from 'app/stream-deck/msg-handlers';
import packager from 'app/stream-deck/util/packager';
import useSelection from './useStreamDeckSelection';

const STREAM_DECK_FARMING_OBSERVER_ID = 'stream-deck-farming-observer';

Expand All @@ -25,8 +25,7 @@ export async function sendToStreamDeck(msg: SendToStreamDeckArgs) {
}

// collect and send data to the stream deck
function refreshStreamDeck() {
const state = store.getState();
function refreshStreamDeck(state: RootState) {
if (websocket.readyState === WebSocket.OPEN) {
const store = currentStoreSelector(state);
store &&
Expand Down Expand Up @@ -75,7 +74,7 @@ function registerObservers(): ThunkResult {
id: STREAM_DECK_INVENTORY_OBSERVER_ID,
runInitially: true,
getObserved: (rootState) => rootState.inventory,
sideEffect: refreshStreamDeck,
sideEffect: ({ rootState }) => refreshStreamDeck(rootState),
}),
);
};
Expand Down Expand Up @@ -153,4 +152,5 @@ function start(): ThunkResult {
export default {
start,
stop,
useSelection,
};
5 changes: 0 additions & 5 deletions src/app/stream-deck/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ export interface MaxPowerAction {
export interface PullItemAction {
action: 'pullItem';
itemId: string;
/**
* @deprecated to be removed in future plugin update
* @see type
*/
equip: boolean;
type: 'equip' | 'pull' | 'vault';
}

Expand Down
3 changes: 2 additions & 1 deletion src/app/stream-deck/msg-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ function pullItemHandler({ msg, state, store }: HandlerArgs<PullItemAction>): Th
const allItems = allItemsSelector(state);
const [item] = allItems.filter((it) => it.index.startsWith(msg.itemId));
const targetStore = msg.type === 'vault' ? vaultSelector(state) : store;
const shouldEquip = msg.type === 'equip' || msg.equip;
const shouldEquip = msg.type === 'equip';
if (targetStore) {
await dispatch(moveItemTo(item, targetStore, shouldEquip, item.amount));
}
Expand Down Expand Up @@ -235,6 +235,7 @@ export function handleStreamDeckMessage(msg: StreamDeckMessage, token: string):
});
throw new Error(!msg.token ? 'missing-token' : 'invalid-token');
}

if (store) {
// handle stream deck actions
const handler = handlers[msg.action] as (args: HandlerArgs<StreamDeckMessage>) => ThunkResult;
Expand Down
4 changes: 0 additions & 4 deletions src/app/stream-deck/stream-deck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@ const lazyLoaded: LazyStreamDeck = {};
// lazy load the stream deck module when needed
export const lazyLoadStreamDeck = async () => {
const core = await import(/* webpackChunkName: "streamdeck" */ './async-module');
const useStreamDeckSelection = await import(
/* webpackChunkName: "streamdeck-selection" */ './useStreamDeckSelection'
);
// load only once
if (!lazyLoaded.start) {
Object.assign(lazyLoaded, {
...core.default,
...useStreamDeckSelection.default,
});
}
};
Expand Down
Loading

0 comments on commit c428486

Please sign in to comment.