Add AppProvider to wardrobe-2020

Hey the app runs now! How exciting! It doesn't run *correctly* but it renders at all!!
This commit is contained in:
Emi Matchu 2023-08-10 18:09:10 -07:00
parent aa76fbc933
commit 8d7eabf1e3
14 changed files with 549 additions and 64 deletions

View file

@ -1,7 +1,19 @@
import React from "react";
import ReactDOM from "react-dom";
import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";
import { WardrobePage } from "./wardrobe-2020";
import { AppProvider, WardrobePage } from "./wardrobe-2020";
// Use Apollo's error messages in development.
if (process.env["NODE_ENV"] === "development") {
loadErrorMessages();
loadDevMessages();
}
const rootNode = document.querySelector("#wardrobe-2020-root");
ReactDOM.render(<WardrobePage />, rootNode);
ReactDOM.render(
<AppProvider>
<WardrobePage />
</AppProvider>,
rootNode
);

View file

@ -0,0 +1,157 @@
import React from "react";
import * as Sentry from "@sentry/react";
import { Integrations } from "@sentry/tracing";
import { Auth0Provider } from "@auth0/auth0-react";
import { CSSReset, ChakraProvider, extendTheme } from "@chakra-ui/react";
import { ApolloProvider } from "@apollo/client";
import { useAuth0 } from "@auth0/auth0-react";
import { mode } from "@chakra-ui/theme-tools";
import { BrowserRouter } from "react-router-dom";
import buildApolloClient from "./apolloClient";
const theme = extendTheme({
styles: {
global: (props) => ({
html: {
// HACK: Chakra sets body as the relative position element, which is
// fine, except its `min-height: 100%` doesn't actually work
// unless paired with height on the root element too!
height: "100%",
},
body: {
background: mode("gray.50", "gray.800")(props),
color: mode("green.800", "green.50")(props),
transition: "all 0.25s",
},
}),
},
});
export default function AppProvider({ children }) {
React.useEffect(() => setupLogging(), []);
return (
<BrowserRouter>
<Auth0Provider
domain="openneo.us.auth0.com"
clientId="8LjFauVox7shDxVufQqnviUIywMuuC4r"
redirectUri={
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: "https://impress-2020.openneo.net"
}
audience="https://impress-2020.openneo.net/api"
scope=""
>
<DTIApolloProvider>
<ChakraProvider theme={theme}>
<CSSReset />
{children}
</ChakraProvider>
</DTIApolloProvider>
</Auth0Provider>
</BrowserRouter>
);
}
function DTIApolloProvider({ children, additionalCacheState = {} }) {
const auth0 = useAuth0();
const auth0Ref = React.useRef(auth0);
React.useEffect(() => {
auth0Ref.current = auth0;
}, [auth0]);
// Save the first `additionalCacheState` we get as our `initialCacheState`,
// which we'll use to initialize the client without having to wait a tick.
const [initialCacheState, unusedSetInitialCacheState] =
React.useState(additionalCacheState);
const client = React.useMemo(
() =>
buildApolloClient({
getAuth0: () => auth0Ref.current,
initialCacheState,
}),
[initialCacheState]
);
// When we get a new `additionalCacheState` object, merge it into the cache:
// copy the previous cache state, merge the new cache state's entries in,
// and "restore" the new merged cache state.
//
// HACK: Using `useMemo` for this is a dastardly trick!! What we want is the
// semantics of `useEffect` kinda, but we need to ensure it happens
// *before* all the children below get rendered, so they don't fire off
// unnecessary network requests. Using `useMemo` but throwing away the
// result kinda does that. It's evil! It's nasty! It's... perfect?
// (This operation is safe to run multiple times too, in case memo
// re-runs it. It's just, y'know, a performance loss. Maybe it's
// actually kinda perfect lol)
//
// I feel like there's probably a better way to do this... like, I want
// the semantic of replacing this client with an updated client - but I
// don't want to actually replace the client, because that'll break
// other kinds of state, like requests loading in the shared layout.
// Idk! I'll see how it goes!
React.useMemo(() => {
const previousCacheState = client.cache.extract();
const mergedCacheState = { ...previousCacheState };
for (const key of Object.keys(additionalCacheState)) {
mergedCacheState[key] = {
...mergedCacheState[key],
...additionalCacheState[key],
};
}
console.debug(
"Merging Apollo cache:",
additionalCacheState,
mergedCacheState
);
client.cache.restore(mergedCacheState);
}, [client, additionalCacheState]);
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
function setupLogging() {
Sentry.init({
dsn: "https://c55875c3b0904264a1a99e5b741a221e@o506079.ingest.sentry.io/5595379",
autoSessionTracking: true,
integrations: [
new Integrations.BrowserTracing({
beforeNavigate: (context) => ({
...context,
// Assume any path segment starting with a digit is an ID, and replace
// it with `:id`. This will help group related routes in Sentry stats.
// NOTE: I'm a bit uncertain about the timing on this for tracking
// client-side navs... but we now only track first-time
// pageloads, and it definitely works correctly for them!
name: window.location.pathname.replaceAll(/\/[0-9][^/]*/g, "/:id"),
}),
// We have a _lot_ of location changes that don't actually signify useful
// navigations, like in the wardrobe page. It could be useful to trace
// them with better filtering someday, but frankly we don't use the perf
// features besides Web Vitals right now, and those only get tracked on
// first-time pageloads, anyway. So, don't track client-side navs!
startTransactionOnLocationChange: false,
}),
],
denyUrls: [
// Don't log errors that were probably triggered by extensions and not by
// our own app. (Apparently Sentry's setting to ignore browser extension
// errors doesn't do this anywhere near as consistently as I'd expect?)
//
// Adapted from https://gist.github.com/impressiver/5092952, as linked in
// https://docs.sentry.io/platforms/javascript/configuration/filtering/.
/^chrome-extension:\/\//,
/^moz-extension:\/\//,
],
// Since we're only tracking first-page loads and not navigations, 100%
// sampling isn't actually so much! Tune down if it becomes a problem, tho.
tracesSampleRate: 1.0,
});
}

View file

@ -854,7 +854,7 @@ function ItemPageOutfitPreview({ itemId }) {
name
restrictedZones {
id
label @client
label
}
compatibleBodiesAndTheirZones {
body {
@ -867,7 +867,7 @@ function ItemPageOutfitPreview({ itemId }) {
}
zones {
id
label @client
label
}
}
canonicalAppearance(

View file

@ -80,12 +80,7 @@ function WardrobePage() {
// that need it, where it's more useful and more performant to access
// via context.
return (
<>
<OutfitStateContext.Provider value={outfitState}>
<SupportOnly>
<WardrobeDevHacks />
</SupportOnly>
<WardrobePageLayout
previewAndControls={
<WardrobePreviewAndControls
@ -113,7 +108,6 @@ function WardrobePage() {
}
/>
</OutfitStateContext.Provider>
</>
);
}

View file

@ -46,7 +46,7 @@ function PosePickerSupport({
id
zone {
id
label @client
label
}
# For AppearanceLayerSupportModal
@ -59,7 +59,7 @@ function PosePickerSupport({
}
restrictedZones {
id
label @client
label
}
# For AppearanceLayerSupportModal to know the name

View file

@ -2,6 +2,7 @@ import React from "react";
import gql from "graphql-tag";
import produce, { enableMapSet } from "immer";
import { useQuery, useApolloClient } from "@apollo/client";
import { useSearchParams } from "react-router-dom";
import { itemAppearanceFragment } from "../components/useOutfitAppearance";
@ -156,13 +157,13 @@ function useOutfitState() {
layers {
zone {
id
label @client
label
}
}
restrictedZones {
id
label @client
isCommonlyUsedByItems @client
label
isCommonlyUsedByItems
}
}
}
@ -387,7 +388,7 @@ function useParseOutfitUrl() {
// stable object!
const memoizedOutfitState = React.useMemo(
() => readOutfitStateFromSearchParams(searchParams),
[query]
[searchParams]
);
return memoizedOutfitState;

View file

@ -87,13 +87,13 @@ export function useSearchResults(
layers {
zone {
id
label @client
label
}
}
restrictedZones {
id
label @client
isCommonlyUsedByItems @client
label
isCommonlyUsedByItems
}
}
}

View file

@ -0,0 +1,230 @@
import { ApolloClient, createHttpLink, InMemoryCache } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { createPersistedQueryLink } from "apollo-link-persisted-queries";
import { getAuthModeFeatureFlag } from "./components/useCurrentUser";
// Teach Apollo to load certain fields from the cache, to avoid extra network
// requests. This happens a lot - e.g. reusing data from item search on the
// outfit immediately!
const typePolicies = {
Query: {
fields: {
closetList: (_, { args, toReference }) => {
return toReference({ __typename: "ClosetList", id: args.id }, true);
},
items: (_, { args, toReference }) => {
return args.ids.map((id) =>
toReference({ __typename: "Item", id }, true)
);
},
item: (_, { args, toReference }) => {
return toReference({ __typename: "Item", id: args.id }, true);
},
petAppearanceById: (_, { args, toReference }) => {
return toReference({ __typename: "PetAppearance", id: args.id }, true);
},
species: (_, { args, toReference }) => {
return toReference({ __typename: "Species", id: args.id }, true);
},
color: (_, { args, toReference }) => {
return toReference({ __typename: "Color", id: args.id }, true);
},
outfit: (_, { args, toReference }) => {
return toReference({ __typename: "Outfit", id: args.id }, true);
},
user: (_, { args, toReference }) => {
return toReference({ __typename: "User", id: args.id }, true);
},
},
},
Item: {
fields: {
appearanceOn: (appearance, { args, readField, toReference }) => {
// If we already have this exact appearance in the cache, serve it!
if (appearance) {
return appearance;
}
// Otherwise, we're going to see if this is a standard color, in which
// case we can reuse the standard color appearance if we already have
// it! This helps for fast loading when switching between standard
// colors.
const { speciesId, colorId } = args;
console.debug(
"[appearanceOn] seeking cached appearance",
speciesId,
colorId,
readField("id")
);
const speciesStandardBodyId = readField(
"standardBodyId",
toReference({ __typename: "Species", id: speciesId })
);
const colorIsStandard = readField(
"isStandard",
toReference({ __typename: "Color", id: colorId })
);
if (speciesStandardBodyId == null || colorIsStandard == null) {
// We haven't loaded all the species/colors into cache yet. We might
// be loading them, depending on the page? Either way, return
// `undefined`, meaning we don't know how to serve this from cache.
// This will cause us to start loading it from the server.
console.debug("[appearanceOn] species/colors not loaded yet");
return undefined;
}
if (colorIsStandard) {
const itemId = readField("id");
console.debug(
"[appearanceOn] standard color, will read:",
`item-${itemId}-body-${speciesStandardBodyId}`
);
return toReference({
__typename: "ItemAppearance",
id: `item-${itemId}-body-${speciesStandardBodyId}`,
});
} else {
console.debug("[appearanceOn] non-standard color, failure");
// This isn't a standard color, so we don't support special
// cross-color caching for it. Return `undefined`, meaning we don't
// know how to serve this from cache. This will cause us to start
// loading it from the server.
return undefined;
}
},
currentUserOwnsThis: (cachedValue, { readField }) => {
if (cachedValue != null) {
return cachedValue;
}
// Do we know what items this user owns? If so, scan for this item.
const currentUserRef = readField("currentUser", {
__ref: "ROOT_QUERY",
});
if (!currentUserRef) {
return undefined;
}
const thisItemId = readField("id");
const itemsTheyOwn = readField("itemsTheyOwn", currentUserRef);
if (!itemsTheyOwn) {
return undefined;
}
const theyOwnThisItem = itemsTheyOwn.some(
(itemRef) => readField("id", itemRef) === thisItemId
);
return theyOwnThisItem;
},
currentUserWantsThis: (cachedValue, { readField }) => {
if (cachedValue != null) {
return cachedValue;
}
// Do we know what items this user owns? If so, scan for this item.
const currentUserRef = readField("currentUser", {
__ref: "ROOT_QUERY",
});
if (!currentUserRef) {
return undefined;
}
const thisItemId = readField("id");
const itemsTheyWant = readField("itemsTheyWant", currentUserRef);
if (!itemsTheyWant) {
return undefined;
}
const theyWantThisItem = itemsTheyWant.some(
(itemRef) => readField("id", itemRef) === thisItemId
);
return theyWantThisItem;
},
},
},
ClosetList: {
fields: {
// When loading the updated contents of a list, replace it entirely.
items: { merge: false },
},
},
};
const httpLink = createHttpLink({
uri: "https://impress-2020.openneo.net/api/graphql",
});
const buildAuthLink = (getAuth0) =>
setContext(async (_, { headers = {}, sendAuth = false }) => {
if (!sendAuth) {
return;
}
const token = await getAccessToken(getAuth0);
if (token) {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
}
});
// This is a temporary way to pass the DTIAuthMode feature flag back to the
// server!
const authModeLink = setContext((_, { headers = {} }) => {
const authMode = getAuthModeFeatureFlag();
return {
headers: {
...headers,
"DTI-Auth-Mode": authMode,
},
};
});
async function getAccessToken(getAuth0) {
// Wait for auth0 to stop loading, so we can maybe get a token!
// We'll do this hackily by checking every 100ms until it's true.
await new Promise((resolve) => {
function check() {
if (getAuth0().isLoading) {
setTimeout(check, 100);
} else {
resolve();
}
}
check();
});
const { isAuthenticated, getAccessTokenSilently } = getAuth0();
if (isAuthenticated) {
const token = await getAccessTokenSilently();
return token;
}
}
const buildLink = (getAuth0) =>
buildAuthLink(getAuth0)
.concat(authModeLink)
.concat(
createPersistedQueryLink({
useGETForHashedQueries: true,
})
)
.concat(httpLink);
/**
* apolloClient is the global Apollo Client instance we use for GraphQL
* queries. This is how we communicate with the server!
*/
const buildClient = ({ getAuth0, initialCacheState }) => {
return new ApolloClient({
link: buildLink(getAuth0),
cache: new InMemoryCache({ typePolicies }).restore(initialCacheState),
connectToDevTools: true,
});
};
export default buildClient;

View file

@ -31,7 +31,11 @@ function SpeciesColorPicker({
colorTestId = null,
onChange,
}) {
const { loading: loadingMeta, error: errorMeta, data: meta } = useQuery(gql`
const {
loading: loadingMeta,
error: errorMeta,
data: meta,
} = useQuery(gql`
query SpeciesColorPicker {
allSpecies {
id
@ -340,11 +344,14 @@ let cachedResponseForAllValidPetPoses = null;
* data from GraphQL serves on the first render, without a loading state.
*/
export function useAllValidPetPoses() {
const networkResponse = useFetch("/api/validPetPoses", {
const networkResponse = useFetch(
"https://impress-2020.openneo.net/api/validPetPoses",
{
responseType: "arrayBuffer",
// If we already have globally-cached valids, skip the request.
skip: cachedResponseForAllValidPetPoses != null,
});
}
);
// Use the globally-cached response if we have one, or await the network
// response if not.

View file

@ -94,8 +94,6 @@ function getVisibleLayers(petAppearance, itemAppearances) {
return visibleLayers;
}
// TODO: The web client could save bandwidth by applying @client to the `depth`
// field, because it already has zone depths cached.
export const itemAppearanceFragmentForGetVisibleLayers = gql`
fragment ItemAppearanceForGetVisibleLayers on ItemAppearance {
id
@ -113,8 +111,6 @@ export const itemAppearanceFragmentForGetVisibleLayers = gql`
}
`;
// TODO: The web client could save bandwidth by applying @client to the `depth`
// field, because it already has zone depths cached.
export const petAppearanceFragmentForGetVisibleLayers = gql`
fragment PetAppearanceForGetVisibleLayers on PetAppearance {
id

View file

@ -136,8 +136,8 @@ export const appearanceLayerFragment = gql`
knownGlitches # For HTML5 & Known Glitches UI
zone {
id
depth @client
label @client
depth
label
}
}
`;
@ -149,7 +149,7 @@ export const appearanceLayerFragmentForSupport = gql`
swfUrl # HACK: This is for Support tools, but other views don't need it
zone {
id
label @client # HACK: This is for Support tools, but other views don't need it
label # HACK: This is for Support tools, but other views don't need it
}
}
`;

View file

@ -1,3 +1,4 @@
import AppProvider from "./AppProvider";
import WardrobePage from "./WardrobePage";
export { WardrobePage };
export { AppProvider, WardrobePage };

View file

@ -9,6 +9,8 @@
"@emotion/styled": "^11.0.0",
"@loadable/component": "^5.12.0",
"@sentry/react": "^5.30.0",
"@sentry/tracing": "^5.30.0",
"apollo-link-persisted-queries": "^0.2.2",
"easeljs": "^1.0.2",
"esbuild": "^0.19.0",
"framer-motion": "^4.1.11",
@ -25,6 +27,7 @@
"tweenjs": "^1.0.2"
},
"scripts": {
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=/assets --loader:.js=jsx --loader:.png=file --loader:.svg=file --loader:.min.js=text"
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=/assets --loader:.js=jsx --loader:.png=file --loader:.svg=file --loader:.min.js=text",
"build:production": "yarn build --minify"
}
}

View file

@ -985,6 +985,17 @@
hoist-non-react-statics "^3.3.2"
tslib "^1.9.3"
"@sentry/tracing@^5.30.0":
version "5.30.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.30.0.tgz#501d21f00c3f3be7f7635d8710da70d9419d4e1f"
integrity sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==
dependencies:
"@sentry/hub" "5.30.0"
"@sentry/minimal" "5.30.0"
"@sentry/types" "5.30.0"
"@sentry/utils" "5.30.0"
tslib "^1.9.3"
"@sentry/types@5.30.0":
version "5.30.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402"
@ -1046,6 +1057,13 @@
dependencies:
tslib "^2.3.0"
"@wry/equality@^0.1.2":
version "0.1.11"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790"
integrity sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA==
dependencies:
tslib "^1.9.3"
"@wry/equality@^0.5.6":
version "0.5.6"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.5.6.tgz#cd4a533c72c3752993ab8cbf682d3d20e3cb601e"
@ -1072,6 +1090,34 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
apollo-link-persisted-queries@^0.2.2:
version "0.2.5"
resolved "https://registry.yarnpkg.com/apollo-link-persisted-queries/-/apollo-link-persisted-queries-0.2.5.tgz#76deabf68dac218d83f2fa23eebc3b25772fd914"
integrity sha512-PYWsMFcRGT9NZ6e6EK5rlhNDtcK6FR76JDy1RIngEfR6RdM5a2Z0IhZdn9RTTNB3V/+s7iWviQmoCfQrTVXu0A==
dependencies:
apollo-link "^1.2.1"
hash.js "^1.1.7"
apollo-link@^1.2.1:
version "1.2.14"
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.14.tgz#3feda4b47f9ebba7f4160bef8b977ba725b684d9"
integrity sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==
dependencies:
apollo-utilities "^1.3.0"
ts-invariant "^0.4.0"
tslib "^1.9.3"
zen-observable-ts "^0.8.21"
apollo-utilities@^1.3.0:
version "1.3.4"
resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.4.tgz#6129e438e8be201b6c55b0f13ce49d2c7175c9cf"
integrity sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==
dependencies:
"@wry/equality" "^0.1.2"
fast-json-stable-stringify "^2.0.0"
ts-invariant "^0.4.0"
tslib "^1.10.0"
aria-hidden@^1.1.1:
version "1.2.3"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954"
@ -1244,6 +1290,11 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fast-text-encoding@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867"
@ -1315,6 +1366,14 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hash.js@^1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
dependencies:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
hey-listen@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
@ -1340,6 +1399,11 @@ import-fresh@^3.2.1:
parent-module "^1.0.0"
resolve-from "^4.0.0"
inherits@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@ -1398,6 +1462,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
object-assign@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
@ -1689,7 +1758,14 @@ ts-invariant@^0.10.3:
dependencies:
tslib "^2.1.0"
tslib@^1.0.0, tslib@^1.9.3:
ts-invariant@^0.4.0:
version "0.4.4"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86"
integrity sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA==
dependencies:
tslib "^1.9.3"
tslib@^1.0.0, tslib@^1.10.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@ -1741,6 +1817,14 @@ yaml@^1.10.0:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
zen-observable-ts@^0.8.21:
version "0.8.21"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d"
integrity sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==
dependencies:
tslib "^1.9.3"
zen-observable "^0.8.0"
zen-observable-ts@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58"
@ -1748,7 +1832,7 @@ zen-observable-ts@^1.2.5:
dependencies:
zen-observable "0.8.15"
zen-observable@0.8.15:
zen-observable@0.8.15, zen-observable@^0.8.0:
version "0.8.15"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==