From 6e317d4c5b2ee80be9f3d4b515e74c03fe9522a3 Mon Sep 17 00:00:00 2001 From: Lucas Pickering Date: Sat, 29 Jul 2023 05:56:32 -0400 Subject: [PATCH] Use BetaMove.target in UI --- ui/relay.config.js | 3 + ui/src/components/CoreContent.tsx | 4 + .../EditorControls/BetaMoveListItem.tsx | 11 +-- .../EditorSvg/BetaEditor/BetaMoveMark.tsx | 26 +++--- ui/src/components/Editor/util/moves.ts | 41 +++------ .../Editor/util/useBetaMoveMutations.ts | 89 ++++++++++++------- ui/src/util/resolvers/BetaMoveIsFree.ts | 34 +++++++ ui/src/util/resolvers/BetaMovePosition.ts | 49 ++++++++++ 8 files changed, 176 insertions(+), 81 deletions(-) create mode 100644 ui/src/util/resolvers/BetaMoveIsFree.ts create mode 100644 ui/src/util/resolvers/BetaMovePosition.ts diff --git a/ui/relay.config.js b/ui/relay.config.js index 9b40a0c6..e1c0e007 100644 --- a/ui/relay.config.js +++ b/ui/relay.config.js @@ -4,4 +4,7 @@ module.exports = { language: "typescript", excludes: ["/node_modules/", "/build/"], noFutureProofEnums: true, + featureFlags: { + enable_relay_resolver_transform: true, + }, }; diff --git a/ui/src/components/CoreContent.tsx b/ui/src/components/CoreContent.tsx index 64f95083..17c104e0 100644 --- a/ui/src/components/CoreContent.tsx +++ b/ui/src/components/CoreContent.tsx @@ -2,6 +2,7 @@ import environment from "util/environment"; import theme, { globalStyles } from "util/theme"; import React, { Suspense } from "react"; import { BrowserRouter } from "react-router-dom"; +import relayRuntime from "relay-runtime"; import { RelayEnvironmentProvider } from "react-relay"; import { ThemeProvider } from "@mui/material/styles"; import { CssBaseline, GlobalStyles } from "@mui/material"; @@ -17,6 +18,9 @@ import LogInPage from "./Account/LogInPage"; import UserQueryProvider from "./UserQueryProvider"; import GuestUserWarningDialog from "./Account/GuestUserWarningDialog"; +// https://relay.dev/docs/guides/relay-resolvers/#current-limitations +relayRuntime.RelayFeatureFlags.ENABLE_RELAY_RESOLVERS = true; + // Code splitting! const HomePage = React.lazy( () => import(/* webpackChunkName: "Home" */ "components/Home/HomePage") diff --git a/ui/src/components/Editor/EditorControls/BetaMoveListItem.tsx b/ui/src/components/Editor/EditorControls/BetaMoveListItem.tsx index d38f4279..5608ea5c 100644 --- a/ui/src/components/Editor/EditorControls/BetaMoveListItem.tsx +++ b/ui/src/components/Editor/EditorControls/BetaMoveListItem.tsx @@ -1,8 +1,7 @@ import { isDefined } from "util/func"; -import React from "react"; import { - DragHandle as IconDragHandle, Close as IconClose, + DragHandle as IconDragHandle, } from "@mui/icons-material"; import { Box, @@ -14,6 +13,7 @@ import { alpha, } from "@mui/material"; import { formatBodyPart } from "components/Editor/util/svg"; +import React from "react"; import { graphql, useFragment } from "react-relay"; import { BetaMoveIconWrapped } from "../EditorSvg/BetaEditor/BetaMoveIcon"; import { useBetaMoveColor } from "../util/moves"; @@ -54,14 +54,11 @@ const BetaMoveListItem = React.forwardRef( order annotation isStart - hold { - id - } + isFree @required(action: THROW) } `, betaMoveKey ); - const isFree = !isDefined(betaMove.hold); const color = useBetaMoveColor()(betaMove.id); return ( @@ -117,7 +114,7 @@ const BetaMoveListItem = React.forwardRef( order={betaMove.order} color={color} isStart={betaMove.isStart} - isFree={isFree} + isFree={betaMove.isFree} /> diff --git a/ui/src/components/Editor/EditorSvg/BetaEditor/BetaMoveMark.tsx b/ui/src/components/Editor/EditorSvg/BetaEditor/BetaMoveMark.tsx index bbb7e030..58418903 100644 --- a/ui/src/components/Editor/EditorSvg/BetaEditor/BetaMoveMark.tsx +++ b/ui/src/components/Editor/EditorSvg/BetaEditor/BetaMoveMark.tsx @@ -1,27 +1,27 @@ import { isDefined } from "util/func"; -import { useRef, useState } from "react"; +import { + Delete as IconDelete, + Notes as IconNotes, + OpenWith as IconOpenWith, +} from "@mui/icons-material"; +import { ClickAwayListener, useTheme } from "@mui/material"; import { DragFinishHandler, useDrag, useDragLayer, } from "components/Editor/util/dnd"; -import { ClickAwayListener, useTheme } from "@mui/material"; -import { graphql, useFragment } from "react-relay"; import { useBetaMoveColor, useBetaMoveVisualPosition, } from "components/Editor/util/moves"; -import { - OpenWith as IconOpenWith, - Delete as IconDelete, - Notes as IconNotes, -} from "@mui/icons-material"; +import { useRef, useState } from "react"; +import { graphql, useFragment } from "react-relay"; +import ActionOrb from "../common/ActionOrb"; import ActionOrbs from "../common/ActionOrbs"; import Positioned from "../common/Positioned"; -import ActionOrb from "../common/ActionOrb"; import SvgTooltip from "../common/SvgTooltip"; -import { BetaMoveMark_betaMoveNode$key } from "./__generated__/BetaMoveMark_betaMoveNode.graphql"; import BetaMoveIcon from "./BetaMoveIcon"; +import { BetaMoveMark_betaMoveNode$key } from "./__generated__/BetaMoveMark_betaMoveNode.graphql"; interface Props { betaMoveKey: BetaMoveMark_betaMoveNode$key; @@ -49,11 +49,9 @@ const BetaMoveMark: React.FC = ({ id bodyPart order - hold { - id - } isStart annotation + isFree @required(action: THROW) } `, betaMoveKey @@ -116,7 +114,7 @@ const BetaMoveMark: React.FC = ({ bodyPart={betaMove.bodyPart} order={betaMove.order} isStart={betaMove.isStart} - isFree={!isDefined(betaMove.hold)} + isFree={betaMove.isFree} // Override color while relocating color={isRelocating ? palette.editor.actions.relocate.main : color} icon={isRelocating ? : undefined} diff --git a/ui/src/components/Editor/util/moves.ts b/ui/src/components/Editor/util/moves.ts index cf855548..820e5b4c 100644 --- a/ui/src/components/Editor/util/moves.ts +++ b/ui/src/components/Editor/util/moves.ts @@ -11,15 +11,15 @@ import { moveArrayElement, } from "util/func"; import { hexToHtml, htmlToHex, lerpColor } from "util/math"; -import { disambiguationDistance } from "styles/svg"; -import { useContext, useCallback } from "react"; -import { graphql } from "relay-runtime"; -import { useFragment } from "react-relay"; import { useTheme } from "@mui/material"; -import { add, BodyPart, OverlayPosition, polarToSvg } from "./svg"; -import { BetaContext } from "./context"; -import { moves_visualPositions_betaMoveNodeConnection$key } from "./__generated__/moves_visualPositions_betaMoveNodeConnection.graphql"; +import { useCallback, useContext } from "react"; +import { useFragment } from "react-relay"; +import { graphql } from "relay-runtime"; +import { disambiguationDistance } from "styles/svg"; import { moves_colors_betaMoveNodeConnection$key } from "./__generated__/moves_colors_betaMoveNodeConnection.graphql"; +import { moves_visualPositions_betaMoveNodeConnection$key } from "./__generated__/moves_visualPositions_betaMoveNodeConnection.graphql"; +import { BetaContext } from "./context"; +import { BodyPart, OverlayPosition, add, polarToSvg } from "./svg"; /** * List of beta moves, from Relay, that we will update locally for the purpose @@ -110,17 +110,12 @@ export function useBetaMoveVisualPositions( node { id bodyPart - hold { - id - position { - x - y + position @required(action: THROW) + target { + ... on HoldNode { + id } } - position { - x - y - } } } } @@ -133,15 +128,7 @@ export function useBetaMoveVisualPositions( // Start by just jamming every move into the map for (const { node } of moves) { - // Grab position from either the hold (for attached moves) or the move - // itself (for free moves) - const position = node.position ?? node.hold?.position; - if (position) { - positionMap.set(node.id, position); - } else { - // eslint-disable-next-line no-console - console.warn("No position available for move:", node); - } + positionMap.set(node.id, node.position); } // Next, we'll apply offsets to moves that share the same hold+body part, so @@ -156,8 +143,8 @@ export function useBetaMoveVisualPositions( for (const movesByHold of groupBy( // Exclude free moves, since they'll never share the *exact* some position // Maybe we'll need a more dynamic disambiguation, but not yet - moves.filter(({ node }) => isDefined(node.hold)), - ({ node }) => node.hold?.id + moves.filter(({ node }) => isDefined(node.target.id)), + ({ node }) => node.target.id ).values()) { const movesByBodyPart = groupBy(movesByHold, ({ node }) => node.bodyPart); diff --git a/ui/src/components/Editor/util/useBetaMoveMutations.ts b/ui/src/components/Editor/util/useBetaMoveMutations.ts index 2f7917b0..c0d6af82 100644 --- a/ui/src/components/Editor/util/useBetaMoveMutations.ts +++ b/ui/src/components/Editor/util/useBetaMoveMutations.ts @@ -2,15 +2,15 @@ import { findNode, isDefined } from "util/func"; import useMutation, { MutationState } from "util/useMutation"; import { graphql, useFragment } from "react-relay"; import { generateUniqueClientID } from "relay-runtime"; -import { DropResult } from "./dnd"; -import { useStanceControls } from "./stance"; -import { BodyPart, OverlayPosition } from "./svg"; -import { useBetaMoveMutations_createBetaMoveMutation } from "./__generated__/useBetaMoveMutations_createBetaMoveMutation.graphql"; import { useBetaMoveMutations_betaNode$key } from "./__generated__/useBetaMoveMutations_betaNode.graphql"; +import { useBetaMoveMutations_createBetaMoveMutation } from "./__generated__/useBetaMoveMutations_createBetaMoveMutation.graphql"; import { useBetaMoveMutations_deleteBetaMoveMutation } from "./__generated__/useBetaMoveMutations_deleteBetaMoveMutation.graphql"; -import { useBetaMoveMutations_updateBetaMoveAnnotationMutation } from "./__generated__/useBetaMoveMutations_updateBetaMoveAnnotationMutation.graphql"; import { useBetaMoveMutations_relocateBetaMoveMutation } from "./__generated__/useBetaMoveMutations_relocateBetaMoveMutation.graphql"; +import { useBetaMoveMutations_updateBetaMoveAnnotationMutation } from "./__generated__/useBetaMoveMutations_updateBetaMoveAnnotationMutation.graphql"; +import { DropResult } from "./dnd"; import { createBetaMoveLocal, deleteBetaMoveLocal } from "./moves"; +import { useStanceControls } from "./stance"; +import { BodyPart, OverlayPosition } from "./svg"; interface Mutation { callback: (data: T) => void; @@ -117,17 +117,20 @@ function useBetaMoveMutations(betaKey: useBetaMoveMutations_betaNode$key): { # These are the only fields we modify # Yes, we need to refetch both positions, in case the move was # converted from free to attached or vice versa - hold { - id - position { + target { + __typename + ... on HoldNode { + id + position { + x + y + } + } + ... on SVGPosition { x y } } - position { - x - y - } } } `); @@ -181,10 +184,7 @@ function useBetaMoveMutations(betaKey: useBetaMoveMutations_betaNode$key): { bodyPart, annotation: "", isStart: false, // Punting on calculating this for now - hold: - dropResult.kind === "hold" ? { id: dropResult.holdId } : null, - position: - dropResult.kind === "dropZone" ? dropResult.position : null, + target: getOptimisticTarget(dropResult), beta: { id: beta.id, moves: createBetaMoveLocal(beta.moves, optimisticId, newOrder), @@ -217,7 +217,7 @@ function useBetaMoveMutations(betaKey: useBetaMoveMutations_betaNode$key): { optimisticResponse: { updateBetaMove: { id: betaMoveId, - ...getDropResponse(dropResult), + target: getOptimisticTarget(dropResult), }, }, }); @@ -258,12 +258,19 @@ graphql` order annotation isStart - hold { - id - } - position { - x - y + target { + __typename + ... on HoldNode { + id + position { + x + y + } + } + ... on SVGPosition { + x + y + } } } `; @@ -286,24 +293,40 @@ function getDropParams( } /** - * Get a set of common response fields for a move mutation, related to the - * object it was dropped onto. These can be used for an optimistic response. + * Get an optimistic value for the `target` field, basd on the object it was + * dropped onto. * @param dropResult Object that the move was dropped onto to trigger the mutation (hold or free?) - * @returns Params to be passed to a move mutation + * @returns Valid value for the `target` field */ -function getDropResponse( +function getOptimisticTarget( dropResult: DropResult<"overlayBetaMove"> -): - | { hold: { id: string; position: OverlayPosition }; position: null } - | { hold: null; position: OverlayPosition } { +): // These response types have some weird cruft because of the types that Relay generates :( +| { + __typename: "HoldNode"; + __isNode: "HoldNode"; + id: string; + position: OverlayPosition; + } + | ({ + __typename: "SVGPosition"; + __isNode: "SVGPosition"; + id: string; + } & OverlayPosition) { switch (dropResult.kind) { case "hold": return { - hold: { id: dropResult.holdId, position: dropResult.position }, - position: null, + __typename: "HoldNode", + __isNode: "HoldNode", + id: dropResult.holdId, + position: dropResult.position, }; case "dropZone": - return { hold: null, position: dropResult.position }; + return { + __typename: "SVGPosition", + __isNode: "SVGPosition", + id: "", + ...dropResult.position, + }; } } diff --git a/ui/src/util/resolvers/BetaMoveIsFree.ts b/ui/src/util/resolvers/BetaMoveIsFree.ts new file mode 100644 index 00000000..7ce3b53d --- /dev/null +++ b/ui/src/util/resolvers/BetaMoveIsFree.ts @@ -0,0 +1,34 @@ +import { graphql } from "relay-runtime"; +import { readFragment } from "relay-runtime/lib/store/ResolverFragments"; +import { BetaMoveIsFreeResolver$key } from "./__generated__/BetaMoveIsFreeResolver.graphql"; + +/** + * @RelayResolver + * + * @onType BetaMoveNode + * @fieldName isFree + * @rootFragment BetaMoveIsFreeResolver + * + * Client-side resolver for BetaMove.isFree + * https://relay.dev/docs/guides/relay-resolvers/ + */ +export default function betaMoveIsFreeResolver( + betaMoveKey: BetaMoveIsFreeResolver$key +): boolean { + const betaMove = readFragment( + graphql` + fragment BetaMoveIsFreeResolver on BetaMoveNode { + target { + __typename + # Needed to get relay to type __typename + ... on HoldNode { + id + } + } + } + `, + betaMoveKey + ); + + return betaMove.target.__typename !== "HoldNode"; +} diff --git a/ui/src/util/resolvers/BetaMovePosition.ts b/ui/src/util/resolvers/BetaMovePosition.ts new file mode 100644 index 00000000..5c77bc85 --- /dev/null +++ b/ui/src/util/resolvers/BetaMovePosition.ts @@ -0,0 +1,49 @@ +import { OverlayPosition } from "components/Editor/util/svg"; +import { graphql } from "relay-runtime"; +import { readFragment } from "relay-runtime/lib/store/ResolverFragments"; +import { BetaMovePositionResolver$key } from "./__generated__/BetaMovePositionResolver.graphql"; + +/** + * @RelayResolver + * + * @onType BetaMoveNode + * @fieldName position + * @rootFragment BetaMovePositionResolver + * + * Client-side resolver for BetaMove.position + * https://relay.dev/docs/guides/relay-resolvers/ + */ +export default function betaMovePositionResolver( + betaMoveKey: BetaMovePositionResolver$key +): OverlayPosition { + const betaMove = readFragment( + graphql` + fragment BetaMovePositionResolver on BetaMoveNode { + target { + __typename + ... on HoldNode { + id + position { + x + y + } + } + ... on SVGPosition { + x + y + } + } + } + `, + betaMoveKey + ); + + switch (betaMove.target.__typename) { + case "HoldNode": + return betaMove.target.position; + case "SVGPosition": + return betaMove.target; + case "%other": + throw new Error("Unreachable"); + } +}