diff --git a/.vscode/settings.json b/.vscode/settings.json index a5abe4d60..edcdb9f37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,8 @@ { "i18n-ally.localesPaths": [ "packages/locales/lib/human", - "packages/locales/lib/generated" + "packages/locales/lib/generated", + "packages/locales/lib/data" ], "i18n-ally.keystyle": "flat", "[javascript]": { @@ -14,4 +15,4 @@ }, "editor.formatOnSave": true, "docwriter.style": "JSDoc" -} \ No newline at end of file +} diff --git a/package.json b/package.json index ba9c7e89f..c0a1581ff 100644 --- a/package.json +++ b/package.json @@ -168,9 +168,7 @@ "react-i18next": "^11.16.7", "react-leaflet": "4.2.1", "react-router-dom": "^6.15.0", - "react-virtualized-auto-sizer": "^1.0.20", - "react-virtuoso": "^4.5.0", - "react-window": "^1.8.9", + "react-virtuoso": "^4.6.2", "rtree": "^1.4.2", "source-map": "^0.7.4", "suncalc": "^1.9.0", diff --git a/packages/locales/lib/human/en.json b/packages/locales/lib/human/en.json index 3a8fbe5cc..4e39d6af7 100644 --- a/packages/locales/lib/human/en.json +++ b/packages/locales/lib/human/en.json @@ -713,5 +713,7 @@ "expert": "Expert", "basic_description": "Easily select Pokémon and apply a global filter", "intermediate_description": "Set individual filters globally and per Pokémon (traditional)", - "expert_description": "Manual input queries for the most customization" + "expert_description": "Manual input queries for the most customization", + "icon_size": "Icon Size", + "developer": "Developer" } diff --git a/packages/types/lib/client.d.ts b/packages/types/lib/client.d.ts index eb486bb11..b1a455e5f 100644 --- a/packages/types/lib/client.d.ts +++ b/packages/types/lib/client.d.ts @@ -1,7 +1,8 @@ import * as React from 'react' import { Config } from './config' import UAssets from '@services/Icons' -import { ButtonProps } from '@mui/material' +import { ButtonProps, SxProps, Theme } from '@mui/material' +import { SystemStyleObject } from '@mui/system' declare global { declare const CONFIG: Config @@ -15,3 +16,38 @@ declare global { export interface CustomI extends React.HTMLProps { size?: ButtonProps['size'] } + +export type TimesOfDay = 'day' | 'night' | 'dawn' | 'dusk' + +export type MarginProps = { + [Key in + | 'm' + | 'mt' + | 'mb' + | 'ml' + | 'mr' + | 'mx' + | 'my']?: React.CSSProperties['margin'] +} + +export type PaddingProps = { + [Key in + | 'p' + | 'pt' + | 'pb' + | 'pl' + | 'pr' + | 'px' + | 'py']?: React.CSSProperties['padding'] +} + +export interface MultiSelectorProps { + value: V + items: readonly V[] + tKey?: string + disabled?: boolean + onClick?: ( + oldValue: V, + newValue: V, + ) => (e?: React.MouseEvent) => void +} diff --git a/packages/types/lib/config.d.ts b/packages/types/lib/config.d.ts index 9ecd613ac..197dd4974 100644 --- a/packages/types/lib/config.d.ts +++ b/packages/types/lib/config.d.ts @@ -107,8 +107,8 @@ export interface Config database: { schemas: Schema[] settings: { - extraUserFields: string[] - } & BaseConfig['database']['settings'] + extraUserFields: (ExtraField | string)[] + } & Omit } & BaseConfig['database'] scanner: { scanNext: { @@ -130,6 +130,12 @@ export interface Config manualAreas: ExampleConfig['manualAreas'][number][] } +export interface ExtraField { + name: string + database: string + disabled: boolean +} + export interface Webhook { enabled: boolean provider: 'poracle' @@ -163,7 +169,7 @@ export type DeepKeys = { : never }[keyof T] -export type ConfigPaths = DeepKeys +export type ConfigPaths = DeepKeys export type PathValue = P extends `${infer K}.${infer Rest}` ? K extends keyof T @@ -175,7 +181,10 @@ export type PathValue = P extends `${infer K}.${infer Rest}` ? T[P] : never -export type ConfigPathValue

= PathValue +export type ConfigPathValue< + T extends object, + P extends ConfigPaths, +> = PathValue export type Join = K extends string | number ? P extends string | number @@ -223,4 +232,4 @@ export type NestedObjectPaths = Paths export type GetSafeConfig =

( path: P, -) => ConfigPathValue

+) => ConfigPathValue diff --git a/packages/types/lib/general.d.ts b/packages/types/lib/general.d.ts index 9425279f6..0ba326875 100644 --- a/packages/types/lib/general.d.ts +++ b/packages/types/lib/general.d.ts @@ -26,6 +26,7 @@ export type RMGeoJSON = { import masterfile = require('packages/masterfile/lib/data/masterfile.json') import { Config } from './config' +import { SliderProps } from '@mui/material' export type Masterfile = typeof masterfile @@ -99,3 +100,28 @@ export type UAssetsClient = Config['icons']['styles'][number] & { data: UICONS } export type FullClientIcons = Omit & { styles: (Config['icons']['styles'][number] & { data: UICONS })[] } + +export interface RMSlider extends SliderProps { + label?: string + perm?: string + step?: number + i18nKey?: string + disabled?: boolean + low?: number + high?: number + i18nKey?: string + markI18n?: string + noTextInput?: boolean + marks?: number[] +} + +export type RMSliderHandleChange = ( + name: N, + values: number | number[], +) => void + +export interface RMSliderProps { + slide: RMSlider + values: number[] + handleChange: RMSliderHandleChange +} diff --git a/packages/types/lib/scanner.d.ts b/packages/types/lib/scanner.d.ts index cd94a4c82..69df57c87 100644 --- a/packages/types/lib/scanner.d.ts +++ b/packages/types/lib/scanner.d.ts @@ -62,6 +62,8 @@ export interface Gym { power_up_level: number power_up_points: number power_up_end_timestamp: number + deleted: boolean + enabled: boolean } export type FullGym = FullModel diff --git a/packages/types/lib/server.d.ts b/packages/types/lib/server.d.ts index ad9109077..fe94cae4a 100644 --- a/packages/types/lib/server.d.ts +++ b/packages/types/lib/server.d.ts @@ -21,7 +21,7 @@ import Backup = require('server/src/models/Backup') import Nest = require('server/src/models/Nest') import NestSubmission = require('server/src/models/NestSubmission') import Pokestop = require('server/src/models/Pokestop') -import { ModelReturn } from './utility' +import { ModelReturn, OnlyType } from './utility' import { Profile } from 'passport-discord' import { User } from './models' @@ -235,3 +235,20 @@ export type DiscordVerifyFunction = ( profile: Profile, done: VerifyCallback, ) => void + +export type BaseFilter = import('server/src/services/filters/Base') + +export type PokemonFilter = + import('server/src/services/filters/pokemon/Frontend') + +export type AllFilters = ReturnType< + typeof import('server/src/services/filters/builder/base') +> + +export type Categories = keyof AllFilters + +export type AdvCategories = 'pokemon' | 'gyms' | 'pokestops' | 'nests' + +export type UIObject = ReturnType< + typeof import('server/src/services/ui/primary') +> diff --git a/packages/types/package.json b/packages/types/package.json index 2574d1202..3b9f5c51d 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -14,6 +14,7 @@ "devDependencies": { "@apollo/client": "^3.7.15", "@mui/material": "^5.14.0", + "@mui/system": "^5.15.2", "@sentry/node": "^7.48.0", "@types/config": "^3.3.0", "@types/node": "^20.5.1", diff --git a/server/src/graphql/server.js b/server/src/graphql/server.js index 7802d98ec..9b850c21f 100644 --- a/server/src/graphql/server.js +++ b/server/src/graphql/server.js @@ -81,6 +81,7 @@ async function startApollo(httpServer) { async requestDidStart(requestContext) { requestContext.contextValue.startTime = Date.now() + log.debug(requestContext.request?.variables?.filters) return { async willSendResponse(context) { const filterCount = Object.keys( @@ -98,7 +99,6 @@ async function startApollo(httpServer) { const data = response.body.singleResult.data?.[endpoint] const returned = Array.isArray(data) ? data.length : 0 - log.info( HELPERS[endpoint] || `[${endpoint?.toUpperCase()}]`, '|', diff --git a/server/src/models/Gym.js b/server/src/models/Gym.js index 6fda87ddc..41dd9c9e0 100644 --- a/server/src/models/Gym.js +++ b/server/src/models/Gym.js @@ -187,13 +187,16 @@ class Gym extends Model { ) teams.forEach((team) => { - let slotCount = 0 - slots.forEach((slot) => { - if (slot.team === team) { - slotCount += 1 - finalSlots[team].push(+slot.slots) - } - }) + const all = args.filters[`t${team}-0`]?.all + let slotCount = all ? baseGymSlotAmounts.length : 0 + if (!all) { + slots.forEach((slot) => { + if (slot.team === team) { + slotCount += 1 + finalSlots[team].push(+slot.slots) + } + }) + } if (slotCount === baseGymSlotAmounts.length || team == 0) { delete finalSlots[team] finalTeams.push(+team) @@ -436,12 +439,9 @@ class Gym extends Model { .then((r) => { const unique = new Set() r.forEach((result) => { - if (result.team) { - if (result.slots) { - unique.add(`g${result.team}-${result.slots}`) - } else { - unique.add(`t${result.team}-0`) - } + if (result.team !== null && result.slots !== null) { + unique.add(`t${result.team}-0`) + unique.add(`g${result.team}-${6 - result.slots}`) } }) return [...unique] diff --git a/server/src/models/Pokestop.js b/server/src/models/Pokestop.js index f2598fcda..1dc6f694c 100644 --- a/server/src/models/Pokestop.js +++ b/server/src/models/Pokestop.js @@ -870,10 +870,9 @@ class Pokestop extends Model { if ( quest.quest_timestamp >= midnight && (filters.onlyAllPokestops || - (filters[newQuest.key] && - (filters[newQuest.key].adv - ? filters[newQuest.key].adv.includes(quest.quest_title) - : true)) || + (filters[newQuest.key]?.adv && !filters[newQuest.key].all + ? filters[newQuest.key].adv.includes(quest.quest_title) + : true) || filters[`u${quest.quest_reward_type}`]) ) { this.fieldAssigner(newQuest, quest, fields) diff --git a/server/src/services/filters/builder/base.js b/server/src/services/filters/builder/base.js index 4420d04fd..6b523afd0 100644 --- a/server/src/services/filters/builder/base.js +++ b/server/src/services/filters/builder/base.js @@ -54,6 +54,7 @@ function buildDefaultFilters(perms, database) { : undefined, badge: perms.gymBadges ? 'all' : undefined, raidTier: perms.raids ? 'all' : undefined, + standard: new BaseFilter(), filter: { ...buildGyms(perms, defaultFilters.gyms), ...pokemon.raids, @@ -67,6 +68,7 @@ function buildDefaultFilters(perms, database) { pokemon: defaultFilters.nests.pokemon, polygons: defaultFilters.nests.polygons, avgFilter: defaultFilters.nests.avgFilter, + standard: new BaseFilter(), filter: pokemon.nests, } : undefined, @@ -93,6 +95,7 @@ function buildDefaultFilters(perms, database) { ? defaultFilters.pokestops.invasions : undefined, arEligible: perms.pokestops ? false : undefined, + standard: new BaseFilter(), filter: { ...pokemon.rocket, ...buildPokestops(perms, defaultFilters.pokestops), @@ -130,6 +133,7 @@ function buildDefaultFilters(perms, database) { 0, Math.ceil(database.filterContext.Route.maxDistance / 1000) + 1, ], + standard: new BaseFilter(), filter: { global: new BaseFilter(), }, @@ -139,6 +143,7 @@ function buildDefaultFilters(perms, database) { perms.portals && database.models.Portal ? { enabled: defaultFilters.portals.enabled, + standard: new BaseFilter(), filter: { global: new BaseFilter(), old: new BaseFilter(), @@ -149,6 +154,7 @@ function buildDefaultFilters(perms, database) { scanAreas: perms.scanAreas ? { enabled: defaultFilters.scanAreas.enabled, + standard: new BaseFilter(), filterByAreas: false, filter: { areas: [], search: '' }, } @@ -161,6 +167,7 @@ function buildDefaultFilters(perms, database) { s17Cells: defaultFilters.submissionCells.s17Cells, s14Cells: defaultFilters.submissionCells.s14Cells, includeSponsored: defaultFilters.submissionCells.includeSponsored, + standard: new BaseFilter(), filter: { global: new BaseFilter() }, } : undefined, @@ -168,6 +175,7 @@ function buildDefaultFilters(perms, database) { ? { enabled: defaultFilters.s2cells.enabled, cells: defaultFilters.s2cells.cells, + standard: new BaseFilter(), filter: { global: new BaseFilter() }, } : undefined, @@ -175,6 +183,7 @@ function buildDefaultFilters(perms, database) { perms.weather && database.models.Weather ? { enabled: defaultFilters.weather.enabled, + standard: new BaseFilter(), filter: { global: new BaseFilter() }, } : undefined, @@ -182,6 +191,7 @@ function buildDefaultFilters(perms, database) { perms.spawnpoints && database.models.Spawnpoint ? { enabled: defaultFilters.spawnpoints.enabled, + standard: new BaseFilter(), tth: defaultFilters.spawnpoints.tth, filter: { global: new BaseFilter(), @@ -194,6 +204,7 @@ function buildDefaultFilters(perms, database) { perms.scanCells && database.models.ScanCell ? { enabled: defaultFilters.scanCells.enabled, + standard: new BaseFilter(), filter: { global: new BaseFilter() }, } : undefined, @@ -201,6 +212,7 @@ function buildDefaultFilters(perms, database) { perms.devices && database.models.Device ? { enabled: defaultFilters.devices.enabled, + standard: new BaseFilter(), filter: { online: new BaseFilter(), offline: new BaseFilter(), diff --git a/server/src/services/filters/builder/gym.js b/server/src/services/filters/builder/gym.js index 3b600c02d..4528be7d1 100644 --- a/server/src/services/filters/builder/gym.js +++ b/server/src/services/filters/builder/gym.js @@ -6,10 +6,9 @@ const BaseFilter = require('../Base') * * @param {import("@rm/types").Permissions} perms * @param {import("@rm/types").Config['defaultFilters']['gyms']} defaults - * @returns */ function buildGyms(perms, defaults) { - const gymFilters = {} + const gymFilters = /** @type {Record} */ ({}) if (perms.gyms) { Object.keys(Event.masterfile.teams).forEach((team, i) => { diff --git a/server/src/services/filters/builder/pokemon.js b/server/src/services/filters/builder/pokemon.js index c6d45c95d..38dd7dc42 100644 --- a/server/src/services/filters/builder/pokemon.js +++ b/server/src/services/filters/builder/pokemon.js @@ -9,8 +9,14 @@ const BaseFilter = require('../Base') * * @param {import("@rm/types").Config['defaultFilters']} defaults * @param {import('../pokemon/Frontend')} base - * @param {*} custom - * @returns + * @param {import('@rm/types').PokemonFilter} custom + * @returns {{ + * full: { [key: string]: import('@rm/types').PokemonFilter }, + * raids: { [key: string]: BaseFilter }, + * quests: { [key: string]: BaseFilter }, + * nests: { [key: string]: BaseFilter }, + * rocket: { [key: string]: BaseFilter }, + * }} */ function buildPokemon(defaults, base, custom) { const pokemon = { @@ -39,15 +45,17 @@ function buildPokemon(defaults, base, custom) { } pokemon.nests[`${i}-${j}`] = new BaseFilter(defaults.nests.allPokemon) } - if (pkmn.family == i) { - pokemon.quests[`c${pkmn.family}`] = new BaseFilter( - defaults.pokestops.candy, - ) - pokemon.quests[`x${pkmn.family}`] = new BaseFilter( - defaults.pokestops.candy, - ) + if ('family' in pkmn) { + if (pkmn.family === +i) { + pokemon.quests[`c${pkmn.family}`] = new BaseFilter( + defaults.pokestops.candy, + ) + pokemon.quests[`x${pkmn.family}`] = new BaseFilter( + defaults.pokestops.candy, + ) + } } - if (pkmn.tempEvolutions) { + if ('tempEvolutions' in pkmn) { energyAmounts.forEach((a) => { pokemon.quests[`m${i}-${a}`] = new BaseFilter( defaults.pokestops.megaEnergy, diff --git a/server/src/services/filters/builder/pokestop.js b/server/src/services/filters/builder/pokestop.js index 0ceb80587..97dcb2eb1 100644 --- a/server/src/services/filters/builder/pokestop.js +++ b/server/src/services/filters/builder/pokestop.js @@ -8,7 +8,7 @@ const { Event } = require('../../initialization') * * @param {import("@rm/types").Permissions} perms * @param {import("@rm/types").Config['defaultFilters']['pokestops']} defaults - * @returns + * @returns {Record} */ function buildPokestops(perms, defaults) { const quests = { s0: new BaseFilter() } diff --git a/server/src/services/filters/pokemon/Backend.js b/server/src/services/filters/pokemon/Backend.js index f6a924466..b204f6ec2 100644 --- a/server/src/services/filters/pokemon/Backend.js +++ b/server/src/services/filters/pokemon/Backend.js @@ -453,7 +453,7 @@ module.exports = class PkmnBackend { 'weather', ]) } - if (this.perms.pvp && result.cp) { + if (this.perms.pvp && pokemon.cp) { const { cleanPvp, bestPvp } = this.buildPvp(pokemon) result.bestPvp = bestPvp result.cleanPvp = cleanPvp diff --git a/server/src/services/filters/pokemon/Frontend.js b/server/src/services/filters/pokemon/Frontend.js index f53d4dc71..16c5007a4 100644 --- a/server/src/services/filters/pokemon/Frontend.js +++ b/server/src/services/filters/pokemon/Frontend.js @@ -2,7 +2,7 @@ const config = require('@rm/config') const BaseFilter = require('../Base') -module.exports = class PokemonFilter extends BaseFilter { +class PokemonFilter extends BaseFilter { /** * @param {boolean} [enabled] * @param {'sm' | 'md' | 'lg' | 'xl'} [size] @@ -52,3 +52,5 @@ module.exports = class PokemonFilter extends BaseFilter { ) } } + +module.exports = PokemonFilter diff --git a/server/src/services/ui/primary.js b/server/src/services/ui/primary.js index f8fd1714e..50ef2dd65 100644 --- a/server/src/services/ui/primary.js +++ b/server/src/services/ui/primary.js @@ -5,75 +5,78 @@ const { Db } = require('../initialization') const nestFilters = config.getSafe('defaultFilters.nests') const leagues = config.getSafe('api.pvp.leagues') -const SLIDERS = { - pokemon: { - primary: [ - { - name: 'iv', - label: '%', - min: 0, - max: 100, - perm: 'iv', - color: 'secondary', - }, - ], - secondary: [ - { - name: 'level', - label: '', - min: 1, - max: 35, - perm: 'iv', - color: 'secondary', - }, - { - name: 'atk_iv', - label: '', - min: 0, - max: 15, - perm: 'iv', - color: 'secondary', - }, - { - name: 'def_iv', - label: '', - min: 0, - max: 15, - perm: 'iv', - color: 'secondary', - }, - { - name: 'sta_iv', - label: '', - min: 0, - max: 15, - perm: 'iv', - color: 'secondary', - }, - { - name: 'cp', - label: '', - min: 10, - max: 5000, - perm: 'iv', - color: 'secondary', - }, - ], - }, - nests: { - secondary: [ - { - name: 'avgFilter', - i18nKey: 'spawns_per_hour', - label: '', - min: nestFilters.avgFilter[0], - max: nestFilters.avgFilter[1], - perm: 'nests', - step: nestFilters.avgSliderStep, - }, - ], - }, -} +/** @typedef {import('@rm/types').RMSlider} Slider */ + +const SLIDERS = + /** @type {{ pokemon: { primary: Slider[], secondary: Slider[] }, nests: { secondary: Slider[] } }} */ ({ + pokemon: { + primary: [ + { + name: 'iv', + label: '%', + min: 0, + max: 100, + perm: 'iv', + color: 'secondary', + }, + ], + secondary: [ + { + name: 'level', + label: '', + min: 1, + max: 35, + perm: 'iv', + color: 'secondary', + }, + { + name: 'atk_iv', + label: '', + min: 0, + max: 15, + perm: 'iv', + color: 'secondary', + }, + { + name: 'def_iv', + label: '', + min: 0, + max: 15, + perm: 'iv', + color: 'secondary', + }, + { + name: 'sta_iv', + label: '', + min: 0, + max: 15, + perm: 'iv', + color: 'secondary', + }, + { + name: 'cp', + label: '', + min: 10, + max: 5000, + perm: 'iv', + color: 'secondary', + }, + ], + }, + nests: { + secondary: [ + { + name: 'avgFilter', + i18nKey: 'spawns_per_hour', + label: '', + min: nestFilters.avgFilter[0], + max: nestFilters.avgFilter[1], + perm: 'nests', + step: nestFilters.avgSliderStep, + }, + ], + }, + }) leagues.forEach((league) => SLIDERS.pokemon.primary.push({ diff --git a/src/assets/constants.js b/src/assets/constants.js new file mode 100644 index 000000000..727401b55 --- /dev/null +++ b/src/assets/constants.js @@ -0,0 +1,53 @@ +export const ICON_SIZES = /** @type {const} */ (['sm', 'md', 'lg', 'xl']) + +export const XXS_XXL = /** @type {const} */ (['xxs', 'xxl']) + +export const NUNDO_HUNDO = /** @type {const} */ (['zeroIv', 'hundoIv']) + +export const ENUM_GENDER = /** @type {const} */ ([0, 1, 2, 3]) + +export const ENUM_BADGES = /** @type {const} */ ([0, 1, 2, 3]) + +export const S2_LEVELS = /** @type {const} */ ([ + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +]) + +export const FORT_LEVELS = /** @type {const} */ (['all', '1', '2', '3']) + +export const BADGES = /** @type {const} */ ([ + 'all', + 'badge_1', + 'badge_2', + 'badge_3', +]) + +export const QUEST_SETS = /** @type {const} */ ([ + 'with_ar', + 'both', + 'without_ar', +]) + +export const WAYFARER_OPTIONS = /** @type {const} */ ([ + 'rings', + 'includeSponsored', + 's14Cells', + 's17Cells', +]) + +export const ENUM_TTH = /** @type {const} */ ([0, 1, 2]) + +export const MIN_MAX = /** @type {const} */ (['min', 'max']) + +export const ENABLED_ALL = /** @type {const} */ (['enabled', 'all']) + +export const RADIUS_CHOICES = /** @type {const} */ (['pokemon', 'gym']) + +export const METHODS = /** @type {const} */ (['discord', 'telegram']) + +export const FILTER_SKIP_LIST = ['filter', 'enabled', 'legacy'] + +export const ALWAYS_EXCLUDED = new Set(['donor', 'blockedGuildNames', 'admin']) + +export const SCAN_MODES = /** @type */ (['confirmed', 'loading', 'error']) + +export const SCAN_SIZES = /** @type {const} */ (['S', 'M', 'XL']) diff --git a/src/assets/css/main.css b/src/assets/css/main.css index 6a720ae6b..0107b68f7 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -387,15 +387,6 @@ img { font-size: 0; } -.disabled-overlay { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 80%; -} - .perm-wrapper img { width: 100%; } @@ -481,3 +472,72 @@ input[type='time']::-webkit-calendar-picker-indicator { .marker-cluster span { line-height: 30px; } + +.container { + display: grid; + grid-template-columns: 25% 75%; +} + +@media screen and (max-width: 600px) { + .container { + display: grid; + grid-template-columns: 100%; + } +} + +.column-25 { + max-height: 75vh; + overflow: auto; +} + +.column-75 { + display: grid; + grid-template-rows: max-content 1fr; +} + +.vgrid-item { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-template-rows: auto auto 20%; + min-height: 225px; +} + +.vgrid-icon { + grid-column: 3; + grid-row: 1; + justify-self: end; + align-self: start; +} + +.vgrid-image { + grid-column: 1 / 4; + grid-row: 1 / 3; + justify-self: center; + align-self: center; + display: relative; + padding: 0; +} + +.vgrid-caption { + grid-column: 1 / 4; + grid-row: 3; + text-align: center; + justify-self: center; + align-self: center; +} + +.badge-diamond { + clip-path: polygon(50% 0%, 85% 55%, 50% 100%, 15% 55%); + position: absolute; +} + +.disabled-overlay { + position: absolute; + color: black; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 90%; + z-index: 9; +} diff --git a/src/components/Config.jsx b/src/components/Config.jsx index 6de1e545e..58abe9f63 100644 --- a/src/components/Config.jsx +++ b/src/components/Config.jsx @@ -108,6 +108,7 @@ export default function Config({ children }) { : {}, counts: data.authReferences || {}, userBackupLimits: data.database.settings.userBackupLimits || 0, + excludeList: data.authentication.excludeList || [], }, theme: data.map.theme, ui: data.ui, diff --git a/src/components/Container.jsx b/src/components/Container.jsx index 999e62de1..b7d0a1351 100644 --- a/src/components/Container.jsx +++ b/src/components/Container.jsx @@ -8,7 +8,7 @@ import Map from './Map' import ScanOnDemand from './layout/dialogs/scanner/ScanOnDemand' import DraggableMarker from './layout/dialogs/webhooks/human/Draggable' import WebhookAreaSelection from './layout/dialogs/webhooks/human/area/AreaSelection' -import Nav from './layout/Nav' +import { Nav } from './layout/Nav' import ActiveWeather from './layout/general/ActiveWeather' import { ControlledLocate, diff --git a/src/components/QueryData.jsx b/src/components/QueryData.jsx index 068b9bc22..5599fe078 100644 --- a/src/components/QueryData.jsx +++ b/src/components/QueryData.jsx @@ -9,14 +9,13 @@ import Query from '@services/Query' import { getQueryArgs } from '@services/functions/getQueryArgs' import RobustTimeout from '@services/apollo/RobustTimeout' import Utility from '@services/Utility' +import { FILTER_SKIP_LIST } from '@assets/constants' import * as index from './tiles/index' import Clustering from './Clustering' import Notification from './layout/general/Notification' import { GenerateCells } from './tiles/S2Cell' -const FILTER_SKIP_LIST = ['filter', 'enabled', 'legacy'] - /** @param {string} category */ const userSettingsCategory = (category) => { switch (category) { diff --git a/src/components/layout/Nav.jsx b/src/components/layout/Nav.jsx index cc8d0d188..7da23364b 100644 --- a/src/components/layout/Nav.jsx +++ b/src/components/layout/Nav.jsx @@ -7,7 +7,7 @@ import Sidebar from './drawer/Drawer' import FilterMenu from './dialogs/filters/FilterMenu' import UserOptions from './dialogs/UserOptions' import Tutorial from './dialogs/tutorial/Tutorial' -import UserProfile from './dialogs/UserProfile' +import UserProfile from './dialogs/profile' import Search from './dialogs/Search' import MessageOfTheDay from './dialogs/Motd' import DonorPage from './dialogs/DonorPage' @@ -17,27 +17,40 @@ import ScanDialog from './dialogs/scanner/ScanDialog' import Webhook from './dialogs/webhooks/Webhook' import ClientError from './dialogs/ClientError' import { WebhookNotification } from './dialogs/webhooks/Notification' +import AdvancedFilter from './dialogs/filters/Advanced' +import BadgeSelection from './dialogs/BadgeSelection' +import WebhookAdvanced from './dialogs/webhooks/WebhookAdv' +import SlotSelection from './dialogs/filters/SlotSelection' +import { HelpDialog } from './dialogs/Help' -export default function Nav() { - const iconsIsReady = useStatic((s) => !!s.Icons) - if (!iconsIsReady) return null - return ( - <> - - - - - - - - - - - - - - - - - ) -} +export const Nav = React.memo( + () => { + const iconsIsReady = useStatic((s) => !!s.Icons) + if (!iconsIsReady) return null + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ) + }, + () => true, +) diff --git a/src/components/layout/custom/Generator.jsx b/src/components/layout/custom/Generator.jsx index 5e88c815d..1ed6f3b1b 100644 --- a/src/components/layout/custom/Generator.jsx +++ b/src/components/layout/custom/Generator.jsx @@ -10,7 +10,7 @@ import LocalLogin from '../auth/Local' import Telegram from '../auth/Telegram' import CustomText from './CustomText' import CustomButton from './CustomButton' -import { Img } from './CustomImg' +import { Img } from '../general/Img' import LocaleSelection from '../general/LocaleSelection' import LinkWrapper from './LinkWrapper' diff --git a/src/components/layout/dialogs/BadgeSelection.jsx b/src/components/layout/dialogs/BadgeSelection.jsx index 4d749b599..2da9948b8 100644 --- a/src/components/layout/dialogs/BadgeSelection.jsx +++ b/src/components/layout/dialogs/BadgeSelection.jsx @@ -1,69 +1,77 @@ // @ts-check import * as React from 'react' import DialogContent from '@mui/material/DialogContent' -import Button from '@mui/material/Button' -import ButtonGroup from '@mui/material/ButtonGroup' -import { useTranslation } from 'react-i18next' +import Dialog from '@mui/material/Dialog' import { useMutation } from '@apollo/client' import { apolloClient, apolloCache } from '@services/apollo' import Query from '@services/Query' +import { ENUM_BADGES } from '@assets/constants' +import { useLayoutStore } from '@hooks/useStore' import Header from '../general/Header' import Footer from '../general/Footer' +import { MultiSelector } from '../drawer/MultiSelector' -export default function BadgeSelection({ id, setBadgeMenu, badge }) { - const { t } = useTranslation() +const handleClose = () => + useLayoutStore.setState({ + gymBadge: { + open: false, + gymId: '', + badge: 0, + }, + }) + +const footerOptions = + /** @type {import('../general/Footer').FooterButton[]} */ ([ + { + name: 'close', + action: handleClose, + color: 'primary', + align: 'right', + }, + ]) +export default function BadgeSelection() { + const { gymId, badge, open } = useLayoutStore((s) => s.gymBadge) const [setBadgeInDb] = useMutation(Query.user('setGymBadge'), { refetchQueries: ['GetBadgeInfo'], }) + /** @type {import('packages/types/lib').MultiSelectorProps['onClick']} */ + const onClick = React.useCallback( + (_, newV) => () => { + setBadgeInDb({ + variables: { + badge: newV, + gymId, + }, + }) + apolloClient.cache.modify({ + id: apolloCache.identify({ __typename: 'Gym', id: gymId }), + fields: { + badge() { + return newV + }, + }, + }) + handleClose() + }, + [setBadgeInDb, gymId], + ) + return ( - <> -

setBadgeMenu(false)} /> - - - {[0, 1, 2, 3].map((i) => ( - - ))} - + +
+ + -
setBadgeMenu(false), - color: 'primary', - align: 'right', - }, - ]} - role="webhook_footer" - /> - +
+
) } diff --git a/src/components/layout/dialogs/ClientError.jsx b/src/components/layout/dialogs/ClientError.jsx index d1cf06b73..4cf8daef0 100644 --- a/src/components/layout/dialogs/ClientError.jsx +++ b/src/components/layout/dialogs/ClientError.jsx @@ -17,7 +17,7 @@ export default function ClientError() { return ( -
+

{t(`${error}_body`)} diff --git a/src/components/layout/dialogs/DialogWrapper.jsx b/src/components/layout/dialogs/DialogWrapper.jsx index 6378307e2..b6eabb05f 100644 --- a/src/components/layout/dialogs/DialogWrapper.jsx +++ b/src/components/layout/dialogs/DialogWrapper.jsx @@ -1,4 +1,3 @@ -/* eslint-disable no-nested-ternary */ // @ts-check import * as React from 'react' import Dialog from '@mui/material/Dialog' @@ -7,9 +6,10 @@ import { useLayoutStore, useStatic } from '@hooks/useStore' /** * * @param {{ - * dialog: keyof ReturnType, + * dialog?: keyof ReturnType, * variant?: 'small' | 'large' * children: React.ReactNode + * open?: boolean * } & Omit} props * @returns {JSX.Element} */ diff --git a/src/components/layout/dialogs/Feedback.jsx b/src/components/layout/dialogs/Feedback.jsx index 7d23e9679..076d81570 100644 --- a/src/components/layout/dialogs/Feedback.jsx +++ b/src/components/layout/dialogs/Feedback.jsx @@ -24,7 +24,7 @@ export default function Feedback() { return ( -
+
{t('use_the_link_below')} diff --git a/src/components/layout/dialogs/Help.jsx b/src/components/layout/dialogs/Help.jsx new file mode 100644 index 000000000..441451912 --- /dev/null +++ b/src/components/layout/dialogs/Help.jsx @@ -0,0 +1,18 @@ +// @ts-check +import * as React from 'react' + +import { useLayoutStore } from '@hooks/useStore' + +import Help from './tutorial/Advanced' +import { DialogWrapper } from './DialogWrapper' + +export function HelpDialog() { + const { open, category } = useLayoutStore((s) => s.help) + const handleClose = () => + useLayoutStore.setState({ help: { open: false, category: '' } }) + return ( + + + + ) +} diff --git a/src/components/layout/dialogs/NestSubmission.jsx b/src/components/layout/dialogs/NestSubmission.jsx index 7b2812dae..c9232b53a 100644 --- a/src/components/layout/dialogs/NestSubmission.jsx +++ b/src/components/layout/dialogs/NestSubmission.jsx @@ -58,7 +58,7 @@ export default function NestSubmission({ id, name }) { return ( -
+
-
+
state.auth) - const { rolesLinkName, rolesLink } = useStatic((state) => state.config.links) - - const locale = localStorage.getItem('i18nextLng') || 'en' - - const [tab, setTab] = React.useState(0) - const [tabsHeight, setTabsHeight] = React.useState(0) - const [contentHeight, setContentHeight] = React.useState(0) - const handleTabChange = (_event, newValue) => { - setTab(newValue) - } - - const handleClose = React.useCallback( - () => useLayoutStore.setState({ userProfile: false }), - [], - ) - - return ( - -
- ref && setContentHeight(ref.clientHeight)} - > - ref && setTabsHeight(ref.clientHeight)} - > - - {['profile', 'badges', 'access'].map((each) => ( - - ))} - - - - - - - {auth.perms.backups && } - - - - - - - - - -