Grabbed dndbeyond's source code
``` ~/go/bin/sourcemapper -output ddb -jsurl https://media.dndbeyond.com/character-app/static/js/main.90aa78c5.js ```
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { useContext } from "react";
|
||||
import { createContext } from "react";
|
||||
|
||||
import useUser from "~/hooks/useUser";
|
||||
import type { AppUserState } from "~/hooks/useUser";
|
||||
|
||||
export let AuthContext = createContext<AppUserState>(undefined);
|
||||
|
||||
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const user = useUser();
|
||||
|
||||
if (user === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>;
|
||||
};
|
||||
|
||||
export const useAuth = () => {
|
||||
return useContext(AuthContext);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { useContext, useEffect } from "react";
|
||||
import { createContext } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
rulesEngineSelectors,
|
||||
CharacterTheme,
|
||||
} from "@dndbeyond/character-rules-engine";
|
||||
|
||||
export let CharacterThemeContext = createContext<CharacterTheme>({
|
||||
isDefault: false,
|
||||
themeColorId: 0,
|
||||
name: "",
|
||||
themeColor: "",
|
||||
backgroundColor: "",
|
||||
isDarkMode: false,
|
||||
});
|
||||
|
||||
export const CharacterThemeProvider = ({ children }) => {
|
||||
const {
|
||||
isDefault,
|
||||
themeColorId,
|
||||
name,
|
||||
themeColor,
|
||||
backgroundColor,
|
||||
isDarkMode,
|
||||
} = useSelector(rulesEngineSelectors.getCharacterTheme);
|
||||
|
||||
const solidBackground =
|
||||
backgroundColor.length > 7 ? backgroundColor.slice(0, -2) : backgroundColor;
|
||||
|
||||
const isThemeColorDark = () => {
|
||||
// Remove #
|
||||
const c = themeColor.substring(1);
|
||||
// convert rrggbb to decimal
|
||||
const rgb = parseInt(c, 16);
|
||||
const r = (rgb >> 16) & 0xff;
|
||||
const g = (rgb >> 8) & 0xff;
|
||||
const b = (rgb >> 0) & 0xff;
|
||||
// Get luma from rgb colors
|
||||
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
return luma < 40;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.body.dataset.theme = isDarkMode ? "dark" : "light";
|
||||
}, [isDarkMode]);
|
||||
|
||||
return (
|
||||
<CharacterThemeContext.Provider
|
||||
value={{
|
||||
isDefault,
|
||||
themeColorId,
|
||||
name,
|
||||
themeColor,
|
||||
backgroundColor,
|
||||
isDarkMode,
|
||||
}}
|
||||
>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
:root {
|
||||
--theme-color: ${themeColor};
|
||||
--theme-background: ${backgroundColor};
|
||||
--theme-background-solid: ${solidBackground};
|
||||
--theme-contrast: var(--ttui_grey-${isDarkMode ? 50 : 900});
|
||||
|
||||
--theme-transparent: color-mix(in srgb, var(--theme-color), transparent 60%);
|
||||
|
||||
/* Update the character muted color for readability on dark mode */
|
||||
${
|
||||
isDarkMode ? "--character-muted-color: var(--ttui_grey-400);" : ""
|
||||
}
|
||||
}`,
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</CharacterThemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useCharacterTheme = () => {
|
||||
return useContext(CharacterThemeContext);
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
import { FC, createContext, useContext, useEffect, useState } from "react";
|
||||
|
||||
import { toCamelCase } from "~/helpers/casing";
|
||||
import { getFeatureFlagsFromCharacterService } from "~/helpers/characterServiceApi";
|
||||
|
||||
const featureFlagsToGet = [
|
||||
"release-gate-ims",
|
||||
"release-gate-gfs-blessings-ui",
|
||||
"release-gate-character-sheet-tour",
|
||||
"release-gate-premade-characters",
|
||||
];
|
||||
|
||||
export const defaultFlags = {
|
||||
imsFlag: false,
|
||||
gfsBlessingsUiFlag: false,
|
||||
characterSheetTourFlag: false,
|
||||
premadeCharactersFlag: false,
|
||||
};
|
||||
|
||||
export type FeatureFlags = typeof defaultFlags;
|
||||
|
||||
export interface FeatureFlagContextType extends FeatureFlags {
|
||||
featureFlags: FeatureFlags;
|
||||
}
|
||||
|
||||
export const FeatureFlagContext = createContext<FeatureFlagContextType>(null!);
|
||||
|
||||
interface ProviderProps {
|
||||
defaultOverrides?: Partial<FeatureFlags>;
|
||||
}
|
||||
|
||||
export const FeatureFlagProvider: FC<ProviderProps> = ({
|
||||
children,
|
||||
defaultOverrides,
|
||||
}) => {
|
||||
const [featureFlags, setFeatureFlags] = useState({
|
||||
...defaultFlags,
|
||||
...defaultOverrides,
|
||||
});
|
||||
|
||||
const convertFlagsToKeys = (flags: Object) => {
|
||||
return Object.keys(flags).reduce((acc, flag) => {
|
||||
// Perform any manipulations to remove unwanted characters
|
||||
const formattedFlag = flag
|
||||
// Remove `character-app` from the flag name
|
||||
.replace(/character-app\./i, "")
|
||||
// Remove `release-gate` from the flag name
|
||||
.replace(/release-gate\W/i, "")
|
||||
// Remove `character-sheet` from flag name if not the tour flag
|
||||
.replace(/character-sheet\W(?!tour)/i, "")
|
||||
// Change `2d` to `tactical` since var can't start with number
|
||||
.replace(/2d/i, "tactical");
|
||||
|
||||
const key = toCamelCase(`${formattedFlag}Flag`);
|
||||
return { ...acc, [key]: flags[flag] };
|
||||
}, {});
|
||||
};
|
||||
|
||||
const getFeatureFlags = async () => {
|
||||
try {
|
||||
const req = await getFeatureFlagsFromCharacterService(featureFlagsToGet);
|
||||
const res = await req.json();
|
||||
const flags = convertFlagsToKeys(res.data) as FeatureFlags;
|
||||
setFeatureFlags(flags);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!defaultOverrides) getFeatureFlags();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultOverrides]);
|
||||
|
||||
return (
|
||||
<FeatureFlagContext.Provider value={{ ...featureFlags, featureFlags }}>
|
||||
{children}
|
||||
</FeatureFlagContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useFeatureFlags = () => {
|
||||
return useContext(FeatureFlagContext);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createContext, FC, useContext, useState } from "react";
|
||||
|
||||
export interface FiltersContextType {
|
||||
showItemTypes: boolean;
|
||||
setShowItemTypes: (show: boolean) => void;
|
||||
showItemSourceCategories: boolean;
|
||||
setShowItemSourceCategories: (show: boolean) => void;
|
||||
showSpellLevels: boolean;
|
||||
setShowSpellLevels: (show: boolean) => void;
|
||||
showSpellSourceCategories: boolean;
|
||||
setShowSpellSourceCategories: (show: boolean) => void;
|
||||
}
|
||||
|
||||
export const FiltersContext = createContext<FiltersContextType>(null!);
|
||||
|
||||
export const FiltersProvider: FC = ({ children }) => {
|
||||
const [showItemTypes, setShowItemTypes] = useState(true);
|
||||
const [showItemSourceCategories, setShowItemSourceCategories] =
|
||||
useState(true);
|
||||
|
||||
const [showSpellLevels, setShowSpellLevels] = useState(true);
|
||||
const [showSpellSourceCategories, setShowSpellSourceCategories] =
|
||||
useState(true);
|
||||
|
||||
return (
|
||||
<FiltersContext.Provider
|
||||
value={{
|
||||
showItemTypes,
|
||||
setShowItemTypes,
|
||||
showItemSourceCategories,
|
||||
setShowItemSourceCategories,
|
||||
showSpellLevels,
|
||||
setShowSpellLevels,
|
||||
showSpellSourceCategories,
|
||||
setShowSpellSourceCategories,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</FiltersContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useFiltersContext = () => {
|
||||
return useContext(FiltersContext);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { createContext, useContext, useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
interface HeadContextProps {
|
||||
title: string;
|
||||
setTitle: (title: string) => void;
|
||||
}
|
||||
|
||||
export const HeadContext = createContext<HeadContextProps>(null!);
|
||||
|
||||
export const HeadContextProvider = ({ children }) => {
|
||||
const [title, setTitle] = useState("Character App");
|
||||
|
||||
const setNewTitle = (title) => {
|
||||
setTitle(`${title} - D&D Beyond`);
|
||||
};
|
||||
|
||||
return (
|
||||
<HeadContext.Provider value={{ title, setTitle: setNewTitle }}>
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
</Helmet>
|
||||
{children}
|
||||
</HeadContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useHeadContext = () => {
|
||||
const context = useContext(HeadContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useHead must be used within a HeadProvider");
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
@@ -0,0 +1,174 @@
|
||||
import { isEqual } from "lodash";
|
||||
import { createContext, FC, useContext, useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
PaneComponentEnum,
|
||||
PaneComponentInfo,
|
||||
PaneIdentifiers,
|
||||
SidebarAlignmentEnum,
|
||||
SidebarPlacementEnum,
|
||||
} from "~/subApps/sheet/components/Sidebar/types";
|
||||
import { SIDEBAR_FIXED_POSITION_START_WIDTH } from "~/tools/js/CharacterSheet/config";
|
||||
import { appEnvSelectors } from "~/tools/js/Shared/selectors";
|
||||
|
||||
export interface SidebarInfo {
|
||||
isVisible: boolean;
|
||||
setIsVisible: (isVisible: boolean) => void;
|
||||
isLocked: boolean;
|
||||
setIsLocked: (isLocked: boolean) => void;
|
||||
placement: SidebarPlacementEnum;
|
||||
setPlacement: (placement: SidebarPlacementEnum) => void;
|
||||
alignment: SidebarAlignmentEnum;
|
||||
setAlignment: (alignment: SidebarAlignmentEnum) => void;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export interface PaneInfo {
|
||||
activePane: PaneComponentInfo | null;
|
||||
showControls: boolean;
|
||||
isAtStart: boolean;
|
||||
isAtEnd: boolean;
|
||||
paneHistoryStart: (
|
||||
componentType: PaneComponentEnum,
|
||||
componentIdentifiers?: PaneIdentifiers | null
|
||||
) => void;
|
||||
paneHistoryPush: (
|
||||
componentType: PaneComponentEnum,
|
||||
componentIdentifiers?: PaneIdentifiers | null
|
||||
) => void;
|
||||
paneHistoryPrevious: () => void;
|
||||
paneHistoryNext: () => void;
|
||||
}
|
||||
|
||||
export interface SidebarContextType {
|
||||
sidebar: SidebarInfo;
|
||||
pane: PaneInfo;
|
||||
}
|
||||
|
||||
export const SidebarContext = createContext<SidebarContextType>(null!);
|
||||
|
||||
export const SidebarProvider: FC = ({ children }) => {
|
||||
const isMobile = useSelector(appEnvSelectors.getIsMobile);
|
||||
const sheetDimensions = useSelector(appEnvSelectors.getDimensions);
|
||||
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
const [placement, setPlacement] = useState(SidebarPlacementEnum.OVERLAY);
|
||||
const [alignment, setAlignment] = useState(SidebarAlignmentEnum.RIGHT);
|
||||
|
||||
const [paneIdx, setPaneIdx] = useState<number>(0);
|
||||
const [paneHistory, setPaneHistory] = useState<Array<PaneComponentInfo>>([]);
|
||||
|
||||
const [activePane, setActivePane] = useState<PaneComponentInfo | null>(null);
|
||||
|
||||
const width: number = 340;
|
||||
|
||||
//Create a new pane history
|
||||
const paneHistoryStart = (
|
||||
type: PaneComponentEnum,
|
||||
identifiers: PaneIdentifiers | null = null
|
||||
) => {
|
||||
setIsVisible(true);
|
||||
setPaneIdx(0);
|
||||
setPaneHistory([
|
||||
{
|
||||
type,
|
||||
identifiers,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
//Add a new pane to the history
|
||||
const paneHistoryPush = (
|
||||
type: PaneComponentEnum,
|
||||
identifiers: PaneIdentifiers | null = null
|
||||
) => {
|
||||
if (paneIdx === null) {
|
||||
paneHistoryStart(type, identifiers);
|
||||
} else {
|
||||
// get current active pane (last in history)
|
||||
const activeHistoryElement = {
|
||||
identifiers: activePane?.identifiers,
|
||||
type: activePane?.type,
|
||||
};
|
||||
|
||||
// create a new node for the history
|
||||
const newHistoryElement = { identifiers, type };
|
||||
|
||||
// compare the current active pane with the new one (last element in history with new one)
|
||||
if (!isEqual(activeHistoryElement, newHistoryElement)) {
|
||||
// Check if new history element is different than current - otherwise skip to avoid duplicates
|
||||
let newHistory = [
|
||||
...paneHistory.slice(0, paneIdx + 1),
|
||||
newHistoryElement,
|
||||
];
|
||||
setPaneHistory(newHistory);
|
||||
setPaneIdx(newHistory.length - 1);
|
||||
}
|
||||
}
|
||||
setIsVisible(true);
|
||||
};
|
||||
|
||||
//Go back to the previous pane in the history
|
||||
const paneHistoryPrevious = () => {
|
||||
setPaneIdx(Math.max(0, paneIdx - 1));
|
||||
};
|
||||
|
||||
//Go to the next pane in the history
|
||||
const paneHistoryNext = () => {
|
||||
setPaneIdx(Math.min(paneHistory.length - 1, paneIdx + 1));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
//If the window less than the fixed position start width (1600px), set the placement to overlay
|
||||
if (
|
||||
isMobile ||
|
||||
sheetDimensions.window.width < SIDEBAR_FIXED_POSITION_START_WIDTH
|
||||
) {
|
||||
setPlacement(SidebarPlacementEnum.OVERLAY);
|
||||
}
|
||||
|
||||
//set Right alignment if mobile
|
||||
setAlignment(isMobile ? SidebarAlignmentEnum.RIGHT : alignment);
|
||||
}, [isMobile, sheetDimensions]);
|
||||
|
||||
useEffect(() => {
|
||||
const activePane = paneHistory[paneIdx];
|
||||
setActivePane(activePane);
|
||||
}, [paneIdx, paneHistory]);
|
||||
|
||||
return (
|
||||
<SidebarContext.Provider
|
||||
value={{
|
||||
sidebar: {
|
||||
isVisible,
|
||||
setIsVisible,
|
||||
isLocked,
|
||||
setIsLocked,
|
||||
placement,
|
||||
setPlacement,
|
||||
alignment,
|
||||
setAlignment,
|
||||
width,
|
||||
},
|
||||
pane: {
|
||||
activePane,
|
||||
showControls: paneHistory.length > 1,
|
||||
isAtStart: paneIdx === 0,
|
||||
isAtEnd: paneIdx === paneHistory.length - 1,
|
||||
paneHistoryStart,
|
||||
paneHistoryPush,
|
||||
paneHistoryPrevious,
|
||||
paneHistoryNext,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SidebarContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSidebar = () => {
|
||||
return useContext(SidebarContext);
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import createCache from "@emotion/cache";
|
||||
import { CacheProvider } from "@emotion/react";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { useState, createContext, useEffect, useMemo } from "react";
|
||||
import { prefixer } from "stylis";
|
||||
|
||||
import { getTheme } from "~/theme";
|
||||
|
||||
type PaletteOpt = "light" | "dark";
|
||||
|
||||
interface Manager {
|
||||
lightOrDark: PaletteOpt;
|
||||
primary?: string;
|
||||
}
|
||||
|
||||
interface ThemeManagerContext {
|
||||
manager: Manager;
|
||||
setManager: (manager: Manager) => void;
|
||||
}
|
||||
|
||||
export const ThemeManager = createContext<ThemeManagerContext>(null!);
|
||||
export const ThemeManagerProvider = ({
|
||||
children,
|
||||
manager: propsManager,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
manager?: Manager;
|
||||
}) => {
|
||||
const [manager, setManager] = useState<Manager>(
|
||||
propsManager || {
|
||||
lightOrDark: "light",
|
||||
}
|
||||
);
|
||||
useEffect(() => {
|
||||
if (propsManager) {
|
||||
setManager(propsManager);
|
||||
}
|
||||
}, [propsManager]);
|
||||
// HACK
|
||||
/**
|
||||
* We are doing some hacky things to
|
||||
* win is styling conflicts this can
|
||||
* go away once we no longer need
|
||||
* waterdeep styles
|
||||
*/
|
||||
function hack__createExtraScopePlugin(...extra) {
|
||||
const scopes = extra.map((scope) => `${scope.trim()} `);
|
||||
return (element) => {
|
||||
if (element.type !== "rule") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (element.root?.type === "@keyframes") {
|
||||
return;
|
||||
}
|
||||
|
||||
element.props = element.props
|
||||
.map((prop) => scopes.map((scope) => scope + prop))
|
||||
.reduce((scopesArray, scope) => scopesArray.concat(scope), []);
|
||||
};
|
||||
}
|
||||
|
||||
const hack__cache = useMemo(
|
||||
() =>
|
||||
createCache({
|
||||
key: "ddb-character-app",
|
||||
container: document.body,
|
||||
prepend: true,
|
||||
stylisPlugins: [
|
||||
hack__createExtraScopePlugin("body"),
|
||||
// has to be included manually when customizing `stylisPlugins` if you want to have vendor prefixes added automatically
|
||||
prefixer,
|
||||
],
|
||||
}),
|
||||
[]
|
||||
);
|
||||
/**
|
||||
* End the hack
|
||||
*/
|
||||
const theme = getTheme(manager.lightOrDark, manager.primary);
|
||||
return (
|
||||
<ThemeManager.Provider value={{ manager, setManager }}>
|
||||
<CacheProvider value={hack__cache}>
|
||||
<ThemeProvider theme={theme}>{children}</ThemeProvider>
|
||||
</CacheProvider>
|
||||
</ThemeManager.Provider>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user