forked from OpenNeo/impress
Add wardrobe-2020 outfit preview to item pages
Eyyy tasty! There were some issues with conflicting styles with the main app, but I think we got it! Scoping Chakra's CSS reset was a big deal to not accidentally overwrite the app's own styles lol, and we had to solve a specificity problem for that, thanks Aria for the :where tip!! <3
This commit is contained in:
parent
2eb8a7cd60
commit
9cc45f0988
7 changed files with 311 additions and 48 deletions
|
@ -91,6 +91,8 @@ input[type=text], input[type=password], input[type=search], input[type=number],
|
||||||
textarea
|
textarea
|
||||||
font: inherit
|
font: inherit
|
||||||
|
|
||||||
|
// TODO: This conflicts with button styles in embedded wardrobe-2020
|
||||||
|
// components. It'd be nice to not apply it to ALL button elements.
|
||||||
a.button, input[type=submit], button
|
a.button, input[type=submit], button
|
||||||
+awesome-button
|
+awesome-button
|
||||||
&.loud
|
&.loud
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
&:hover
|
&:hover
|
||||||
color: #fff
|
color: #fff
|
||||||
&:active
|
&:active
|
||||||
top: 1px
|
transform: translateY(1px)
|
||||||
|
|
||||||
=reset-awesome-button
|
=reset-awesome-button
|
||||||
+border-radius(0)
|
+border-radius(0)
|
||||||
|
|
13
app/javascript/item-page.js
Normal file
13
app/javascript/item-page.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
|
import { AppProvider, ItemPageOutfitPreview } from "./wardrobe-2020";
|
||||||
|
|
||||||
|
const rootNode = document.querySelector("#outfit-preview-root");
|
||||||
|
const itemId = rootNode.getAttribute("data-item-id");
|
||||||
|
ReactDOM.render(
|
||||||
|
<AppProvider>
|
||||||
|
<ItemPageOutfitPreview itemId={itemId} />
|
||||||
|
</AppProvider>,
|
||||||
|
rootNode
|
||||||
|
);
|
|
@ -2,32 +2,14 @@ import React from "react";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { Integrations } from "@sentry/tracing";
|
import { Integrations } from "@sentry/tracing";
|
||||||
import { Auth0Provider } from "@auth0/auth0-react";
|
import { Auth0Provider } from "@auth0/auth0-react";
|
||||||
import { CSSReset, ChakraProvider, extendTheme } from "@chakra-ui/react";
|
import { ChakraProvider, Box } from "@chakra-ui/react";
|
||||||
import { ApolloProvider } from "@apollo/client";
|
import { ApolloProvider } from "@apollo/client";
|
||||||
import { useAuth0 } from "@auth0/auth0-react";
|
import { useAuth0 } from "@auth0/auth0-react";
|
||||||
import { mode } from "@chakra-ui/theme-tools";
|
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import { Global } from "@emotion/react";
|
||||||
|
|
||||||
import buildApolloClient from "./apolloClient";
|
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 }) {
|
export default function AppProvider({ children }) {
|
||||||
React.useEffect(() => setupLogging(), []);
|
React.useEffect(() => setupLogging(), []);
|
||||||
|
|
||||||
|
@ -45,9 +27,8 @@ export default function AppProvider({ children }) {
|
||||||
scope=""
|
scope=""
|
||||||
>
|
>
|
||||||
<DTIApolloProvider>
|
<DTIApolloProvider>
|
||||||
<ChakraProvider theme={theme}>
|
<ChakraProvider resetCSS={false}>
|
||||||
<CSSReset />
|
<ScopedCSSReset>{children}</ScopedCSSReset>
|
||||||
{children}
|
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
</DTIApolloProvider>
|
</DTIApolloProvider>
|
||||||
</Auth0Provider>
|
</Auth0Provider>
|
||||||
|
@ -155,3 +136,288 @@ function setupLogging() {
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1.0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ScopedCSSReset applies a copy of Chakra UI's CSS reset, but only to its
|
||||||
|
* children (or, well, any element with the chakra-css-reset class).
|
||||||
|
*
|
||||||
|
* TODO: What about Chakra's portal elements like toast messages, which are
|
||||||
|
* intentionally mounted elsewhere in the document?
|
||||||
|
*
|
||||||
|
* NOTE: We use the `:where` CSS selector, instead of the .chakra-css-reset
|
||||||
|
* selector directly, to avoid specificity conflicts. e.g. the selector
|
||||||
|
* `.chakra-css-reset h1` is considered MORE specific than `.my-h1`, whereas
|
||||||
|
* the selector `:where(.chakra-css-reset) h1` is lower specificity.
|
||||||
|
*/
|
||||||
|
function ScopedCSSReset({ children }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box className="chakra-css-reset">{children}</Box>
|
||||||
|
<Global
|
||||||
|
styles={`
|
||||||
|
:where(.chakra-css-reset) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
border-width: 0;
|
||||||
|
border-style: solid;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-top-width: 1px;
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: transparent;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: none;
|
||||||
|
text-decoration: underline;
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 100%;
|
||||||
|
line-height: 1.15;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
padding: 0.35em 0.75em 0.625em;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: inherit;
|
||||||
|
display: table;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="checkbox"],
|
||||||
|
[type="radio"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="search"] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
hr,
|
||||||
|
figure,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::-moz-focus-inner {
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
padding: 0;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
audio,
|
||||||
|
iframe,
|
||||||
|
embed,
|
||||||
|
object {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-js-focus-visible] :focus:not([data-focus-visible-added]) {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -759,7 +759,7 @@ function IconCheckbox({ icon, isChecked, ...props }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemPageOutfitPreview({ itemId }) {
|
export function ItemPageOutfitPreview({ itemId }) {
|
||||||
const idealPose = React.useMemo(
|
const idealPose = React.useMemo(
|
||||||
() => (Math.random() > 0.5 ? "HAPPY_FEM" : "HAPPY_MASC"),
|
() => (Math.random() > 0.5 ? "HAPPY_FEM" : "HAPPY_MASC"),
|
||||||
[]
|
[]
|
||||||
|
@ -1018,6 +1018,7 @@ function ItemPageOutfitPreview({ itemId }) {
|
||||||
rowGap="4"
|
rowGap="4"
|
||||||
columnGap="6"
|
columnGap="6"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
|
width="100%"
|
||||||
>
|
>
|
||||||
<AspectRatio
|
<AspectRatio
|
||||||
gridArea="preview"
|
gridArea="preview"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import AppProvider from "./AppProvider";
|
import AppProvider from "./AppProvider";
|
||||||
|
import { ItemPageOutfitPreview } from "./ItemPage";
|
||||||
import WardrobePage from "./WardrobePage";
|
import WardrobePage from "./WardrobePage";
|
||||||
|
|
||||||
export { AppProvider, WardrobePage };
|
export { AppProvider, ItemPageOutfitPreview, WardrobePage };
|
||||||
|
|
|
@ -69,16 +69,7 @@
|
||||||
%span.more= t '.trading_closet_hangers.show_more'
|
%span.more= t '.trading_closet_hangers.show_more'
|
||||||
%span.less= t '.trading_closet_hangers.show_less'
|
%span.less= t '.trading_closet_hangers.show_less'
|
||||||
|
|
||||||
#item-preview-header
|
#outfit-preview-root{'data-item-id': @item.id}
|
||||||
%h3= t '.preview.header'
|
|
||||||
= link_to t('.preview.customize_more'), root_path, :id => 'customize-more',
|
|
||||||
:class => 'button'
|
|
||||||
|
|
||||||
#item-preview
|
|
||||||
%ul#item-preview-species{'data-supported-species-ids' => @supported_species_ids.join(',')}
|
|
||||||
= standard_species_images_for(@basic_colored_pet_types_by_species_id)
|
|
||||||
#item-preview-error
|
|
||||||
#item-preview-swf= t '.preview.requirements_not_met'
|
|
||||||
|
|
||||||
- unless @contributors_with_counts.empty?
|
- unless @contributors_with_counts.empty?
|
||||||
#item-contributors
|
#item-contributors
|
||||||
|
@ -88,17 +79,6 @@
|
||||||
%li= link_to(contributor.name, user_contributions_path(contributor)) + format_contribution_count(count)
|
%li= link_to(contributor.name, user_contributions_path(contributor)) + format_contribution_count(count)
|
||||||
%footer= t '.contributors.footer'
|
%footer= t '.contributors.footer'
|
||||||
|
|
||||||
:javascript
|
|
||||||
var CURRENT_ITEM_ZONES_RESTRICT = #{@item.zones_restrict.inspect},
|
|
||||||
IMPRESS_HOST = #{RemoteImpressHost.inspect};
|
|
||||||
|
|
||||||
%script#swf-assets-not-found-template{:type => 'text/x-jquery-tmpl'}
|
|
||||||
= twl '.preview.not_found', :item_name => h(@item.name),
|
|
||||||
:species_name => '${species_name}',
|
|
||||||
:color_name => '${color_name}',
|
|
||||||
:modeling_link_url => root_path
|
|
||||||
|
|
||||||
- content_for :javascripts do
|
- content_for :javascripts do
|
||||||
= include_javascript_libraries :jquery, :swfobject, :jquery_tmpl
|
= javascript_include_tag 'item-page', defer: true
|
||||||
= javascript_include_tag 'items/show'
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue