Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit in-game identifiers for DIM loadouts #9760

Merged
merged 17 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion babel.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ module.exports = function (api) {
if (isProduction) {
plugins.push(
'@babel/plugin-transform-react-constant-elements',
'@babel/plugin-transform-react-inline-elements'
'@babel/plugin-transform-react-inline-elements',
);
} else {
if (!isTest) {
Expand Down
2 changes: 2 additions & 0 deletions config/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export function makeFeatureFlags(env: {
customStatWeights: false,
// On the Loadouts page, run Loadout Optimizer to find better tiers for loadouts.
runLoInBackground: true,
// Whether to allow setting in-game loadout identifiers on DIM loadouts.
editInGameLoadoutIdentifiers: false,
};
}

Expand Down
4 changes: 4 additions & 0 deletions config/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@
"DeletedBody": "Cleared the in-game loadout at slot {{index}}",
"DeleteFailed": "Failed to delete loadout",
"Save": "Update Loadout",
"SaveIdentifiers": "Update Identifiers",
"SaveToDimLoadout": "Save as DIM Loadout",
"Replace": "Replace Loadout {{index}}",
"SnapshotFailed": "Failed to snapshot equipped loadout"
Expand Down Expand Up @@ -702,6 +703,9 @@
"CannotCustomizeSubclass": "This subclass cannot be configured",
"ChooseItem": "Add {{name}}",
"Classified": "Some of your items are classified, and cannot be included in the max power calculation.",
"ClassType": "Any class loadout",
"ClassType_male": "{{className}} loadout",
"ClassType_female": "{{className}} loadout",
"ClassTypeMismatch": "A {{className}} item cannot be added to this loadout",
"ClassTypeMissing": "You do not have a {{className}} to create a loadout for",
"ClearSection": "Remove all",
Expand Down
16 changes: 8 additions & 8 deletions icons/build_icons.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ for (const VERSION of ['release', 'beta', 'dev', 'pr']) {

for (const size of [16, 32, 96, 48]) {
execSync(
`rsvg-convert -w ${size} -h ${size} -o "${VERSION}/favicon-${size}x${size}.png" "favicon-${VERSION}.svg"`
`rsvg-convert -w ${size} -h ${size} -o "${VERSION}/favicon-${size}x${size}.png" "favicon-${VERSION}.svg"`,
);
}

Expand All @@ -27,22 +27,22 @@ for (const VERSION of ['release', 'beta', 'dev', 'pr']) {
}[VERSION];

execSync(
`rsvg-convert -w 180 -h 180 -o "${VERSION}/apple-touch-icon.png" "apple-touch-icon-${VERSION}.svg"`
`rsvg-convert -w 180 -h 180 -o "${VERSION}/apple-touch-icon.png" "apple-touch-icon-${VERSION}.svg"`,
);
execSync(
`rsvg-convert -w 180 -h 180 -o "${VERSION}/apple-touch-icon-${CACHEBREAKER}.png" "apple-touch-icon-${VERSION}.svg"`
`rsvg-convert -w 180 -h 180 -o "${VERSION}/apple-touch-icon-${CACHEBREAKER}.png" "apple-touch-icon-${VERSION}.svg"`,
);
execSync(
`rsvg-convert -w 192 -h 192 -o "${VERSION}/android-chrome-192x192-${CACHEBREAKER}.png" "android-icon-${VERSION}.svg"`
`rsvg-convert -w 192 -h 192 -o "${VERSION}/android-chrome-192x192-${CACHEBREAKER}.png" "android-icon-${VERSION}.svg"`,
);
execSync(
`rsvg-convert -w 512 -h 512 -o "${VERSION}/android-chrome-512x512-${CACHEBREAKER}.png" "android-icon-${VERSION}.svg"`
`rsvg-convert -w 512 -h 512 -o "${VERSION}/android-chrome-512x512-${CACHEBREAKER}.png" "android-icon-${VERSION}.svg"`,
);
execSync(
`rsvg-convert -w 512 -h 512 -b "${color}" -o "${VERSION}/android-chrome-mask-512x512-${CACHEBREAKER}.png" "android-icon-${VERSION}.svg"`
`rsvg-convert -w 512 -h 512 -b "${color}" -o "${VERSION}/android-chrome-mask-512x512-${CACHEBREAKER}.png" "android-icon-${VERSION}.svg"`,
);
execSync(
`convert ${VERSION}/favicon-48x48.png -define icon:auto-resize=48,32,16 ${VERSION}/favicon.ico`
`convert ${VERSION}/favicon-48x48.png -define icon:auto-resize=48,32,16 ${VERSION}/favicon.ico`,
);
rimraf.sync(`${VERSION}/favicon-48x48.png`);
}
Expand All @@ -54,6 +54,6 @@ fs.mkdirSync('splash');
for (const [_a, _b, _c, _d, w, h] of splash) {
execSync(`rsvg-convert -w ${w} -h ${h} -a -o "splash/splash-${w}x${h}.png" "splash.svg"`);
execSync(
`convert splash/splash-${w}x${h}.png -background "#313233" -gravity center -extent ${w}x${h} splash/splash-${w}x${h}.png`
`convert splash/splash-${w}x${h}.png -background "#313233" -gravity center -extent ${w}x${h} splash/splash-${w}x${h}.png`,
);
}
2 changes: 1 addition & 1 deletion src/app/dim-ui/Sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const SheetDisabledContext = createContext<(shown: boolean) => void>(() => {
* takes an "onClose" function that can be used to close the sheet. Using onClose to close
* the sheet ensures that it will animate away rather than simply disappearing.
*/
type SheetContent = React.ReactNode | ((args: { onClose: () => void }) => React.ReactNode);
export type SheetContent = React.ReactNode | ((args: { onClose: () => void }) => React.ReactNode);

// The sheet is dismissed if it's flicked at a velocity above dismissVelocity,
// or dragged down more than dismissAmount times the height of the sheet.
Expand Down
23 changes: 22 additions & 1 deletion src/app/loadout-drawer/LoadoutDrawer.m.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
}

.notes {
margin-top: 8px;
font-size: 14px;
margin-top: 4px;

summary {
font-weight: bold;
Expand All @@ -29,3 +29,24 @@
align-items: center;
gap: 4px;
}

.header {
composes: flexRow from '../dim-ui/common.m.scss';
gap: 0 12px;
}

.headerDetails {
composes: flexColumn from '../dim-ui/common.m.scss';
flex: 1;
gap: 4px;
}

.classType {
composes: flexRow from '../dim-ui/common.m.scss';
align-items: center;
gap: 0.25em;

svg {
height: 16px;
}
}
3 changes: 3 additions & 0 deletions src/app/loadout-drawer/LoadoutDrawer.m.scss.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 34 additions & 15 deletions src/app/loadout-drawer/LoadoutDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { apiPermissionGrantedSelector } from 'app/dim-api/selectors';
import { AlertIcon } from 'app/dim-ui/AlertIcon';
import CheckButton from 'app/dim-ui/CheckButton';
import ClassIcon from 'app/dim-ui/ClassIcon';
import { WithSymbolsPicker } from 'app/dim-ui/destiny-symbols/SymbolsPicker';
import { useAutocomplete } from 'app/dim-ui/text-complete/text-complete';
import { t } from 'app/i18next-t';
import { getStore } from 'app/inventory/stores-helpers';
import InGameLoadoutIdentifiersSelectButton from 'app/loadout/ingame/InGameLoadoutIdentifiersSelectButton';
import { useDefinitions } from 'app/manifest/selectors';
import { searchFilterSelector } from 'app/search/items/item-search-filter';
import { AppIcon, addIcon, faRandom } from 'app/shell/icons';
Expand Down Expand Up @@ -56,6 +58,7 @@ export default function LoadoutDrawer({
initialLoadout,
storeId,
isNew,
fromExternal,
onClose,
}: {
initialLoadout: Loadout;
Expand All @@ -66,6 +69,7 @@ export default function LoadoutDrawer({
*/
storeId: string;
isNew: boolean;
fromExternal: boolean;
onClose: () => void;
}) {
const dispatch = useThunkDispatch();
Expand All @@ -88,7 +92,7 @@ export default function LoadoutDrawer({
return (...args: T) => setLoadout(fn(...args));
}

const store = getStore(stores, storeId)!;
const store = getStore(stores, storeId);

const onAddItem = useCallback(
(item: DimItem, equip?: boolean) => setLoadout(addItem(defs, item, equip)),
Expand Down Expand Up @@ -170,21 +174,36 @@ export default function LoadoutDrawer({
const toggleAnyClass = (checked: boolean) =>
setLoadout(setClassType(checked ? DestinyClass.Unknown : store.classType));

const showInGameLoadoutIdentifiers =
$featureFlags.editInGameLoadoutIdentifiers &&
(Boolean(loadout.parameters?.inGameIdentifiers) || loadout.items.length > 0);

const header = (
<div>
<LoadoutDrawerHeader loadout={loadout} onNameChanged={handleNameChanged} />
<details className={styles.notes} open={Boolean(loadout.notes?.length)}>
<summary>{t('MovePopup.Notes')}</summary>
<WithSymbolsPicker input={ref} setValue={(val) => setLoadout(setNotes(val))}>
<TextareaAutosize
onChange={handleNotesChanged}
ref={ref}
value={loadout.notes}
maxLength={2048}
placeholder={t('Loadouts.NotesPlaceholder')}
/>
</WithSymbolsPicker>
</details>
<div className={styles.header}>
{showInGameLoadoutIdentifiers && (
<InGameLoadoutIdentifiersSelectButton loadout={loadout} setLoadout={setLoadout} />
)}
<div className={styles.headerDetails}>
{fromExternal && (
<div className={styles.classType}>
<ClassIcon classType={loadout.classType} />
{t('Loadouts.ClassType', { className: store.className, context: store.genderName })}
</div>
)}
<LoadoutDrawerHeader loadout={loadout} onNameChanged={handleNameChanged} />
<details className={styles.notes} open={Boolean(loadout.notes?.length)}>
<summary>{t('MovePopup.Notes')}</summary>
<WithSymbolsPicker input={ref} setValue={(val) => setLoadout(setNotes(val))}>
<TextareaAutosize
onChange={handleNotesChanged}
ref={ref}
value={loadout.notes}
maxLength={2048}
placeholder={t('Loadouts.NotesPlaceholder')}
/>
</WithSymbolsPicker>
</details>
</div>
</div>
);

Expand Down
23 changes: 8 additions & 15 deletions src/app/loadout-drawer/LoadoutDrawerContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { DestinyClass } from 'bungie-api-ts/destiny2';
import { Suspense, lazy, useCallback, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useLocation, useNavigate } from 'react-router';
import { Loadout } from '../loadout/loadout-types';
import { addItem$, editLoadout$ } from './loadout-events';
import { EditLoadoutState, addItem$, editLoadout$ } from './loadout-events';
import { convertToLoadoutItem, newLoadout, pickBackingStore } from './loadout-utils';

const LoadoutDrawer = lazy(
Expand Down Expand Up @@ -43,12 +42,7 @@ export default function LoadoutDrawerContainer({ account }: { account: DestinyAc
// TODO: Alternately we could come up with the concept of a
// `useControlledReducer` that applied a reducer to mutate an object whose
// state is handled outside the component.
const [initialLoadout, setInitialLoadout] = useState<{
loadout: Loadout;
storeId: string;
showClass: boolean;
isNew: boolean;
}>();
const [initialLoadout, setInitialLoadout] = useState<EditLoadoutState>();

const handleDrawerClose = useCallback(() => {
setInitialLoadout(undefined);
Expand All @@ -60,7 +54,8 @@ export default function LoadoutDrawerContainer({ account }: { account: DestinyAc
useEventBusListener(
editLoadout$,
useCallback(
({ loadout, storeId, showClass, isNew }) => {
(state) => {
const { storeId, loadout } = state;
// Fall back to current store because otherwise there's no way to delete loadouts
// the user doesn't have a class for.
const editingStore =
Expand All @@ -73,12 +68,7 @@ export default function LoadoutDrawerContainer({ account }: { account: DestinyAc
return;
}

setInitialLoadout({
loadout,
storeId: editingStore.id,
showClass: Boolean(showClass),
isNew: Boolean(isNew),
});
setInitialLoadout({ ...state, storeId: editingStore.id });
},
[stores, defs],
),
Expand Down Expand Up @@ -113,6 +103,7 @@ export default function LoadoutDrawerContainer({ account }: { account: DestinyAc
storeId: owner.id,
isNew: true,
showClass: true,
fromExternal: true,
});
}
},
Expand Down Expand Up @@ -140,6 +131,7 @@ export default function LoadoutDrawerContainer({ account }: { account: DestinyAc
storeId,
isNew: true,
showClass: false,
fromExternal: true,
});
// Clear the loadout from params if the URL contained one...
navigate(pathname, { replace: true });
Expand Down Expand Up @@ -173,6 +165,7 @@ export default function LoadoutDrawerContainer({ account }: { account: DestinyAc
storeId={initialLoadout.storeId}
isNew={initialLoadout.isNew}
onClose={handleDrawerClose}
fromExternal={initialLoadout.fromExternal}
/>
) : (
<D1LoadoutDrawer
Expand Down
13 changes: 0 additions & 13 deletions src/app/loadout-drawer/LoadoutDrawerHeader.m.scss
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
@use '../variables.scss' as *;

.loadoutName {
font-size: 18px;
display: flex;
flex-flow: row nowrap;
align-items: center;
max-width: 50em;

svg,
:global(.app-icon) {
margin-right: 8px;
}
}

.dimInput {
composes: flexRow from '../dim-ui/common.m.scss';
padding: 2px 25px 2px 5px;
Expand Down
1 change: 0 additions & 1 deletion src/app/loadout-drawer/LoadoutDrawerHeader.m.scss.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 14 additions & 18 deletions src/app/loadout-drawer/LoadoutDrawerHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import ClassIcon from 'app/dim-ui/ClassIcon';
import { WithSymbolsPicker } from 'app/dim-ui/destiny-symbols/SymbolsPicker';
import { useAutocomplete } from 'app/dim-ui/text-complete/text-complete';
import { t } from 'app/i18next-t';
Expand All @@ -22,22 +21,19 @@ export default function LoadoutDrawerHeader({
useAutocomplete(inputRef, tags);

return (
<div className={styles.loadoutName}>
<ClassIcon classType={loadout.classType} />
<WithSymbolsPicker className={styles.dimInput} input={inputRef} setValue={onNameChanged}>
<input
name="name"
ref={inputRef}
onChange={setName}
minLength={1}
maxLength={50}
required={true}
autoComplete="off"
type="text"
value={loadout.name}
placeholder={t('Loadouts.LoadoutName')}
/>
</WithSymbolsPicker>
</div>
<WithSymbolsPicker className={styles.dimInput} input={inputRef} setValue={onNameChanged}>
<input
name="name"
ref={inputRef}
onChange={setName}
minLength={1}
maxLength={50}
required={true}
autoComplete="off"
type="text"
value={loadout.name}
placeholder={t('Loadouts.LoadoutName')}
/>
</WithSymbolsPicker>
);
}
9 changes: 8 additions & 1 deletion src/app/loadout-drawer/loadout-drawer-reducer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LoadoutParameters } from '@destinyitemmanager/dim-api-types';
import { InGameLoadoutIdentifiers, LoadoutParameters } from '@destinyitemmanager/dim-api-types';
import { D1Categories } from 'app/destiny1/d1-bucket-categories';
import { D1ManifestDefinitions } from 'app/destiny1/d1-definitions';
import { D2Categories } from 'app/destiny2/d2-bucket-categories';
Expand Down Expand Up @@ -851,3 +851,10 @@ export function randomizeLoadoutMods(
})(loadout);
});
}

/**
* Set the name/icon/color of this loadout.
*/
export function setInGameLoadoutIdentifiers(identifiers: InGameLoadoutIdentifiers | undefined) {
return setLoadoutParameters({ inGameIdentifiers: identifiers });
}
Loading
Loading