diff --git a/app/javascript/wardrobe-2020/ItemPageDrawer.js b/app/javascript/wardrobe-2020/ItemPageDrawer.js
deleted file mode 100644
index 65426e8d..00000000
--- a/app/javascript/wardrobe-2020/ItemPageDrawer.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from "react";
-import {
- Drawer,
- DrawerBody,
- DrawerContent,
- DrawerCloseButton,
- DrawerOverlay,
- useBreakpointValue,
-} from "@chakra-ui/react";
-
-import { ItemPageContent } from "./ItemPage";
-
-function ItemPageDrawer({ item, isOpen, onClose }) {
- const placement = useBreakpointValue({ base: "bottom", lg: "right" });
-
- return (
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export default ItemPageDrawer;
diff --git a/app/javascript/wardrobe-2020/ItemPageLayout.js b/app/javascript/wardrobe-2020/ItemPageLayout.js
deleted file mode 100644
index 38d7203e..00000000
--- a/app/javascript/wardrobe-2020/ItemPageLayout.js
+++ /dev/null
@@ -1,411 +0,0 @@
-import React from "react";
-import {
- Badge,
- Box,
- Flex,
- Popover,
- PopoverArrow,
- PopoverContent,
- PopoverTrigger,
- Portal,
- Select,
- Skeleton,
- Spinner,
- Tooltip,
- useToast,
- VStack,
-} from "@chakra-ui/react";
-import { ExternalLinkIcon, ChevronRightIcon } from "@chakra-ui/icons";
-import { gql, useMutation } from "@apollo/client";
-
-import {
- ItemBadgeList,
- ItemKindBadge,
- ItemThumbnail,
-} from "./components/ItemCard";
-import { Heading1 } from "./util";
-
-import useSupport from "./WardrobePage/support/useSupport";
-
-function ItemPageLayout({ children, item, isEmbedded }) {
- return (
-
-
- {children}
-
- );
-}
-
-function ItemPageHeader({ item, isEmbedded }) {
- return (
-
-
-
-
-
-
-
- {item?.name || "Item name here"}
-
-
-
-
-
- );
-}
-
-/**
- * SubtleSkeleton hides the skeleton animation until a second has passed, and
- * doesn't fade in the content if it loads near-instantly. This helps avoid
- * flash-of-content stuff!
- *
- * For plain Skeletons, we often use instead. But
- * that pattern doesn't work as well for wrapper skeletons where we're using
- * placeholder content for layout: we don't want the delay if the content
- * really _is_ present!
- */
-export function SubtleSkeleton({ isLoaded, ...props }) {
- const [shouldFadeIn, setShouldFadeIn] = React.useState(false);
- const [shouldShowSkeleton, setShouldShowSkeleton] = React.useState(false);
-
- React.useEffect(() => {
- const t = setTimeout(() => {
- if (!isLoaded) {
- setShouldFadeIn(true);
- }
- }, 150);
- return () => clearTimeout(t);
- });
-
- React.useEffect(() => {
- const t = setTimeout(() => setShouldShowSkeleton(true), 500);
- return () => clearTimeout(t);
- });
-
- return (
-
- );
-}
-
-function ItemPageBadges({ item, isEmbedded }) {
- const searchBadgesAreLoaded = item?.name != null && item?.isNc != null;
-
- return (
-
-
-
-
- {
- // If the createdAt date is null (loaded and empty), hide the badge.
- item?.createdAt !== null && (
-
-
- {item?.createdAt && }
-
-
- )
- }
-
-
- Classic DTI
-
-
-
-
- Jellyneo
-
-
- {item?.isNc && (
-
- {item?.ncTradeValueText && (
-
- OWLS: {item?.ncTradeValueText}
-
- )}
-
- )}
-
- {!item?.isNc && !item?.isPb && (
-
- Shop Wiz
-
- )}
-
-
- {!item?.isNc && !item?.isPb && (
-
- Super Wiz
-
- )}
-
-
- {!item?.isNc && !item?.isPb && (
-
- Trade Post
-
- )}
-
-
- {!item?.isNc && !item?.isPb && (
-
- Auctions
-
- )}
-
-
- );
-}
-
-function ItemKindBadgeWithSupportTools({ item }) {
- const { isSupportUser, supportSecret } = useSupport();
- const toast = useToast();
-
- const ncRef = React.useRef(null);
-
- const isNcAutoDetectedFromRarity =
- item?.rarityIndex === 500 || item?.rarityIndex === 0;
-
- const [mutate, { loading }] = useMutation(gql`
- mutation ItemPageSupportSetIsManuallyNc(
- $itemId: ID!
- $isManuallyNc: Boolean!
- $supportSecret: String!
- ) {
- setItemIsManuallyNc(
- itemId: $itemId
- isManuallyNc: $isManuallyNc
- supportSecret: $supportSecret
- ) {
- id
- isNc
- isManuallyNc
- }
- }
- `);
-
- if (
- isSupportUser &&
- item?.rarityIndex != null &&
- item?.isManuallyNc != null
- ) {
- // TODO: Could code-split this into a SupportOnly file...
- return (
-
-
-
-
-
-
-
-
-
-
- NC:
-
-
- {loading && }
-
-
-
- PB:
-
-
-
-
- Support 💖
-
-
-
-
-
- );
- }
-
- return ;
-}
-
-const LinkBadge = React.forwardRef(
- ({ children, href, isEmbedded, ...props }, ref) => {
- return (
-
- {children}
- {
- // We also change the icon to signal whether this will launch in a new
- // window or not!
- isEmbedded ? (
-
- ) : (
-
- )
- }
-
- );
- },
-);
-
-const fullDateFormatter = new Intl.DateTimeFormat("en-US", {
- dateStyle: "long",
-});
-const monthYearFormatter = new Intl.DateTimeFormat("en-US", {
- month: "short",
- year: "numeric",
-});
-const monthDayYearFormatter = new Intl.DateTimeFormat("en-US", {
- month: "short",
- day: "numeric",
- year: "numeric",
-});
-function ShortTimestamp({ when }) {
- const date = new Date(when);
-
- // To find the start of last month, take today, then set its date to the 1st
- // and its time to midnight (the start of this month), and subtract one
- // month. (JS handles negative months and rolls them over correctly.)
- const startOfLastMonth = new Date();
- startOfLastMonth.setDate(1);
- startOfLastMonth.setHours(0);
- startOfLastMonth.setMinutes(0);
- startOfLastMonth.setSeconds(0);
- startOfLastMonth.setMilliseconds(0);
- startOfLastMonth.setMonth(startOfLastMonth.getMonth() - 1);
-
- const dateIsOlderThanLastMonth = date < startOfLastMonth;
-
- return (
-
- {dateIsOlderThanLastMonth
- ? monthYearFormatter.format(date)
- : monthDayYearFormatter.format(date)}
-
- );
-}
-
-export default ItemPageLayout;
diff --git a/app/javascript/wardrobe-2020/ItemPage.js b/app/javascript/wardrobe-2020/ItemPageOutfitPreview.js
similarity index 53%
rename from app/javascript/wardrobe-2020/ItemPage.js
rename to app/javascript/wardrobe-2020/ItemPageOutfitPreview.js
index 7622fd2c..5e83ea20 100644
--- a/app/javascript/wardrobe-2020/ItemPage.js
+++ b/app/javascript/wardrobe-2020/ItemPageOutfitPreview.js
@@ -1,771 +1,37 @@
import React from "react";
-import { ClassNames } from "@emotion/react";
+import { useQuery } from "@apollo/client";
+import gql from "graphql-tag";
import {
AspectRatio,
- Button,
Box,
- HStack,
- IconButton,
- SkeletonText,
- Tooltip,
- VisuallyHidden,
- VStack,
- useBreakpointValue,
- useColorModeValue,
- useTheme,
- useToast,
+ Button,
Flex,
- usePrefersReducedMotion,
Grid,
- Popover,
- PopoverContent,
- PopoverTrigger,
- Checkbox,
+ IconButton,
+ Tooltip,
+ useColorModeValue,
+ usePrefersReducedMotion,
} from "@chakra-ui/react";
-import {
- CheckIcon,
- ChevronDownIcon,
- ChevronRightIcon,
- EditIcon,
- StarIcon,
- WarningIcon,
-} from "@chakra-ui/icons";
+import { EditIcon, WarningIcon } from "@chakra-ui/icons";
import { MdPause, MdPlayArrow } from "react-icons/md";
-import gql from "graphql-tag";
-import { useQuery, useMutation } from "@apollo/client";
-import ItemPageLayout, { SubtleSkeleton } from "./ItemPageLayout";
-import {
- Delay,
- logAndCapture,
- MajorErrorMessage,
- useLocalStorage,
-} from "./util";
import HTML5Badge, { layerUsesHTML5 } from "./components/HTML5Badge";
-import {
- itemAppearanceFragment,
- petAppearanceFragment,
-} from "./components/useOutfitAppearance";
-import { useOutfitPreview } from "./components/OutfitPreview";
import SpeciesColorPicker, {
useAllValidPetPoses,
getValidPoses,
getClosestPose,
} from "./components/SpeciesColorPicker";
-import useCurrentUser from "./components/useCurrentUser";
import SpeciesFacesPicker, {
colorIsBasic,
} from "./ItemPage/SpeciesFacesPicker";
+import {
+ itemAppearanceFragment,
+ petAppearanceFragment,
+} from "./components/useOutfitAppearance";
+import { useOutfitPreview } from "./components/OutfitPreview";
+import { logAndCapture, useLocalStorage } from "./util";
-// Removed for the wardrobe-2020 case.
-// TODO: Refactor this stuff, do we even need ItemPageContent really?
-// function ItemPage() {
-// const { query } = useRouter();
-// return ;
-// }
-
-/**
- * ItemPageContent is the content of ItemPage, but we also use it as the
- * entry point for ItemPageDrawer! When embedded in ItemPageDrawer, the
- * `isEmbedded` prop is true, so we know not to e.g. set the page title.
- */
-export function ItemPageContent({ itemId, isEmbedded = false }) {
- const { isLoggedIn } = useCurrentUser();
-
- const { error, data } = useQuery(
- gql`
- query ItemPage($itemId: ID!) {
- item(id: $itemId) {
- id
- name
- isNc
- isPb
- thumbnailUrl
- description
- createdAt
- ncTradeValueText
-
- # For Support users.
- rarityIndex
- isManuallyNc
- }
- }
- `,
- { variables: { itemId }, returnPartialData: true },
- );
-
- if (error) {
- return ;
- }
-
- const item = data?.item;
-
- return (
- <>
-
-
-
-
-
- {isLoggedIn && }
-
- {!isEmbedded && }
-
-
- >
- );
-}
-
-function ItemPageDescription({ description, isEmbedded }) {
- // Show 2 lines of description text placeholder on small screens, or when
- // embedded in the wardrobe page's narrow drawer. In larger contexts, show
- // just 1 line.
- const viewportNumDescriptionLines = useBreakpointValue({ base: 2, md: 1 });
- const numDescriptionLines = isEmbedded ? 2 : viewportNumDescriptionLines;
-
- return (
-
- {description ? (
- description
- ) : description === "" ? (
- (This item has no description.)
- ) : (
-
-
-
-
-
- )}
-
- );
-}
-
-const ITEM_PAGE_OWN_WANT_BUTTONS_QUERY = gql`
- query ItemPageOwnWantButtons($itemId: ID!) {
- item(id: $itemId) {
- id
- name
- currentUserOwnsThis
- currentUserWantsThis
- }
- currentUser {
- closetLists {
- id
- name
- isDefaultList
- ownsOrWantsItems
- hasItem(itemId: $itemId)
- }
- }
- }
-`;
-
-function ItemPageOwnWantButtons({ itemId }) {
- const { loading, error, data } = useQuery(ITEM_PAGE_OWN_WANT_BUTTONS_QUERY, {
- variables: { itemId },
- context: { sendAuth: true },
- });
-
- if (error) {
- return {error.message};
- }
-
- const closetLists = data?.currentUser?.closetLists || [];
- const realLists = closetLists.filter((cl) => !cl.isDefaultList);
- const ownedLists = realLists.filter((cl) => cl.ownsOrWantsItems === "OWNS");
- const wantedLists = realLists.filter((cl) => cl.ownsOrWantsItems === "WANTS");
-
- return (
-
-
-
-
- = 1}
- popoverPlacement="bottom-end"
- />
-
-
-
-
- = 1}
- popoverPlacement="bottom-start"
- />
-
- );
-}
-
-function ItemPageOwnWantListsDropdown({
- closetLists,
- item,
- isVisible,
- popoverPlacement,
-}) {
- return (
-
-
-
-
-
-
-
-
- );
-}
-
-const ItemPageOwnWantListsDropdownButton = React.forwardRef(
- ({ closetLists, isVisible, ...props }, ref) => {
- const listsToShow = closetLists.filter((cl) => cl.hasItem);
-
- let buttonText;
- if (listsToShow.length === 1) {
- buttonText = `In list: "${listsToShow[0].name}"`;
- } else if (listsToShow.length > 1) {
- const listNames = listsToShow.map((cl) => `"${cl.name}"`).join(", ");
- buttonText = `${listsToShow.length} lists: ${listNames}`;
- } else {
- buttonText = "Add to list";
- }
-
- return (
-
- {/* Flex tricks to center the text, ignoring the caret */}
-
-
- {buttonText}
-
-
-
-
-
- );
- },
-);
-
-function ItemPageOwnWantListsDropdownContent({ closetLists, item }) {
- return (
-
- {closetLists.map((closetList) => (
-
-
-
- ))}
-
- );
-}
-
-function ItemPageOwnWantsListsDropdownRow({ closetList, item }) {
- const toast = useToast();
-
- const [sendAddToListMutation] = useMutation(
- gql`
- mutation ItemPage_AddToClosetList($listId: ID!, $itemId: ID!) {
- addItemToClosetList(
- listId: $listId
- itemId: $itemId
- removeFromDefaultList: true
- ) {
- id
- hasItem(itemId: $itemId)
- }
- }
- `,
- { context: { sendAuth: true } },
- );
-
- const [sendRemoveFromListMutation] = useMutation(
- gql`
- mutation ItemPage_RemoveFromClosetList($listId: ID!, $itemId: ID!) {
- removeItemFromClosetList(
- listId: $listId
- itemId: $itemId
- ensureInSomeList: true
- ) {
- id
- hasItem(itemId: $itemId)
- }
- }
- `,
- { context: { sendAuth: true } },
- );
-
- const onChange = React.useCallback(
- (e) => {
- if (e.target.checked) {
- sendAddToListMutation({
- variables: { listId: closetList.id, itemId: item.id },
- optimisticResponse: {
- addItemToClosetList: {
- __typename: "ClosetList",
- id: closetList.id,
- hasItem: true,
- },
- },
- }).catch((error) => {
- console.error(error);
- toast({
- status: "error",
- title: `Oops, error adding "${item.name}" to "${closetList.name}!"`,
- description:
- "Check your connection and try again? Sorry about this!",
- });
- });
- } else {
- sendRemoveFromListMutation({
- variables: { listId: closetList.id, itemId: item.id },
- optimisticResponse: {
- removeItemFromClosetList: {
- __typename: "ClosetList",
- id: closetList.id,
- hasItem: false,
- },
- },
- }).catch((error) => {
- console.error(error);
- toast({
- status: "error",
- title: `Oops, error removing "${item.name}" from "${closetList.name}!"`,
- description:
- "Check your connection and try again? Sorry about this!",
- });
- });
- }
- },
- [
- closetList,
- item,
- sendAddToListMutation,
- sendRemoveFromListMutation,
- toast,
- ],
- );
-
- return (
-
- {closetList.name}
-
- );
-}
-
-function ItemPageOwnButton({ itemId, isChecked }) {
- const theme = useTheme();
- const toast = useToast();
-
- const [sendAddMutation] = useMutation(
- gql`
- mutation ItemPageOwnButtonAdd($itemId: ID!) {
- addToItemsCurrentUserOwns(itemId: $itemId) {
- id
- currentUserOwnsThis
- }
- }
- `,
- {
- variables: { itemId },
- context: { sendAuth: true },
- optimisticResponse: {
- __typename: "Mutation",
- addToItemsCurrentUserOwns: {
- __typename: "Item",
- id: itemId,
- currentUserOwnsThis: true,
- },
- },
- // TODO: Refactor the mutation result to include closet lists
- refetchQueries: [
- {
- query: ITEM_PAGE_OWN_WANT_BUTTONS_QUERY,
- variables: { itemId },
- context: { sendAuth: true },
- },
- ],
- },
- );
-
- const [sendRemoveMutation] = useMutation(
- gql`
- mutation ItemPageOwnButtonRemove($itemId: ID!) {
- removeFromItemsCurrentUserOwns(itemId: $itemId) {
- id
- currentUserOwnsThis
- }
- }
- `,
- {
- variables: { itemId },
- context: { sendAuth: true },
- optimisticResponse: {
- __typename: "Mutation",
- removeFromItemsCurrentUserOwns: {
- __typename: "Item",
- id: itemId,
- currentUserOwnsThis: false,
- },
- },
- // TODO: Refactor the mutation result to include closet lists
- refetchQueries: [
- {
- query: ITEM_PAGE_OWN_WANT_BUTTONS_QUERY,
- variables: { itemId },
- context: { sendAuth: true },
- },
- ],
- },
- );
-
- return (
-
- {({ css }) => (
-
- {
- if (e.target.checked) {
- sendAddMutation().catch((e) => {
- console.error(e);
- toast({
- title: "We had trouble adding this to the items you own.",
- description:
- "Check your internet connection, and try again.",
- status: "error",
- duration: 5000,
- });
- });
- } else {
- sendRemoveMutation().catch((e) => {
- console.error(e);
- toast({
- title:
- "We had trouble removing this from the items you own.",
- description:
- "Check your internet connection, and try again.",
- status: "error",
- duration: 5000,
- });
- });
- }
- }}
- />
-
-
- )}
-
- );
-}
-
-function ItemPageWantButton({ itemId, isChecked }) {
- const theme = useTheme();
- const toast = useToast();
-
- const [sendAddMutation] = useMutation(
- gql`
- mutation ItemPageWantButtonAdd($itemId: ID!) {
- addToItemsCurrentUserWants(itemId: $itemId) {
- id
- currentUserWantsThis
- }
- }
- `,
- {
- variables: { itemId },
- context: { sendAuth: true },
- optimisticResponse: {
- __typename: "Mutation",
- addToItemsCurrentUserWants: {
- __typename: "Item",
- id: itemId,
- currentUserWantsThis: true,
- },
- },
- // TODO: Refactor the mutation result to include closet lists
- refetchQueries: [
- {
- query: ITEM_PAGE_OWN_WANT_BUTTONS_QUERY,
- variables: { itemId },
- context: { sendAuth: true },
- },
- ],
- },
- );
-
- const [sendRemoveMutation] = useMutation(
- gql`
- mutation ItemPageWantButtonRemove($itemId: ID!) {
- removeFromItemsCurrentUserWants(itemId: $itemId) {
- id
- currentUserWantsThis
- }
- }
- `,
- {
- variables: { itemId },
- context: { sendAuth: true },
- optimisticResponse: {
- __typename: "Mutation",
- removeFromItemsCurrentUserWants: {
- __typename: "Item",
- id: itemId,
- currentUserWantsThis: false,
- },
- },
- // TODO: Refactor the mutation result to include closet lists
- refetchQueries: [
- {
- query: ITEM_PAGE_OWN_WANT_BUTTONS_QUERY,
- variables: { itemId },
- context: { sendAuth: true },
- },
- ],
- },
- );
-
- return (
-
- {({ css }) => (
-
- {
- if (e.target.checked) {
- sendAddMutation().catch((e) => {
- console.error(e);
- toast({
- title: "We had trouble adding this to the items you want.",
- description:
- "Check your internet connection, and try again.",
- status: "error",
- duration: 5000,
- });
- });
- } else {
- sendRemoveMutation().catch((e) => {
- console.error(e);
- toast({
- title:
- "We had trouble removing this from the items you want.",
- description:
- "Check your internet connection, and try again.",
- status: "error",
- duration: 5000,
- });
- });
- }
- }}
- />
-
-
- )}
-
- );
-}
-
-function ItemPageTradeLinks({ itemId, isEmbedded }) {
- const { data, loading, error } = useQuery(
- gql`
- query ItemPageTradeLinks($itemId: ID!) {
- item(id: $itemId) {
- id
- numUsersOfferingThis
- numUsersSeekingThis
- }
- }
- `,
- { variables: { itemId } },
- );
-
- if (error) {
- return {error.message};
- }
-
- return (
-
-
- Trading:
-
-
-
-
-
-
-
-
- );
-}
-
-function ItemPageTradeLink({ href, count, label, colorScheme, isEmbedded }) {
- return (
-
- );
-}
-
-function IconCheckbox({ icon, isChecked, ...props }) {
- return (
-
-
-
- {icon}
-
-
- );
-}
-
-export function ItemPageOutfitPreview({ itemId }) {
+function ItemPageOutfitPreview({ itemId }) {
const idealPose = React.useMemo(
() => (Math.random() > 0.5 ? "HAPPY_FEM" : "HAPPY_MASC"),
[],
@@ -1260,10 +526,7 @@ function PlayPauseButton({ isPaused, onClick }) {
);
}
-export function ItemZonesInfo({
- compatibleBodiesAndTheirZones,
- restrictedZones,
-}) {
+function ItemZonesInfo({ compatibleBodiesAndTheirZones, restrictedZones }) {
// Reorganize the body-and-zones data, into zone-and-bodies data. Also, we're
// merging zones with the same label, because that's how user-facing zone UI
// generally works!
@@ -1434,3 +697,5 @@ function buildSortKeyForZoneLabelsAndTheirBodies({ zoneLabel, bodies }) {
return `${representsAllBodies ? "A" : "Z"}-${inverseBodyCount}-${zoneLabel}`;
}
+
+export default ItemPageOutfitPreview;
diff --git a/app/javascript/wardrobe-2020/WardrobePage/Item.js b/app/javascript/wardrobe-2020/WardrobePage/Item.js
index 8086fb00..b341ad81 100644
--- a/app/javascript/wardrobe-2020/WardrobePage/Item.js
+++ b/app/javascript/wardrobe-2020/WardrobePage/Item.js
@@ -24,7 +24,6 @@ import {
import SupportOnly from "./support/SupportOnly";
import useSupport from "./support/useSupport";
-const LoadableItemPageDrawer = loadable(() => import("../ItemPageDrawer"));
const LoadableItemSupportDrawer = loadable(() =>
import("./support/ItemSupportDrawer"),
);
@@ -56,7 +55,6 @@ function Item({
onRemove,
isDisabled = false,
}) {
- const [infoDrawerIsOpen, setInfoDrawerIsOpen] = React.useState(false);
const [supportDrawerIsOpen, setSupportDrawerIsOpen] = React.useState(false);
return (
@@ -97,24 +95,10 @@ function Item({
icon={}
label="More info"
to={`/items/${item.id}`}
- onClick={(e) => {
- const willProbablyOpenInNewTab =
- e.metaKey || e.shiftKey || e.altKey || e.ctrlKey;
- if (willProbablyOpenInNewTab) {
- return;
- }
-
- setInfoDrawerIsOpen(true);
- e.preventDefault();
- }}
+ target="_blank"
/>
- setInfoDrawerIsOpen(false)}
- />
(