Compare commits
No commits in common. "c7cf1d21116ea110a4619f56973f224c8e773463" and "b06149cf220fe06042ca2acd0df720dacf1eb87c" have entirely different histories.
c7cf1d2111
...
b06149cf22
11 changed files with 55 additions and 117 deletions
|
@ -115,7 +115,7 @@ class OutfitsController < ApplicationController
|
|||
|
||||
def outfit_params
|
||||
params.require(:outfit).permit(
|
||||
:name, :starred, :alt_style_id, item_ids: {worn: [], closeted: []},
|
||||
:name, :starred, item_ids: {worn: [], closeted: []},
|
||||
biology: [:species_id, :color_id, :pose])
|
||||
end
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
useToast,
|
||||
useToken,
|
||||
} from "@chakra-ui/react";
|
||||
import { ChevronDownIcon, WarningTwoIcon } from "@chakra-ui/icons";
|
||||
import { ChevronDownIcon } from "@chakra-ui/icons";
|
||||
import { loadable } from "../util";
|
||||
|
||||
import { petAppearanceFragment } from "../components/useOutfitAppearance";
|
||||
|
@ -42,7 +42,6 @@ import twemojiSunglasses from "../images/twemoji/sunglasses.svg";
|
|||
import twemojiQuestion from "../images/twemoji/question.svg";
|
||||
import twemojiMasc from "../images/twemoji/masc.svg";
|
||||
import twemojiFem from "../images/twemoji/fem.svg";
|
||||
import twemojiHourglass from "../images/twemoji/hourglass.svg";
|
||||
|
||||
const PosePickerSupport = loadable(() => import("./support/PosePickerSupport"));
|
||||
|
||||
|
@ -219,11 +218,18 @@ function PosePicker({
|
|||
gap="2"
|
||||
index={tabIndex}
|
||||
onChange={setTabIndex}
|
||||
// HACK: To only apply `initialFocusRef` to the selected input
|
||||
// in the *active* tab, we just use `isLazy` to only *render*
|
||||
// the active tab. We could also watch the tab state and set
|
||||
// the ref accordingly!
|
||||
isLazy
|
||||
>
|
||||
<TabList paddingX="2" paddingY="0">
|
||||
<Tab width="50%">Expressions</Tab>
|
||||
<Tab width="50%">Styles</Tab>
|
||||
</TabList>
|
||||
<SupportOnly>
|
||||
<TabList paddingX="2" paddingY="0">
|
||||
<Tab width="50%">Expressions</Tab>
|
||||
<Tab width="50%">Styles</Tab>
|
||||
</TabList>
|
||||
</SupportOnly>
|
||||
<TabPanels position="relative">
|
||||
<TabPanel paddingX="4" paddingY="0">
|
||||
{isInSupportMode ? (
|
||||
|
@ -232,9 +238,7 @@ function PosePicker({
|
|||
colorId={colorId}
|
||||
pose={pose}
|
||||
appearanceId={appearanceId}
|
||||
initialFocusRef={
|
||||
tabIndex === 0 ? initialFocusRef : null
|
||||
}
|
||||
initialFocusRef={initialFocusRef}
|
||||
dispatchToOutfit={dispatchToOutfit}
|
||||
/>
|
||||
) : (
|
||||
|
@ -242,9 +246,7 @@ function PosePicker({
|
|||
<PosePickerTable
|
||||
poseInfos={poseInfos}
|
||||
onChange={onChangePose}
|
||||
initialFocusRef={
|
||||
tabIndex === 0 ? initialFocusRef : null
|
||||
}
|
||||
initialFocusRef={initialFocusRef}
|
||||
/>
|
||||
{numStandardPoses == 0 && (
|
||||
<PosePickerEmptyExplanation />
|
||||
|
@ -265,7 +267,7 @@ function PosePicker({
|
|||
selectedStyleId={altStyleId}
|
||||
altStyles={altStyles}
|
||||
onChange={onChangeStyle}
|
||||
initialFocusRef={tabIndex === 1 ? initialFocusRef : null}
|
||||
initialFocusRef={initialFocusRef}
|
||||
/>
|
||||
<StyleExplanation />
|
||||
</TabPanel>
|
||||
|
@ -422,22 +424,14 @@ function PosePickerTable({ poseInfos, onChange, initialFocusRef }) {
|
|||
</tbody>
|
||||
</table>
|
||||
{poseInfos.unconverted.isAvailable && (
|
||||
<Flex
|
||||
align="center"
|
||||
justify="center"
|
||||
gap="1"
|
||||
<PoseOption
|
||||
poseInfo={poseInfos.unconverted}
|
||||
onChange={onChange}
|
||||
inputRef={poseInfos.unconverted.isSelected && initialFocusRef}
|
||||
size="sm"
|
||||
label="Unconverted"
|
||||
marginTop="2"
|
||||
marginBottom="2"
|
||||
>
|
||||
<PoseOption
|
||||
poseInfo={poseInfos.unconverted}
|
||||
onChange={onChange}
|
||||
inputRef={poseInfos.unconverted.isSelected && initialFocusRef}
|
||||
size="sm"
|
||||
label="Retired UC"
|
||||
/>
|
||||
<RetiredUCWarning isSelected={poseInfos.unconverted.isSelected} />
|
||||
</Flex>
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
@ -630,40 +624,6 @@ function PosePickerEmptyExplanation() {
|
|||
);
|
||||
}
|
||||
|
||||
function RetiredUCWarning({ isSelected }) {
|
||||
return (
|
||||
<Popover placement="right" trigger="hover">
|
||||
<PopoverTrigger>
|
||||
<Box
|
||||
as="button"
|
||||
tabIndex="0"
|
||||
aria-label="Warning"
|
||||
cursor="help"
|
||||
lineHeight="1"
|
||||
opacity={isSelected ? "1" : "0.75"}
|
||||
transform={isSelected ? "scale(1)" : "scale(0.8)"}
|
||||
color={isSelected ? "yellow.500" : "inherit"}
|
||||
transition="all 0.2s"
|
||||
padding="1"
|
||||
>
|
||||
<WarningTwoIcon />
|
||||
</Box>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
background="blackAlpha.800"
|
||||
borderColor="blackAlpha.900"
|
||||
color="white"
|
||||
padding="2"
|
||||
fontSize="sm"
|
||||
>
|
||||
"Unconverted" pets are no longer available on Neopets.com, and have been
|
||||
replaced with the very similar Styles feature. We're just keeping this
|
||||
as an archive!
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function StyleSelect({
|
||||
selectedStyleId,
|
||||
altStyles,
|
||||
|
@ -788,6 +748,10 @@ function StyleExplanation() {
|
|||
Styling Chamber
|
||||
</Box>
|
||||
. Not all items fit Alt Style pets. The pet's color doesn't have to match.
|
||||
<SupportOnly>
|
||||
<br />
|
||||
WIP: Only Support staff see this tab for now! 💖
|
||||
</SupportOnly>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
@ -925,7 +889,7 @@ function getIcon(pose) {
|
|||
} else if (["SICK_MASC", "SICK_FEM"].includes(pose)) {
|
||||
return twemojiSick;
|
||||
} else if (pose === "UNCONVERTED") {
|
||||
return twemojiHourglass;
|
||||
return twemojiSunglasses;
|
||||
} else {
|
||||
return twemojiSmile;
|
||||
}
|
||||
|
@ -939,7 +903,7 @@ function getLabel(pose) {
|
|||
} else if (pose === "SICK_MASC" || pose === "SICK_FEM") {
|
||||
return "Sick";
|
||||
} else if (pose === "UNCONVERTED") {
|
||||
return "Retired UC";
|
||||
return "Classic UC";
|
||||
} else {
|
||||
return "Default";
|
||||
}
|
||||
|
|
|
@ -69,7 +69,6 @@ function useOutfitSaving(outfitState, dispatchToOutfit) {
|
|||
speciesId: outfitState.speciesId,
|
||||
colorId: outfitState.colorId,
|
||||
pose: outfitState.pose,
|
||||
altStyleId: outfitState.altStyleId,
|
||||
wornItemIds: [...outfitState.wornItemIds],
|
||||
closetedItemIds: [...outfitState.closetedItemIds],
|
||||
})
|
||||
|
|
|
@ -447,7 +447,6 @@ function getOutfitStateFromOutfitData(outfit) {
|
|||
speciesId: outfit.speciesId,
|
||||
colorId: outfit.colorId,
|
||||
pose: outfit.pose,
|
||||
altStyleId: outfit.altStyleId,
|
||||
wornItemIds: new Set(outfit.wornItemIds),
|
||||
closetedItemIds: new Set(outfit.closetedItemIds),
|
||||
};
|
||||
|
@ -704,6 +703,12 @@ function buildOutfitQueryString(outfitState) {
|
|||
color: colorId || "",
|
||||
pose: pose || "",
|
||||
});
|
||||
for (const itemId of wornItemIds) {
|
||||
params.append("objects[]", itemId);
|
||||
}
|
||||
for (const itemId of closetedItemIds) {
|
||||
params.append("closet[]", itemId);
|
||||
}
|
||||
if (altStyleId != null) {
|
||||
params.append("style", altStyleId);
|
||||
}
|
||||
|
@ -712,12 +717,6 @@ function buildOutfitQueryString(outfitState) {
|
|||
// refers to "PetState", the database table name for pet appearances.
|
||||
params.append("state", appearanceId);
|
||||
}
|
||||
for (const itemId of wornItemIds) {
|
||||
params.append("objects[]", itemId);
|
||||
}
|
||||
for (const itemId of closetedItemIds) {
|
||||
params.append("closet[]", itemId);
|
||||
}
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFE8B6" d="M21 18c0-2.001 3.246-3.369 5-6 2-3 2-10 2-10H8s0 7 2 10c1.754 2.631 5 3.999 5 6s-3.246 3.369-5 6c-2 3-2 10-2 10h20s0-7-2-10c-1.754-2.631-5-3.999-5-6z"/><path fill="#FFAC33" d="M20.999 24c-.999 0-2.057-1-2.057-2C19 20.287 19 19.154 19 18c0-3.22 3.034-4.561 4.9-7H12.1c1.865 2.439 4.9 3.78 4.9 7 0 1.155 0 2.289.058 4 0 1-1.058 2-2.058 2-2 0-3.595 1.784-4 3-1 3-1 7-1 7h16s0-4-1-7c-.405-1.216-2.001-3-4.001-3z"/><path fill="#3B88C3" d="M30 34c0 1.104-.896 2-2 2H8c-1.104 0-2-.896-2-2s.896-2 2-2h20c1.104 0 2 .896 2 2zm0-32c0 1.104-.896 2-2 2H8c-1.104 0-2-.896-2-2s.896-2 2-2h20c1.104 0 2 .896 2 2z"/></svg>
|
Before Width: | Height: | Size: 688 B |
|
@ -30,9 +30,7 @@ export function useDeleteOutfitMutation(options = {}) {
|
|||
...options,
|
||||
mutationFn: deleteOutfit,
|
||||
onSuccess: (emptyData, id, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["outfits", String(id)],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ["outfits", String(id)] });
|
||||
if (options.onSuccess) {
|
||||
options.onSuccess(emptyData, id, context);
|
||||
}
|
||||
|
@ -44,9 +42,7 @@ async function loadSavedOutfit(id) {
|
|||
const res = await fetch(`/outfits/${encodeURIComponent(id)}.json`);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`loading outfit failed: ${res.status} ${res.statusText}`,
|
||||
);
|
||||
throw new Error(`loading outfit failed: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
return res.json().then(normalizeOutfit);
|
||||
|
@ -58,7 +54,6 @@ async function saveOutfit({
|
|||
speciesId,
|
||||
colorId,
|
||||
pose,
|
||||
altStyleId,
|
||||
wornItemIds,
|
||||
closetedItemIds,
|
||||
}) {
|
||||
|
@ -70,7 +65,6 @@ async function saveOutfit({
|
|||
color_id: colorId,
|
||||
pose: pose,
|
||||
},
|
||||
alt_style_id: altStyleId,
|
||||
item_ids: { worn: wornItemIds, closeted: closetedItemIds },
|
||||
},
|
||||
};
|
||||
|
@ -97,9 +91,7 @@ async function saveOutfit({
|
|||
}
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`saving outfit failed: ${res.status} ${res.statusText}`,
|
||||
);
|
||||
throw new Error(`saving outfit failed: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
|
||||
return res.json().then(normalizeOutfit);
|
||||
|
@ -114,9 +106,7 @@ async function deleteOutfit(id) {
|
|||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`deleting outfit failed: ${res.status} ${res.statusText}`,
|
||||
);
|
||||
throw new Error(`deleting outfit failed: ${res.status} ${res.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,11 +117,8 @@ function normalizeOutfit(outfit) {
|
|||
speciesId: String(outfit.species_id),
|
||||
colorId: String(outfit.color_id),
|
||||
pose: outfit.pose,
|
||||
altStyleId: outfit.alt_style_id ? String(outfit.alt_style_id) : null,
|
||||
wornItemIds: (outfit.item_ids?.worn || []).map((id) => String(id)),
|
||||
closetedItemIds: (outfit.item_ids?.closeted || []).map((id) =>
|
||||
String(id),
|
||||
),
|
||||
closetedItemIds: (outfit.item_ids?.closeted || []).map((id) => String(id)),
|
||||
creator: outfit.user ? { id: String(outfit.user.id) } : null,
|
||||
createdAt: outfit.created_at,
|
||||
updatedAt: outfit.updated_at,
|
||||
|
|
|
@ -4,7 +4,6 @@ class Outfit < ApplicationRecord
|
|||
class_name: 'ItemOutfitRelationship'
|
||||
has_many :worn_items, through: :worn_item_outfit_relationships, source: :item
|
||||
|
||||
belongs_to :alt_style, optional: true
|
||||
belongs_to :pet_state, optional: true # We validate presence below!
|
||||
belongs_to :user, optional: true
|
||||
|
||||
|
@ -83,8 +82,7 @@ class Outfit < ApplicationRecord
|
|||
|
||||
def as_json(more_options={})
|
||||
serializable_hash(
|
||||
only: [:id, :name, :pet_state_id, :starred, :created_at, :updated_at,
|
||||
:alt_style_id],
|
||||
only: [:id, :name, :pet_state_id, :starred, :created_at, :updated_at],
|
||||
methods: [:color_id, :species_id, :pose, :item_ids, :user]
|
||||
)
|
||||
end
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
- title "Styling Studio"
|
||||
|
||||
%p
|
||||
Here's all the new NC Pet Styles we have! They're available in the app too,
|
||||
by opening the emotion picker and clicking the "Styles" tab.
|
||||
We're getting set up with the new NC Pet Styles! They're not ready in the app
|
||||
yet, but here's what we have so far!
|
||||
|
||||
%p
|
||||
If you have an Alt Style we don't, please model it by entering your pet's
|
||||
name on the homepage! Thank you! 💖
|
||||
If you have one we don't, please model it by entering your pet's name on the
|
||||
homepage! Thank you! 💖
|
||||
|
||||
%p
|
||||
Also, heads-up: Style tokens are pretty different from normal wearables, so
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
= advertise_campaign_progress @campaign
|
||||
|
||||
.notice
|
||||
%strong Alt styles are ready now!
|
||||
You can find them by opening the emotion picker, then clicking "Styles".
|
||||
%strong Happy NC UC day!
|
||||
We're working on Styling Studio support,
|
||||
= link_to("here's what we have so far", alt_styles_path) + "!"
|
||||
%br
|
||||
= link_to("Here's our reference page, too!", alt_styles_path)
|
||||
Thanks again for all your help, let us know how it works for you! 💖
|
||||
Thank you for helping us model the new styles, we appreciate it lots!!! 💖
|
||||
|
||||
%p#pet-not-found.alert= t 'pets.load.not_found'
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
class AddAltStyleIdToOutfits < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_reference :outfits, :alt_style, null: true, foreign_key: true
|
||||
end
|
||||
end
|
15
db/schema.rb
15
db/schema.rb
|
@ -10,13 +10,13 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_02_01_134440) do
|
||||
create_table "alt_styles", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_01_29_114639) do
|
||||
create_table "alt_styles", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
|
||||
t.integer "species_id", null: false
|
||||
t.integer "color_id", null: false
|
||||
t.integer "body_id", null: false
|
||||
t.datetime "created_at", precision: nil, null: false
|
||||
t.datetime "updated_at", precision: nil, null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["color_id"], name: "index_alt_styles_on_color_id"
|
||||
t.index ["species_id"], name: "index_alt_styles_on_species_id"
|
||||
end
|
||||
|
@ -125,11 +125,11 @@ ActiveRecord::Schema[7.1].define(version: 2024_02_01_134440) do
|
|||
t.index ["outfit_id", "is_worn"], name: "index_item_outfit_relationships_on_outfit_id_and_is_worn"
|
||||
end
|
||||
|
||||
create_table "item_translations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
create_table "item_translations", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
|
||||
t.integer "item_id"
|
||||
t.string "locale"
|
||||
t.string "name"
|
||||
t.text "description"
|
||||
t.text "description", size: :medium
|
||||
t.string "rarity"
|
||||
t.datetime "created_at", precision: nil
|
||||
t.datetime "updated_at", precision: nil
|
||||
|
@ -191,8 +191,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_02_01_134440) do
|
|||
t.string "image"
|
||||
t.string "image_layers_hash"
|
||||
t.boolean "image_enqueued", default: false, null: false
|
||||
t.bigint "alt_style_id"
|
||||
t.index ["alt_style_id"], name: "index_outfits_on_alt_style_id"
|
||||
t.index ["pet_state_id"], name: "index_outfits_on_pet_state_id"
|
||||
t.index ["user_id"], name: "index_outfits_on_user_id"
|
||||
end
|
||||
|
@ -316,5 +314,4 @@ ActiveRecord::Schema[7.1].define(version: 2024_02_01_134440) do
|
|||
|
||||
add_foreign_key "alt_styles", "colors"
|
||||
add_foreign_key "alt_styles", "species"
|
||||
add_foreign_key "outfits", "alt_styles"
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue