From 968ae5c41ef47eb3292135e7a0c5490375af6b5b Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sun, 9 Apr 2023 19:40:15 -0700 Subject: [PATCH] Deploy advanced filters (#297) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added items from 2023/03 Legfest and 2023/03/30 update (#290) * Added avatars (#286) * Deploy #287 (#288) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added new weapon series * Added updates * Missed items (#291) * Added avatars (#286) * Deploy #287 (#288) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added new weapon series * Added updates * Add more items * Added World Series to weapon series empty state (#293) * Push 2023/03 updates to main (#292) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added items from 2023/03 Legfest and 2023/03/30 update (#290) * Added avatars (#286) * Deploy #287 (#288) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added new weapon series * Added updates * Missed items (#291) * Added avatars (#286) * Deploy #287 (#288) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added new weapon series * Added updates * Add more items * Added items from 2023/03 Legfest and 2023/03/30 update (#290) * Added avatars (#286) * Deploy #287 (#288) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added new weapon series * Added updates * Missed items (#291) * Added avatars (#286) * Deploy #287 (#288) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added new weapon series * Added updates * Add more items * Add World series to empty state * Added items from 2023/03 Legfest and 2023/03/30 update (#290) * Added avatars (#286) * Deploy #287 (#288) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added new weapon series * Added updates * Missed items (#291) * Added avatars (#286) * Deploy #287 (#288) * Added avatars * Added content from the 2023/03/22 update (#287) * Added avatars (#286) * Added localizations * Added update, changed CSS * Add logic for showing Lucifer uncap and 250 art * Added new weapon series * Added updates * Add more items * Enables advanced filters in collections (#289) * Add skeleton of FilterModal * Install react-slider from Radix * Move AccountModal styles to more generic place * Make generic TableField and move styles This is so we have a base for other table rows that use different interactive elements * Implement custom Slider component This inherits from Radix's Slider * Implement SliderTableField * Implemented SwitchTableField * Change enabled switch color * Implement InputTableField * Update modal skeleton * Added localizations for Advanced filters * Update styles for various components Added some new colors and fixed spacing * Added value reporting and fixed a cycle error * Added default values, clearing filters, etc * Default values * Ability to clear filters * Receiving values from components * Fix maximum cycle depth exceeded error * Update TableFields to not error Also optional value is required * Create FilterSet.d.ts * Send filtersets to FilterModal This sends the default filterset and the user's filterset to the filter modal. The default filterset is used when resetting all filters. The users filterset is used so that it is populated with the user's values when they open the modal * Add new localizations * Change types and add default filterset object * Add fast-deep-equal package * Change value in table fields * Input table fields need to be able to be empty * Slider table fields should default to 0 if value isn't provided * Set width of Select in table field in Filter dialog * Add style for filter button with filters active * Swap to using selects for some boolean fields Charge Attack, Full Auto, and Auto Guard are not boolean values since the user can select (and the default should be) to show both on and off values. We swap to using a SelectTableField here to represent this difference. We also added logic for Full Auto and Auto Guard fields since they are tied together in some cases (you can't show Auto Guard teams that have Full Auto disabled) * Populate values from defaultFilterSet * Update how we save and propagate filters We save filterset in a local state, because the FilterBar will send it down to us from cookies. We then set each individual property from that filter set. We set inputs to have a placeholder, as max buttons and max turns could not be set (null). Then, we only send those fields when they have a value provided by the user. * Remove default filterset This was moved to a utils/ file * Propagate filters from modal This updates how we handle filter propagation to accommodate the advanced ones. The icon lights up when filters are active. * Implement advanced filters on Teams page * Add skeleton of FilterModal * Make generic TableField and move styles This is so we have a base for other table rows that use different interactive elements * Implement custom Slider component This inherits from Radix's Slider * Implement SliderTableField * Implemented SwitchTableField * Implement InputTableField * Update modal skeleton * Added localizations for Advanced filters * Update styles for various components Added some new colors and fixed spacing * Added value reporting and fixed a cycle error * Added default values, clearing filters, etc * Default values * Ability to clear filters * Receiving values from components * Fix maximum cycle depth exceeded error * Update TableFields to not error Also optional value is required * Create FilterSet.d.ts * Send filtersets to FilterModal This sends the default filterset and the user's filterset to the filter modal. The default filterset is used when resetting all filters. The users filterset is used so that it is populated with the user's values when they open the modal * Add new localizations * Change types and add default filterset object * Change value in table fields * Input table fields need to be able to be empty * Slider table fields should default to 0 if value isn't provided * Set width of Select in table field in Filter dialog * Add style for filter button with filters active * Swap to using selects for some boolean fields Charge Attack, Full Auto, and Auto Guard are not boolean values since the user can select (and the default should be) to show both on and off values. We swap to using a SelectTableField here to represent this difference. We also added logic for Full Auto and Auto Guard fields since they are tied together in some cases (you can't show Auto Guard teams that have Full Auto disabled) * Populate values from defaultFilterSet * Update how we save and propagate filters We save filterset in a local state, because the FilterBar will send it down to us from cookies. We then set each individual property from that filter set. We set inputs to have a placeholder, as max buttons and max turns could not be set (null). Then, we only send those fields when they have a value provided by the user. * Remove default filterset This was moved to a utils/ file * Propagate filters from modal This updates how we handle filter propagation to accommodate the advanced ones. The icon lights up when filters are active. * GridRep adjustments * Properly unset mainhand when cells get reused and the new team doesnt have one * Slightly better styling to make the grid more correct * Fix bad merge * Add advanced filter support to saved and profile pages * Fix auto guard text * Ensure fetchTeams callback is updated with filters * Add auto guard icon to GridRep * Disable max buttons and turns * Fix build errors --- components/AccountModal/index.scss | 11 - components/DialogContent/index.scss | 16 + components/FilterBar/index.scss | 18 + components/FilterBar/index.tsx | 215 +++++++----- components/FilterModal/index.scss | 15 + components/FilterModal/index.tsx | 435 +++++++++++++++++++++++++ components/GridRep/index.scss | 23 +- components/GridRep/index.tsx | 42 ++- components/InputTableField/index.scss | 4 + components/InputTableField/index.tsx | 56 ++++ components/PartyDetails/index.tsx | 3 +- components/SelectTableField/index.scss | 116 ------- components/SelectTableField/index.tsx | 60 ++-- components/Slider/index.scss | 48 +++ components/Slider/index.tsx | 32 ++ components/SliderTableField/index.scss | 8 + components/SliderTableField/index.tsx | 78 +++++ components/Switch/index.scss | 8 +- components/SwitchTableField/index.scss | 0 components/SwitchTableField/index.tsx | 52 +++ components/TableField/index.scss | 124 +++++++ components/TableField/index.tsx | 48 +++ package-lock.json | 102 +++++- package.json | 2 + pages/[username].tsx | 56 +++- pages/saved.tsx | 56 +++- pages/teams.tsx | 54 ++- public/icons/Filter.svg | 3 + public/icons/Shield.svg | 2 +- public/locales/en/common.json | 25 ++ public/locales/ja/common.json | 25 ++ styles/themes.scss | 16 + styles/variables.scss | 13 + types/FilterSet.d.ts | 16 + utils/defaultFilters.tsx | 21 ++ 35 files changed, 1475 insertions(+), 328 deletions(-) create mode 100644 components/FilterModal/index.scss create mode 100644 components/FilterModal/index.tsx create mode 100644 components/InputTableField/index.scss create mode 100644 components/InputTableField/index.tsx create mode 100644 components/Slider/index.scss create mode 100644 components/Slider/index.tsx create mode 100644 components/SliderTableField/index.scss create mode 100644 components/SliderTableField/index.tsx create mode 100644 components/SwitchTableField/index.scss create mode 100644 components/SwitchTableField/index.tsx create mode 100644 components/TableField/index.scss create mode 100644 components/TableField/index.tsx create mode 100644 public/icons/Filter.svg create mode 100644 types/FilterSet.d.ts create mode 100644 utils/defaultFilters.tsx diff --git a/components/AccountModal/index.scss b/components/AccountModal/index.scss index 47a61c56..e2bbd2e0 100644 --- a/components/AccountModal/index.scss +++ b/components/AccountModal/index.scss @@ -5,17 +5,6 @@ width: $unit * 64; overflow-y: hidden; - .Fields { - display: flex; - flex-direction: column; - gap: $unit-2x; - padding: 0 $unit-4x; - - @include breakpoint(phone) { - gap: $unit-4x; - } - } - .DialogDescription { font-size: $font-regular; line-height: 1.24; diff --git a/components/DialogContent/index.scss b/components/DialogContent/index.scss index 26421dbf..8603f748 100644 --- a/components/DialogContent/index.scss +++ b/components/DialogContent/index.scss @@ -171,6 +171,11 @@ width: 100%; } } + + &.Spaced { + justify-content: space-between; + width: 100%; + } } } @@ -180,6 +185,17 @@ width: 100%; } + .Fields { + display: flex; + flex-direction: column; + gap: $unit-2x; + padding: 0 $unit-4x; + + @include breakpoint(phone) { + gap: $unit-4x; + } + } + &.Conflict { $weapon-diameter: 14rem; diff --git a/components/FilterBar/index.scss b/components/FilterBar/index.scss index e7e074c0..b8bde51e 100644 --- a/components/FilterBar/index.scss +++ b/components/FilterBar/index.scss @@ -38,6 +38,24 @@ flex-direction: column; width: 100%; } + + .Button.Filter.Blended { + &.FiltersActive .Accessory svg { + fill: var(--accent-blue); + stroke: none; + } + + &:hover { + background: var(--button-bg); + } + + .Accessory svg { + fill: none; + stroke: var(--button-text); + width: 18px; + height: 18px; + } + } } &.shadow { diff --git a/components/FilterBar/index.tsx b/components/FilterBar/index.tsx index 426dd856..116d8bf2 100644 --- a/components/FilterBar/index.tsx +++ b/components/FilterBar/index.tsx @@ -1,12 +1,20 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { useTranslation } from 'next-i18next' import classNames from 'classnames' +import equals from 'fast-deep-equal' +import FilterModal from '~components/FilterModal' import RaidDropdown from '~components/RaidDropdown' - -import './index.scss' import Select from '~components/Select' import SelectItem from '~components/SelectItem' +import Button from '~components/Button' + +import { defaultFilterset } from '~utils/defaultFilters' + +import FilterIcon from '~public/icons/Filter.svg' + +import './index.scss' +import { getCookie } from 'cookies-next' interface Props { children: React.ReactNode @@ -14,15 +22,7 @@ interface Props { element?: number raidSlug?: string recency?: number - onFilter: ({ - element, - raidSlug, - recency, - }: { - element?: number - raidSlug?: string - recency?: number - }) => void + onFilter: (filters: FilterSet) => void } const FilterBar = (props: Props) => { @@ -32,12 +32,32 @@ const FilterBar = (props: Props) => { const [recencyOpen, setRecencyOpen] = useState(false) const [elementOpen, setElementOpen] = useState(false) + const [filterModalOpen, setFilterModalOpen] = useState(false) + const [advancedFilters, setAdvancedFilters] = useState({}) + + const [matchesDefaultFilters, setMatchesDefaultFilters] = useState(false) // Set up classes object for showing shadow on scroll const classes = classNames({ FilterBar: true, shadow: props.scrolled, }) + const filterButtonClasses = classNames({ + Filter: true, + FiltersActive: !matchesDefaultFilters, + }) + + useEffect(() => { + // Fetch user's advanced filters + const filtersCookie = getCookie('filters') + if (filtersCookie) setAdvancedFilters(JSON.parse(filtersCookie as string)) + else setAdvancedFilters(defaultFilterset) + }, []) + + useEffect(() => { + setMatchesDefaultFilters(equals(advancedFilters, defaultFilterset)) + }, [advancedFilters, defaultFilterset]) + function openElementSelect() { setElementOpen(!elementOpen) } @@ -48,16 +68,21 @@ const FilterBar = (props: Props) => { function elementSelectChanged(value: string) { const elementValue = parseInt(value) - props.onFilter({ element: elementValue }) + props.onFilter({ element: elementValue, ...advancedFilters }) } function recencySelectChanged(value: string) { const recencyValue = parseInt(value) - props.onFilter({ recency: recencyValue }) + props.onFilter({ recency: recencyValue, ...advancedFilters }) } function raidSelectChanged(slug?: string) { - props.onFilter({ raidSlug: slug }) + props.onFilter({ raidSlug: slug, ...advancedFilters }) + } + + function handleAdvancedFiltersChanged(filters: FilterSet) { + setAdvancedFilters(filters) + props.onFilter(filters) } function onSelectChange(name: 'element' | 'recency') { @@ -66,81 +91,97 @@ const FilterBar = (props: Props) => { } return ( -
- {props.children} -
- + <> +
+ {props.children} +
+ - + - + + +
-
+ + ) } diff --git a/components/FilterModal/index.scss b/components/FilterModal/index.scss new file mode 100644 index 00000000..6be72ddf --- /dev/null +++ b/components/FilterModal/index.scss @@ -0,0 +1,15 @@ +.Dialog { + .Filter.DialogContent { + overflow: hidden; + + .TableField .Right .SelectTrigger.Table { + width: $unit-20x; + min-width: auto; + } + } + + .DialogFooter .Buttons .Button.Blended { + padding-left: 0; + padding-right: 0; + } +} diff --git a/components/FilterModal/index.tsx b/components/FilterModal/index.tsx new file mode 100644 index 00000000..c6a09bb7 --- /dev/null +++ b/components/FilterModal/index.tsx @@ -0,0 +1,435 @@ +import React, { useEffect, useState } from 'react' +import { getCookie, setCookie } from 'cookies-next' +import { useRouter } from 'next/router' +import { useTranslation } from 'react-i18next' + +import { + Dialog, + DialogTrigger, + DialogClose, + DialogTitle, +} from '~components/Dialog' +import DialogContent from '~components/DialogContent' + +import Button from '~components/Button' +import InputTableField from '~components/InputTableField' +import SelectTableField from '~components/SelectTableField' +import SliderTableField from '~components/SliderTableField' +import SwitchTableField from '~components/SwitchTableField' +import SelectItem from '~components/SelectItem' + +import type { DialogProps } from '@radix-ui/react-dialog' + +import CrossIcon from '~public/icons/Cross.svg' +import './index.scss' + +interface Props extends DialogProps { + defaultFilterSet: FilterSet + filterSet: FilterSet + sendAdvancedFilters: (filters: FilterSet) => void +} + +const MAX_CHARACTERS = 5 +const MAX_WEAPONS = 13 +const MAX_SUMMONS = 8 + +const FilterModal = (props: Props) => { + // Set up router + const router = useRouter() + const locale = router.locale + + // Set up translation + const { t } = useTranslation('common') + + // Refs + const headerRef = React.createRef() + const footerRef = React.createRef() + + // States + const [open, setOpen] = useState(false) + const [chargeAttackOpen, setChargeAttackOpen] = useState(false) + const [fullAutoOpen, setFullAutoOpen] = useState(false) + const [autoGuardOpen, setAutoGuardOpen] = useState(false) + const [filterSet, setFilterSet] = useState({}) + + // Filter states + const [fullAuto, setFullAuto] = useState(props.defaultFilterSet.full_auto) + const [autoGuard, setAutoGuard] = useState(props.defaultFilterSet.auto_guard) + const [chargeAttack, setChargeAttack] = useState( + props.defaultFilterSet.charge_attack + ) + const [minCharacterCount, setMinCharacterCount] = useState( + props.defaultFilterSet.characters_count + ) + const [minWeaponCount, setMinWeaponCount] = useState( + props.defaultFilterSet.weapons_count + ) + const [minSummonCount, setMinSummonCount] = useState( + props.defaultFilterSet.summons_count + ) + const [maxButtonsCount, setMaxButtonsCount] = useState( + props.defaultFilterSet.button_count + ) + const [maxTurnsCount, setMaxTurnsCount] = useState( + props.defaultFilterSet.turn_count + ) + const [userQuality, setUserQuality] = useState( + props.defaultFilterSet.user_quality + ) + const [nameQuality, setNameQuality] = useState( + props.defaultFilterSet.name_quality + ) + const [originalOnly, setOriginalOnly] = useState( + props.defaultFilterSet.original + ) + + // Hooks + useEffect(() => { + if (props.open !== undefined) setOpen(props.open) + }) + + useEffect(() => { + setFilterSet(props.filterSet) + }, [props.filterSet]) + + useEffect(() => { + setFullAuto(filterSet.full_auto) + setAutoGuard(filterSet.auto_guard) + setChargeAttack(filterSet.charge_attack) + + setMinCharacterCount(filterSet.characters_count) + setMinSummonCount(filterSet.summons_count) + setMinWeaponCount(filterSet.weapons_count) + + setMaxButtonsCount(filterSet.button_count) + setMaxTurnsCount(filterSet.turn_count) + + setNameQuality(filterSet.name_quality) + setUserQuality(filterSet.user_quality) + setOriginalOnly(filterSet.original) + }, [filterSet]) + + function openSelect(name: 'charge_attack' | 'full_auto' | 'auto_guard') { + setChargeAttackOpen(name === 'charge_attack' ? !chargeAttackOpen : false) + setFullAutoOpen(name === 'full_auto' ? !fullAutoOpen : false) + setAutoGuardOpen(name === 'auto_guard' ? !autoGuardOpen : false) + } + + function saveFilters() { + const filters: FilterSet = {} + filters.full_auto = fullAuto + filters.auto_guard = autoGuard + filters.charge_attack = chargeAttack + filters.characters_count = minCharacterCount + filters.weapons_count = minWeaponCount + filters.summons_count = minSummonCount + filters.name_quality = nameQuality + filters.user_quality = userQuality + filters.original = originalOnly + + if (maxButtonsCount) filters.button_count = maxButtonsCount + if (maxTurnsCount) filters.turn_count = maxTurnsCount + + setCookie('filters', filters, { path: '/' }) + props.sendAdvancedFilters(filters) + openChange() + } + + function resetFilters() { + setFullAuto(props.defaultFilterSet.full_auto) + setAutoGuard(props.defaultFilterSet.auto_guard) + setChargeAttack(props.defaultFilterSet.charge_attack) + setMinCharacterCount(props.defaultFilterSet.characters_count) + setMinWeaponCount(props.defaultFilterSet.weapons_count) + setMinSummonCount(props.defaultFilterSet.summons_count) + setMaxButtonsCount(props.defaultFilterSet.button_count) + setMaxTurnsCount(props.defaultFilterSet.turn_count) + setUserQuality(props.defaultFilterSet.user_quality) + setNameQuality(props.defaultFilterSet.name_quality) + setOriginalOnly(props.defaultFilterSet.original) + } + + function openChange() { + if (open) { + setOpen(false) + if (props.onOpenChange) props.onOpenChange(false) + } else { + setOpen(true) + if (props.onOpenChange) props.onOpenChange(true) + } + } + + function onEscapeKeyDown(event: KeyboardEvent) { + event.preventDefault() + openChange() + } + + function onOpenAutoFocus(event: Event) { + event.preventDefault() + } + + // Value listeners + function handleChargeAttackValueChange(value: string) { + setChargeAttack(parseInt(value)) + } + + function handleFullAutoValueChange(value: string) { + const newValue = parseInt(value) + setFullAuto(newValue) + if (newValue === 0 || (newValue === -1 && autoGuard === 1)) + setAutoGuard(newValue) + } + + function handleAutoGuardValueChange(value: string) { + const newValue = parseInt(value) + setAutoGuard(newValue) + if (newValue === 1 || (newValue === -1 && fullAuto === 0)) + setFullAuto(newValue) + } + + function handleMinCharactersValueChange(value: number) { + setMinCharacterCount(value) + } + + function handleMinSummonsValueChange(value: number) { + setMinSummonCount(value) + } + + function handleMinWeaponsValueChange(value: number) { + setMinWeaponCount(value) + } + + function handleMaxButtonsCountValueChange(value: number) { + setMaxButtonsCount(value) + } + + function handleMaxTurnsCountValueChange(value: number) { + setMaxTurnsCount(value) + } + + function handleNameQualityValueChange(value: boolean) { + setNameQuality(value) + } + + function handleUserQualityValueChange(value: boolean) { + setUserQuality(value) + } + + function handleOriginalOnlyValueChange(value: boolean) { + setOriginalOnly(value) + } + + // Sliders + const minCharactersField = () => ( + + ) + + const minWeaponsField = () => ( + + ) + + const minSummonsField = () => ( + + ) + + // Selects + const fullAutoField = () => ( + openSelect('full_auto')} + onClose={() => setFullAutoOpen(false)} + onChange={handleFullAutoValueChange} + > + + {t('modals.filters.options.on_and_off')} + + + {t('modals.filters.options.on')} + + + {t('modals.filters.options.off')} + + + ) + + const autoGuardField = () => ( + openSelect('auto_guard')} + onClose={() => setAutoGuardOpen(false)} + onChange={handleAutoGuardValueChange} + > + + {t('modals.filters.options.on_and_off')} + + + {t('modals.filters.options.on')} + + + {t('modals.filters.options.off')} + + + ) + + const chargeAttackField = () => ( + openSelect('charge_attack')} + onClose={() => setChargeAttackOpen(false)} + onChange={handleChargeAttackValueChange} + > + + {t('modals.filters.options.on_and_off')} + + + {t('modals.filters.options.on')} + + + {t('modals.filters.options.off')} + + + ) + + // Switches + const nameQualityField = () => ( + + ) + + const userQualityField = () => ( + + ) + + const originalOnlyField = () => ( + + ) + + // Inputs + const maxButtonsField = () => ( + + ) + + const maxTurnsField = () => ( + + ) + + return ( + + {props.children} + +
+
+ + {t('modals.filters.title')} + +
+ + + + + +
+ +
+ {chargeAttackField()} + {fullAutoField()} + {autoGuardField()} + {/* {maxButtonsField()} */} + {/* {maxTurnsField()} */} + {minCharactersField()} + {minSummonsField()} + {minWeaponsField()} + {nameQualityField()} + {userQualityField()} + {originalOnlyField()} +
+
+
+
+
+
+
+ ) +} + +export default FilterModal diff --git a/components/GridRep/index.scss b/components/GridRep/index.scss index 02a480b0..4d5bd3df 100644 --- a/components/GridRep/index.scss +++ b/components/GridRep/index.scss @@ -35,9 +35,9 @@ } & > .Grid { - aspect-ratio: 2/1; + aspect-ratio: 2/0.95; display: grid; - grid-template-columns: 1fr 3fr; /* left column takes up 1 fraction, right column takes up 3 fractions */ + grid-template-columns: 1fr 3.36fr; /* left column takes up 1 fraction, right column takes up 3 fractions */ grid-gap: $unit; /* add a gap of 8px between grid items */ .Weapon { @@ -49,6 +49,7 @@ aspect-ratio: 73/153; display: grid; grid-column: 1 / 2; /* spans one column */ + max-height: 140px; } .GridWeapons { @@ -62,6 +63,8 @@ 1fr ); /* create 3 rows, each taking up 1 fraction */ gap: $unit; + // column-gap: $unit; + // row-gap: $unit-2x; } .Grid.Weapon { @@ -136,9 +139,25 @@ } .Properties { + .auto { + display: inline-flex; + gap: $unit-half; + flex-direction: row; + margin-left: $unit-half; + } + .full_auto { color: var(--full-auto-label-text); } + + .auto_guard { + width: 12px; + height: 12px; + + svg { + fill: var(--full-auto-label-text); + } + } } .raid { diff --git a/components/GridRep/index.tsx b/components/GridRep/index.tsx index f9a50e3f..f2b17fac 100644 --- a/components/GridRep/index.tsx +++ b/components/GridRep/index.tsx @@ -12,7 +12,7 @@ import { formatTimeAgo } from '~utils/timeAgo' import Button from '~components/Button' import SaveIcon from '~public/icons/Save.svg' - +import ShieldIcon from '~public/icons/Shield.svg' import './index.scss' interface Props { @@ -23,6 +23,7 @@ interface Props { grid: GridWeapon[] user?: User fullAuto: boolean + autoGuard: boolean favorited: boolean createdAt: Date displayUser?: boolean | false @@ -62,14 +63,21 @@ const GridRep = (props: Props) => { const newWeapons = Array(numWeapons) const gridWeapons = Array(numWeapons) + let foundMainhand = false for (const [key, value] of Object.entries(props.grid)) { - if (value.position == -1) setMainhand(value.object) - else if (!value.mainhand && value.position != null) { + if (value.position == -1) { + setMainhand(value.object) + foundMainhand = true + } else if (!value.mainhand && value.position != null) { newWeapons[value.position] = value.object gridWeapons[value.position] = value } } + if (!foundMainhand) { + setMainhand(undefined) + } + setWeapons(newWeapons) setGrid(gridWeapons) }, [props.grid]) @@ -164,6 +172,26 @@ const GridRep = (props: Props) => {
) + function fullAutoString() { + const fullAutoElement = ( + + {` · ${t('party.details.labels.full_auto')}`} + + ) + + const autoGuardElement = ( + + + + ) + + return ( +
+ {fullAutoElement} + {props.autoGuard ? autoGuardElement : ''} +
+ ) + } const details = (

{props.name ? props.name : t('no_title')}

@@ -172,13 +200,7 @@ const GridRep = (props: Props) => { {props.raid ? props.raid.name[locale] : t('no_raid')} - {props.fullAuto ? ( - - {` · ${t('party.details.labels.full_auto')}`} - - ) : ( - '' - )} + {props.fullAuto ? fullAutoString() : ''}