diff --git a/.gitignore b/.gitignore
index ebe916c9b..ba1df17f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,10 @@ server/src/models/Custom.js
public/images/uicons/*
!public/images/uicons/.gitkeep
+# Custom Image Folder for Custom Components
+public/images/custom/*
+!public/images/custom/.gitkeep
+
# Ignore generated locales
public/locales/*
public/missing-locales/*
diff --git a/package.json b/package.json
index d3ce6b704..ebce3818f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "reactmap",
- "version": "1.0.14",
+ "version": "1.0.16",
"description": "React based frontend map.",
"main": "index.js",
"author": "TurtIeSocks <58572875+TurtIeSocks@users.noreply.github.com>",
@@ -16,7 +16,7 @@
"create-locales": "node server/scripts/createLocales.js",
"missing-locales": "node server/scripts/createLocales.js --missing",
"config-migrate": "node server/scripts/configMigration.js",
- "console": "node --experimental-repl-await ./server/src/console.js",
+ "console": "node --experimental-repl-await ./server/scripts/console.js",
"migrate:make": "knex --knexfile server/knexfile.cjs migrate:make",
"migrate:latest": "knex --knexfile server/knexfile.cjs migrate:latest",
"migrate:rollback": "knex --knexfile server/knexfile.cjs migrate:rollback"
@@ -95,7 +95,7 @@
"lru-cache": "^6.0.0",
"morgan": "^1.10.0",
"mysql2": "^2.3.3",
- "node-fetch": "^2.6.1",
+ "node-fetch": "2",
"node-geocoder": "^3.27.0",
"nodes2ts": "^2.0.0",
"objection": "^3.0.0",
diff --git a/public/base-locales/de.json b/public/base-locales/de.json
index 441df0bd6..6300e315d 100644
--- a/public/base-locales/de.json
+++ b/public/base-locales/de.json
@@ -277,7 +277,7 @@
"weather_subtitle": "Zeigt Wetterinformationen im Spiel für jede Zelle an",
"user_profile": "Benutzer-Profil",
"candy": "Bonbon",
- "show_ex_badge": "Zeige EX Abzeichen",
+ "show_ex_badge": "EX Abzeichen anzeigen",
"search": "Suche",
"global_search_quests": "Füge den Name der Belohnung ein...",
"global_search_pokestops": "Füge den Name des Pokestop ein...",
@@ -328,7 +328,7 @@
"popup_pokemon_data_width": 11,
"login_button": 10,
"join_button": 10,
- "show_dex_num_in_popup": "Zeige Pokedex # in Popup",
+ "show_dex_num_in_popup": "Pokedex # in Popup anzeigen",
"popup": "Popup",
"pvp_level": "Level {{level}} PVP Stats",
"pvp_mega": "Mega PVP Stats",
@@ -390,7 +390,7 @@
"webhook_selection": "{{name}} Auswahl",
"message_of_the_day": "Nachricht des Tages",
"has_quest_indicator": "Alternative Farbe für Stops mit Quests",
- "show_ar_badge": "Zeige AR Symbol",
+ "show_ar_badge": "AR Symbol anzeigen",
"raids_or": "Raidbosse separat filtern",
"general": "Allgemeines",
"link_global_and_advanced": "Verknüpfe Global und Erweitert",
@@ -454,7 +454,7 @@
"error_creating_user": "Neuer Benutzer kann derzeit nicht registriert werden",
"discord_linked": "Discord Verknüpft",
"telegram_linked": "Telegram Verknüpft",
- "slider_little": "kleine Schieberegler",
+ "slider_little": "Mini-Cup",
"tile_servers": "Tileserver",
"tile_servers_default": "Standard Tileserver",
"alola": "Alola",
@@ -474,5 +474,19 @@
"all": "Alle",
"with_ar": "Mit AR",
"without_ar": "Ohne AR",
- "both": "Beide"
+ "both": "Beide",
+ "badge_0": "keiner",
+ "badge_1": "Bronze",
+ "badge_2": "Silber",
+ "badge_3": "Gold",
+ "gym_badge_menu": "Arenaorden bearbeiten",
+ "gym_badges": "Arenaorden",
+ "gym_badge_diamonds": "Arenaorden anzeigen",
+ "gym_badges_subtitle": "Die Arenaorden auf der Karte und als Liste auf der Profilseite anzeigen.",
+ "confirm_filters_reset": "Filter zurücksetzen",
+ "filters_reset_text": "Sollen die Einstellungen wirklich auf die Standardwerte zurückgesetzt werden? Das kann nicht rückgängig gemacht werden!",
+ "filters_reset_title": "Filter zurücksetzen",
+ "loading": "laden von {{category}}",
+ "loading_icons": "Icons abrufen",
+ "loading_invasions": "Rocket-Lineup abrufen"
}
diff --git a/public/base-locales/en.json b/public/base-locales/en.json
index 7589a1509..d0a320586 100644
--- a/public/base-locales/en.json
+++ b/public/base-locales/en.json
@@ -475,5 +475,19 @@
"pokemon_cell": "Pokemon's Location Could Vary",
"timer_verified": "This Timer is Verified",
"timer_unverified": "This Timer is Unverified",
- "all": "All"
+ "all": "All",
+ "badge_0": "None",
+ "badge_1": "Bronze",
+ "badge_2": "Silver",
+ "badge_3": "Gold",
+ "gym_badge_menu": "Edit Gym Badge",
+ "gym_badges": "Gym Badges",
+ "gym_badge_diamonds": "Show Gym Badges",
+ "gym_badges_subtitle": "Displays Gym Badges on the map and a list in the profile page.",
+ "confirm_filters_reset": "Reset filters",
+ "filters_reset_text": "Are you sure you want to reset settings to default values? This cannot be undone!",
+ "filters_reset_title": "Reset filters",
+ "loading": "Loading {{category}}",
+ "loading_icons": "Fetching Icons",
+ "loading_invasions": "Fetching Invasions"
}
diff --git a/public/base-locales/es.json b/public/base-locales/es.json
index 1bc951cb6..9d24f85a4 100644
--- a/public/base-locales/es.json
+++ b/public/base-locales/es.json
@@ -398,8 +398,8 @@
"webhook_selection": "Selección de {{name}}",
"server_dev_error_0": "{{variable_0}}",
"navigation_controls": "Controles de navegación",
- "navigation_controls_react": "Reaccionar",
- "navigation_controls_leaflet": "Folleto",
+ "navigation_controls_react": "React",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "Exclusivo",
"all_moves": "Todos los movimientos",
"move": "Muevete",
@@ -466,5 +466,8 @@
"timer_verified": "Este temporizador está verificado",
"timer_unverified": "Este temporizador no está verificado",
"all": "Todo",
- "both": "Ambos"
+ "both": "Ambos",
+ "loading_icons": "Obtener iconos",
+ "loading_invasions": "Ir a buscar invasiones",
+ "loading": "Cargando {{category}}"
}
diff --git a/public/base-locales/fr.json b/public/base-locales/fr.json
index 5750a126b..3ead6b7b9 100644
--- a/public/base-locales/fr.json
+++ b/public/base-locales/fr.json
@@ -1,6 +1,6 @@
{
"login": "Connexion",
- "click_once": "Ne cliquer 'Connexion' qu'une fois",
+ "click_once": "Ne cliquer 'Autoriser' qu'une fois",
"save": "Enregistrer",
"reset": "Réinitialiser",
"close": "Fermer",
@@ -247,7 +247,7 @@
"sidebar": "Barre latérale",
"sliders": "Curseurs",
"popups": "Popups",
- "closing": "Fermeture",
+ "closing": "Conclusion",
"shortcuts": "Raccourcis",
"iv": "IV",
"area_restrictions": "Restrictions de Zones",
@@ -352,7 +352,7 @@
"pvp_ranking_min_cp": "PC min",
"slider_pvp": "Rang",
"profile_no": "Profil",
- "template": "Template",
+ "template": "Modèle",
"gender": "Sexe",
"clean": "Effacer",
"all_forms": "Toutes les formes",
@@ -385,7 +385,7 @@
"global": "Global",
"manage_webhook": "Gérer {{name}}",
"drag_and_drop": "Glisser et déposer le marqueur pour définir votre emplacement",
- "click_to_select": "Cliquer pour sélectionner",
+ "click_to_select": "Valider",
"add_new": "Ajouter une alerte {{category}}",
"my_location": "Ma position",
"choose_on_map": "Choisir sur la carte",
@@ -423,7 +423,7 @@
"poke_global": "Tous",
"amount": "Quantité",
"specific_gym": "Arène spéficique",
- "search_location": "Recherche de lieux",
+ "search_location": "Recherche d'adresse",
"search_specific": "Chercher un(e) {{category}} spécifique",
"webhook_success_gym": "Les alertes d'arènes, œufs et changement d'équipe ont été ajoutées !",
"slot_changes": "Changements de places",
@@ -471,5 +471,18 @@
"pokemon_cell": "L'emplacement du Pokémon peut varier",
"timer_verified": "Ce timer est vérifié",
"timer_unverified": "Ce timer n'est pas vérifié",
- "all": "Tout"
+ "all": "Tout",
+ "badge_0": "Sans",
+ "badge_1": "Bronze",
+ "badge_2": "Argent",
+ "badge_3": "Or",
+ "gym_badge_menu": "Editer Badge d'Arène",
+ "gym_badges": "Badges d'Arènes",
+ "gym_badge_diamonds": "Afficher les Badges d'Arènes",
+ "gym_badges_subtitle": "Affiche les badges d'arène sur la carte et une liste dans la page profile.",
+ "loading": "Chargement {{category}}",
+ "loading_icons": "Récupération des Icônes",
+ "loading_invasions": "Récupération des Invasions",
+ "login_button": 12,
+ "join_button": 12
}
\ No newline at end of file
diff --git a/public/base-locales/it.json b/public/base-locales/it.json
index a1f49d83a..19837412f 100644
--- a/public/base-locales/it.json
+++ b/public/base-locales/it.json
@@ -1,8 +1,8 @@
{
"page": "Pagina {{page}}",
"navigation_controls": "Controlli di navigazione",
- "navigation_controls_react": "Reagire",
- "navigation_controls_leaflet": "Volantino",
+ "navigation_controls_react": "React",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "Esclusivo",
"all_moves": "Tutte le mosse",
"move": "Mossa",
@@ -435,5 +435,8 @@
"timer_verified": "Questo timer è verificato",
"timer_unverified": "Questo timer non è verificato",
"all": "Tutto",
- "both": "Entrambi"
+ "both": "Entrambi",
+ "loading_icons": "Recupero delle icone",
+ "loading_invasions": "Recupero delle invasioni",
+ "loading": "Caricamento {{category}}"
}
diff --git a/public/base-locales/ja.json b/public/base-locales/ja.json
index a1abbd6a6..82aef7345 100644
--- a/public/base-locales/ja.json
+++ b/public/base-locales/ja.json
@@ -395,7 +395,7 @@
"page": "ページ{{page}}",
"navigation_controls": "ナビゲーションコントロール",
"navigation_controls_react": "React",
- "navigation_controls_leaflet": "リーフレット",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "エクスクルーシブ",
"all_moves": "すべての動き",
"move": "動く",
@@ -436,5 +436,8 @@
"timer_verified": "このタイマーは検証済みです",
"timer_unverified": "このタイマーは未確認です",
"all": "全て",
- "both": "両方"
+ "both": "両方",
+ "loading_icons": "アイコンの取得",
+ "loading_invasions": "侵略をフェッチする",
+ "loading": "{{category}}を読み込んでいます"
}
diff --git a/public/base-locales/ko.json b/public/base-locales/ko.json
index 7e4012f89..9c8ddb152 100644
--- a/public/base-locales/ko.json
+++ b/public/base-locales/ko.json
@@ -34,8 +34,8 @@
"weather_indicator": "날씨 부스트 표시기",
"page": "페이지 {{page}}",
"navigation_controls": "탐색 컨트롤",
- "navigation_controls_react": "반응",
- "navigation_controls_leaflet": "전단",
+ "navigation_controls_react": "React",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "독점적 인",
"all_moves": "모든 동작",
"move": "이동하다",
@@ -435,5 +435,8 @@
"timer_verified": "이 타이머는 확인되었습니다",
"timer_unverified": "이 타이머는 확인되지 않았습니다.",
"all": "모두",
- "both": "둘 다"
+ "both": "둘 다",
+ "loading_icons": "아이콘 가져오기",
+ "loading_invasions": "침략 가져오기",
+ "loading": "{{category}} 로드 중"
}
diff --git a/public/base-locales/nl.json b/public/base-locales/nl.json
index 0f61a8ab4..2f0fb6c72 100644
--- a/public/base-locales/nl.json
+++ b/public/base-locales/nl.json
@@ -475,5 +475,19 @@
"webhook_advanced_save_width": 2,
"login_button": 5,
"join_button": 5,
- "all": "Alle"
+ "all": "Alle",
+ "confirm_filters_reset": "Bevestig filters resetten",
+ "filters_reset_text": "Weet je zeker dat je de filters instellingen terug wilt zetten naar standaardwaarden? Dit kan niet ongedaan gemaakt worden!",
+ "filters_reset_title": "Filters resetten",
+ "badge_0": "Geen",
+ "badge_1": "Bronze",
+ "badge_2": "Silver",
+ "badge_3": "Gold",
+ "gym_badge_menu": "Bewerk Gym Badge",
+ "gym_badges": "Gym Badges",
+ "gym_badge_diamonds": "Toon Gym Badges",
+ "gym_badges_subtitle": "Toont Gym Badges op de kaart en een lijst op de profielpagina.",
+ "loading": "Loading {{category}}",
+ "loading_icons": "Pictogrammen aan het Ophalen",
+ "loading_invasions": "Invasions aan het Ophalen"
}
diff --git a/public/base-locales/pl.json b/public/base-locales/pl.json
index 5e8eaae5a..219a83442 100644
--- a/public/base-locales/pl.json
+++ b/public/base-locales/pl.json
@@ -79,6 +79,12 @@
"locale_selection_fr": "Français",
"locale_selection_it": "Italiano",
"locale_selection_ja": "日本語",
+ "locale_selection_ko": "한국어",
+ "locale_selection_zh-tw": "中文",
+ "locale_selection_ru": "Русский",
+ "locale_selection_pt-br": "Português",
+ "locale_selection_sv": "Svenska",
+ "locale_selection_th": "ไทย",
"import": "Importuj",
"export": "Eksportuj",
"logout": "Wyloguj",
@@ -339,19 +345,19 @@
"tile_servers_default": "Domyślny",
"alola": "Alola",
"galarian": "galarian",
- "submission_cells_subtitle": "Wyświetla informacje przydatne do przesyłania nowych POI",
+ "submission_cells_subtitle": "Wyświetla informacje przydatne w dodawaniu nowych POI",
"webhook_entry": "Dodaj {{category}} do {{name}}",
"distance": "Dystans",
- "gym": "siłownia",
- "egg": "jajko",
- "raid": "Nalot",
- "lure": "Wabik",
- "quest": "Poszukiwanie",
+ "gym": "Gym",
+ "egg": "Jajko",
+ "raid": "Raid",
+ "lure": "Lur",
+ "quest": "Quest",
"invasion": "Inwazja",
- "nest": "Gniazdo",
+ "nest": "Nest",
"pokestop": "Pokestop",
- "team": "Zespół",
- "global": "Światowy",
+ "team": "Drużyna",
+ "global": "Globalny",
"manage_webhook": "Zarządzaj {{name}}",
"drag_and_drop": "Przeciągnij i upuść znacznik, aby ustawić swoją lokalizację",
"click_to_select": "Kliknij aby wybrać",
@@ -362,97 +368,131 @@
"distance_radius": "Podgląd odległości promienia",
"areas": "Obszary",
"select_webhook": "Wybierz bota",
- "stardust": "Gwiezdny pył",
+ "stardust": "Stardust",
"slider_cp": "CP",
- "pvp_ranking_league": "Liga",
+ "pvp_ranking_league": "League",
"pvp_ranking_min_cp": "Min. CP",
- "slider_pvp": "Szeregi",
+ "slider_pvp": "Ranking",
"profile_no": "Profil",
"template": "Szablon",
"gender": "Płeć",
- "clean": "Czysty",
+ "clean": "Czyść",
"all_forms": "Wszystkie formy",
- "pvp_entry": "Wejście PVP",
- "no_iv": "W tym \nNie IV",
+ "pvp_entry": "Wpis PVP",
+ "no_iv": "Dołącz bez IV",
"by_distance": "Według odległości",
"min_time": "Minimalny czas",
"add_new_profile": "Dodaj nowy profil",
- "slider_none": "Nic",
- "non_registered_human_title": "Ups, coś jest nie tak!",
+ "slider_none": "Brak",
+ "non_registered_human_title": "Ups, coś poszło nie tak!",
"non_registered_human_desc": "Nie możesz być zarejestrowany w {{webhook}}\n\nLub serwer jest obecnie niedostępny",
"try_again_later": "Spróbuj ponownie później",
- "selected_areas": "{{amount}} Wybrany obszar",
- "selected_areas_plural": "{{amount}} Wybrane obszary",
- "webhook_selection": "{{name}} Wybór",
+ "selected_areas": "{{amount}} wybrany obszar",
+ "selected_areas_plural": "{{amount}} wybrane obszary",
+ "webhook_selection": "{{name}} wybór",
"server_dev_error_0": "{{variable_0}}",
"navigation_controls": "Sterowanie nawigacją",
- "navigation_controls_react": "Reagować",
- "navigation_controls_leaflet": "Ulotka",
+ "navigation_controls_react": "React",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "Ekskluzywny",
"all_moves": "Wszystkie ruchy",
- "move": "Ruszaj się",
+ "move": "Ruch",
"any": "Każdy",
- "min_spawn_average": "Minimalna częstotliwość pojawiania się",
"select_all": "Zaznacz wszystko",
"delete_all": "Usuń zaznaczone",
"cancel": "Anuluj",
"day_1": "Poniedziałek",
- "day_2": "wtorek",
+ "day_2": "Wtorek",
"day_3": "Środa",
"day_4": "Czwartek",
- "day_5": "piątek",
- "day_6": "sobota",
- "day_7": "niedziela",
+ "day_5": "Piątek",
+ "day_6": "Sobota",
+ "day_7": "Niedziela",
"profile_name": "Nazwa profilu",
"manage_profiles": "Zarządzaj profilami",
- "min_spawn_avg": "Średnia minimalna liczba odradzania",
+ "min_spawn_avg": "Średnia minimalna liczba spawnów",
+ "min_spawn_average": "Średnia minimalna liczba spawnów",
"poke_global": "Wszystko",
- "amount": "Kwota",
- "specific_gym": "Określona siłownia",
+ "amount": "Ilość",
+ "specific_gym": "Określony Gym",
"search_location": "Wyszukaj lokalizacje",
"search_specific": "Wyszukaj konkretną {{category}}",
- "webhook_success_gym": "Dodano najazdy, jajka i alerty o zmianie drużyny!",
- "slot_changes": "Zmiany w automatach",
+ "webhook_success_gym": "Dodano alerty o raidach, jajkach oraz zmianie drużyny!",
+ "slot_changes": "Zmiany w szczelinach",
"slot_changes_poracle": "slot_changes",
"confirm_delete": "Spowoduje to usunięcie całego śledzenia dla tego profilu, czy na pewno?",
"confirm_copy": "Wybierz profil, do którego chcesz skopiować „{{profile}}”, to całkowicie nadpisze ten profil.",
"webhook_error": "{{name}} nie mógł tego przetworzyć.\n\nSpróbuj ponownie później.",
- "profile_error": "Musi być unikalny i ważny",
- "team_0": "Harmonia",
+ "profile_error": "Musi być unikalny i prawidłowy",
+ "team_0": "Harmony",
"battle_changes": "W bitwie",
"battle_changes_poracle": "battle_changes",
"auto": "Automatyczny",
"webhooks_subtitle": "Zarządzaj alertami bezpośrednio z mapy dla tych botów",
"webhooks": "Alerty",
"everything_individually": "Dodaj indywidualne alerty",
- "individually": "indywidualnie",
- "join": "Dołączyć",
+ "individually": "indywidualne",
+ "join": "Dołącz",
"errors_404": "Ups, nie można znaleźć tej strony!",
"errors_500": "Wystąpił błąd serwera, spróbuj ponownie później!",
"local_username": "Nazwa użytkownika",
"local_password": "Hasło",
"local_error": "Wystąpił błąd, spróbuj ponownie później!",
"no_filter_results": "Nie znaleziono wyników\n\nSpróbuj poszerzyć swoje filtry",
- "donor_page": "Strona dawcy",
+ "donor_page": "Strona Sponsora",
"go_back": "Wróć",
"access": "Dostęp",
- "link_discord": "Połącz Discord",
+ "link_discord": "Odnośnik do Discorda",
"select_webhook_strategy": "Menedżer alertów",
- "webhook_strategy_success_0": "Powodzenie! \nOdświeżanie, aby pobrać ustawienia alertów...",
- "register": "Zarejestrować",
- "invalid_credentials": "Nieprawidłowe poświadczenia",
+ "webhook_strategy_success_0": "Sukces! \nOdświeżam, aby pobrać ustawienia alertów...",
+ "register": "Zarejestruj",
+ "invalid_credentials": "Nieprawidłowe dane logowania",
"error_creating_user": "W tej chwili nie można zarejestrować nowego użytkownika",
"discord_linked": "Dyskord połączony",
"telegram_linked": "Połączono z telegramem",
"level": "poziom",
"km": "km",
- "feedback": "Sprzężenie zwrotne",
- "donor_menu": "Menu dawcy",
+ "feedback": "Wyślij opinię",
+ "donor_menu": "Menu Sponsora",
"with_ar": "Z AR",
"without_ar": "Bez AR",
"pokemon_cell": "Lokalizacja Pokemona może się różnić",
- "timer_verified": "Ten zegar jest zweryfikowany",
- "timer_unverified": "Ten zegar jest niezweryfikowany",
+ "timer_verified": "Ten licznik jest zweryfikowany",
+ "timer_unverified": "Ten licznik jest niezweryfikowany",
"all": "Wszystkie",
- "both": "Obydwa"
+ "both": "Obydwa",
+ "badge_0": "Brak",
+ "badge_1": "Brąz",
+ "badge_2": "Srebro",
+ "badge_3": "Złoto",
+ "gym_badge_menu": "Edytuj odznakę gymu",
+ "gym_badges": "Odznaki gymów",
+ "gym_badge_diamonds": "Pokaż odznaki gymów",
+ "gym_badges_subtitle": "Wyświetla odznaki gymów na mapie oraz listę na stronie profilu.",
+ "dialog_filter_footer_adv_menu_width": 2,
+ "dialog_filter_footer_disable_all_width": 2,
+ "dialog_filter_footer_enable_all_width": 2,
+ "dialog_filter_footer_help_width": 2,
+ "dialog_filter_footer_save_width": 2,
+ "dialog_filter_footer_next_width": 2,
+ "drawer_settings_export_settings_width": 6,
+ "drawer_settings_feedback_width": 6,
+ "drawer_settings_import_settings_width": 6,
+ "drawer_settings_logout_width": 6,
+ "drawer_settings_profile_width": 6,
+ "drawer_settings_reset_filters_width": 6,
+ "drawer_settings_stats_width": 6,
+ "webhook_footer_help_width": 4,
+ "webhook_footer_feedback_width": 4,
+ "webhook_footer_close_width": 4,
+ "webhook_footer_add_new_width": 4,
+ "webhook_advanced_save_width": 2,
+ "login_button": 5,
+ "join_button": 5,
+ "confirm_filters_reset": "Zresetuj ustawienia",
+ "filters_reset_text": "Czy na pewno chcesz zresetować ustawienia do domyślnych wartości? Ta czynność jest nieodwracalna!",
+ "filters_reset_title": "Reset ustawień",
+ "loading": "Ładowanie {{category}}",
+ "loading_icons": "Ładowanie ikon",
+ "loading_invasions": "Ładowanie inwazji"
}
diff --git a/public/base-locales/pt-br.json b/public/base-locales/pt-br.json
index 24d1ae8e7..5ec22a2f1 100644
--- a/public/base-locales/pt-br.json
+++ b/public/base-locales/pt-br.json
@@ -366,8 +366,8 @@
"weather_indicator": "Indicador de impulso climático",
"page": "Página {{page}}",
"navigation_controls": "Controles de navegação",
- "navigation_controls_react": "Reagir",
- "navigation_controls_leaflet": "Folheto",
+ "navigation_controls_react": "React",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "Exclusivo",
"all_moves": "Todos os movimentos",
"move": "Jogada",
@@ -435,5 +435,8 @@
"timer_verified": "Este temporizador foi verificado",
"timer_unverified": "Este temporizador não foi verificado",
"all": "Tudo",
- "both": "Ambos"
+ "both": "Ambos",
+ "loading_icons": "Buscando ícones",
+ "loading_invasions": "Buscando Invasões",
+ "loading": "Carregando {{category}}"
}
diff --git a/public/base-locales/ru.json b/public/base-locales/ru.json
index 79ba6cf90..2ccd8dab6 100644
--- a/public/base-locales/ru.json
+++ b/public/base-locales/ru.json
@@ -394,8 +394,8 @@
"non_registered_human_desc": "Вы не можете быть зарегистрированы в {{webhook}}\n\nИли сервер в настоящее время недоступен",
"page": "Страница {{page}}",
"navigation_controls": "Элементы управления навигацией",
- "navigation_controls_react": "Реагировать",
- "navigation_controls_leaflet": "Листовка",
+ "navigation_controls_react": "React",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "Эксклюзивный",
"all_moves": "Все ходы",
"move": "Переехать",
@@ -435,5 +435,8 @@
"timer_verified": "Этот таймер проверен",
"timer_unverified": "Этот таймер не проверен",
"all": "Все",
- "both": "Оба"
+ "both": "Оба",
+ "loading_icons": "Получение иконок",
+ "loading_invasions": "Получение вторжений",
+ "loading": "Загрузка {{category}}"
}
diff --git a/public/base-locales/sv.json b/public/base-locales/sv.json
index 15f367053..8feb50e5a 100644
--- a/public/base-locales/sv.json
+++ b/public/base-locales/sv.json
@@ -365,8 +365,8 @@
"weather_indicator": "Väderboostindikator",
"page": "Sida {{page}}",
"navigation_controls": "Navigationskontroller",
- "navigation_controls_react": "Reagera",
- "navigation_controls_leaflet": "Folder",
+ "navigation_controls_react": "React",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "Exklusiv",
"all_moves": "Alla rörelser",
"move": "Flytta",
@@ -435,5 +435,8 @@
"timer_verified": "Denna timer är verifierad",
"timer_unverified": "Denna timer är overifierad",
"all": "Allt",
- "both": "Både"
+ "both": "Både",
+ "loading_icons": "Hämta ikoner",
+ "loading_invasions": "Att hämta invasioner",
+ "loading": "Laddar {{category}}"
}
diff --git a/public/base-locales/th.json b/public/base-locales/th.json
index a6dd69d3f..f096325f0 100644
--- a/public/base-locales/th.json
+++ b/public/base-locales/th.json
@@ -365,8 +365,8 @@
"weather_indicator": "ตัวบ่งชี้การเพิ่มสภาพอากาศ",
"page": "หน้า {{page}}",
"navigation_controls": "การควบคุมการนำทาง",
- "navigation_controls_react": "ปฏิกิริยา",
- "navigation_controls_leaflet": "แผ่นพับ",
+ "navigation_controls_react": "React",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "พิเศษ",
"all_moves": "การเคลื่อนไหวทั้งหมด",
"move": "เคลื่อนไหว",
@@ -435,5 +435,8 @@
"timer_verified": "ตัวจับเวลานี้ได้รับการยืนยันแล้ว",
"timer_unverified": "ตัวจับเวลานี้ไม่ได้รับการยืนยัน",
"all": "ทั้งหมด",
- "both": "ทั้งคู่"
+ "both": "ทั้งคู่",
+ "loading": "กำลังโหลด {{category}}",
+ "loading_invasions": "เรียกการบุกรุก",
+ "loading_icons": "กำลังเรียกไอคอน"
}
diff --git a/public/base-locales/zh-tw.json b/public/base-locales/zh-tw.json
index 5959bb2ab..855574c2e 100644
--- a/public/base-locales/zh-tw.json
+++ b/public/base-locales/zh-tw.json
@@ -366,8 +366,8 @@
"weather_indicator": "天氣增強指標",
"page": "第 {{page}} 頁",
"navigation_controls": "導航控制",
- "navigation_controls_react": "反應",
- "navigation_controls_leaflet": "傳單",
+ "navigation_controls_react": "React",
+ "navigation_controls_leaflet": "Leaflet",
"exclusive": "獨家的",
"all_moves": "所有動作",
"move": "移動",
@@ -435,5 +435,8 @@
"timer_verified": "此計時器已驗證",
"timer_unverified": "此計時器未經驗證",
"all": "全部",
- "both": "兩個都"
+ "both": "兩個都",
+ "loading_icons": "獲取圖標",
+ "loading_invasions": "獲取入侵",
+ "loading": "正在加載 {{category}}"
}
diff --git a/public/images/custom/.gitkeep b/public/images/custom/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/public/images/perms/gymBadges.png b/public/images/perms/gymBadges.png
new file mode 100644
index 000000000..746027ca4
Binary files /dev/null and b/public/images/perms/gymBadges.png differ
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index e398317a9..000000000
--- a/public/index.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
- Map
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/index.template.html b/public/index.template.html
index 3e133b188..65da5cd1b 100644
--- a/public/index.template.html
+++ b/public/index.template.html
@@ -10,6 +10,38 @@
+
+
+
+
+
+
Loading Map
+
+
obj ? ({
enableTutorial: obj?.enableTutorial,
enableUserProfile: obj?.enableUserProfile,
enableQuestSetSelector: obj?.enableQuestSetSelector,
+ noScanAreaOverlay: obj?.noScanAreaOverlay,
},
theme: obj?.theme,
clustering: {
diff --git a/server/src/configs/default.json b/server/src/configs/default.json
index c23425511..4b3a90004 100644
--- a/server/src/configs/default.json
+++ b/server/src/configs/default.json
@@ -1,6 +1,7 @@
{
"interface": "0.0.0.0",
"port": 8080,
+ "packageSource": "git",
"devOptions": {
"enabled": false,
"graphiql": false,
@@ -21,6 +22,16 @@
"raids": true,
"nests": false
},
+ "queryLimits": {
+ "pokemon": 10000,
+ "pokemonPvp": 25000,
+ "pokestops": 5000,
+ "gyms": 5000,
+ "portals": 5000,
+ "spawnpoints": 10000,
+ "nests": 2500,
+ "scanCells": 5000
+ },
"pvp": {
"leagues": [
{
@@ -60,7 +71,8 @@
"interactionRangeZoom": 15,
"scanAreasZoom": 12,
"scanCellsZoom": 13,
- "submissionZoom": 15
+ "submissionZoom": 15,
+ "geoJsonFileName": "areas.json"
},
"localeSelection": [
"en",
@@ -95,10 +107,12 @@
"invasionCacheHrs": 1,
"questMessage": "",
"navigationControls": "react",
+ "enableFloatingProfileButton": false,
"forceTutorial": true,
"enableTutorial": true,
"enableUserProfile": true,
- "enableQuestSetSelector": false
+ "enableQuestSetSelector": false,
+ "noScanAreaOverlay": false
},
"theme": {
"style": "dark",
@@ -178,7 +192,8 @@
"showExBadge": false,
"showArBadge": false,
"raidLevelBadges": false,
- "raidsOr": true
+ "raidsOr": true,
+ "gymBadgeDiamonds": true
},
"pokestops": {
"clustering": true,
@@ -239,13 +254,16 @@
"inBattle": false,
"raids": true,
"eggs": true,
- "pokemon": true
+ "pokemon": true,
+ "gymBadges": false
},
"nests": {
"enabled": false,
"polygons": false,
"pokemon": false,
- "allPokemon": true
+ "allPokemon": true,
+ "avgFilter": [0, 100],
+ "avgSliderStep": 1
},
"pokestops": {
"enabled": false,
@@ -262,6 +280,7 @@
"pokemon": {
"enabled": false,
"legacyFilter": false,
+ "allPokemon": false,
"globalValues": {
"iv": [
80,
@@ -311,8 +330,10 @@
"database": {
"settings": {
"userTableName": "users",
+ "gymBadgeTableName": "gymBadges",
"sessionTableName": "session",
"migrationTableName": "knex_migrations",
+ "joinGymBadgeTable": false,
"hideOldQuests": false,
"maxConnections": 10
},
@@ -450,6 +471,10 @@
"donor": {
"enabled": true,
"roles": []
+ },
+ "gymBadges": {
+ "enabled": true,
+ "roles": []
}
}
},
diff --git a/server/src/configs/local.example.json b/server/src/configs/local.example.json
index fc96e2d65..39e2c02a0 100644
--- a/server/src/configs/local.example.json
+++ b/server/src/configs/local.example.json
@@ -1,6 +1,7 @@
{
"interface": "0.0.0.0",
"port": 8080,
+ "packageSource": "git/docker",
"api": {
"sessionSecret": "98ki^e72~!@#(85o3kXLI*#c9wu5l!Zx",
"reactMapSecret": "You Should Change Me",
@@ -218,6 +219,10 @@
},
"donor": {
"roles": []
+ },
+ "gymBadges": {
+ "enabled": true,
+ "roles": []
}
}
},
diff --git a/server/src/db/initialization.js b/server/src/db/initialization.js
index 0ed865181..0c2c3ceb0 100644
--- a/server/src/db/initialization.js
+++ b/server/src/db/initialization.js
@@ -27,6 +27,9 @@ const connections = schemas.map(schema => Knex({
schemas.forEach((schema, index) => {
try {
schema.useFor.forEach(category => {
+ if (category === 'user') {
+ models.Badge.knex(connections[index])
+ }
const capital = `${category.charAt(0).toUpperCase()}${category.slice(1)}`
models[capital].knex(connections[index])
})
diff --git a/server/src/db/migrations/20220116023217_gym_tracking.cjs b/server/src/db/migrations/20220116023217_gym_tracking.cjs
new file mode 100644
index 000000000..77cc95eaa
--- /dev/null
+++ b/server/src/db/migrations/20220116023217_gym_tracking.cjs
@@ -0,0 +1,38 @@
+const {
+ database: { settings: { userTableName, gymBadgeTableName } },
+} = require('../../services/config')
+/**
+ * @typedef {import("knex")} Knex
+ */
+
+/**
+ * @param {Knex} knex
+ */
+exports.up = async (knex) => knex.schema.createTable(gymBadgeTableName, table => {
+ table.bigIncrements('id')
+ .primary()
+ table.bigInteger('userId')
+ .references(`${userTableName}.id`)
+ .unsigned()
+ .notNullable()
+ .index()
+ table.string('gymId')
+ .notNullable()
+ table.integer('badge')
+ .unsigned()
+ .notNullable()
+ .defaultTo(0)
+ table.bigInteger('createdAt')
+ .unsigned()
+ .notNullable()
+ .defaultTo(0)
+ table.bigInteger('updatedAt')
+ .unsigned()
+ .notNullable()
+ .defaultTo(0)
+})
+
+/**
+ * @param {Knex} knex
+ */
+exports.down = (knex) => knex.schema.dropTableIfExists(gymBadgeTableName)
diff --git a/server/src/graphql/mapTypes.js b/server/src/graphql/mapTypes.js
index 58a3f7b0b..8a84f4b76 100644
--- a/server/src/graphql/mapTypes.js
+++ b/server/src/graphql/mapTypes.js
@@ -1,6 +1,17 @@
const { gql } = require('apollo-server-express')
module.exports = gql`
+
+ type Badge {
+ id: String
+ name: String
+ url: String
+ lat: Float
+ lon: Float
+ badge: Int
+ deleted: Boolean
+ }
+
type Geocoder {
latitude: Float
longitude: Float
diff --git a/server/src/graphql/resolvers.js b/server/src/graphql/resolvers.js
index 7d7f352cc..5f49782c1 100644
--- a/server/src/graphql/resolvers.js
+++ b/server/src/graphql/resolvers.js
@@ -4,7 +4,7 @@ const { raw } = require('objection')
const config = require('../services/config')
const {
- Device, Gym, Pokemon, Pokestop, Portal, ScanCell, Spawnpoint, Weather, Nest, User,
+ Device, Gym, Pokemon, Pokestop, Portal, ScanCell, Spawnpoint, Weather, Nest, User, Badge,
} = require('../models/index')
const Utility = require('../services/Utility')
const Fetch = require('../services/Fetch')
@@ -12,10 +12,17 @@ const Fetch = require('../services/Fetch')
module.exports = {
JSON: GraphQLJSON,
Query: {
+ badges: (parent, args, { req }) => {
+ const perms = req.user ? req.user.perms : req.session.perms
+ if (perms?.gymBadges) {
+ return Gym.getGymBadges(Utility.dbSelection('gym').type === 'mad', req?.user?.id)
+ }
+ return []
+ },
devices: (parent, args, { req }) => {
const perms = req.user ? req.user.perms : req.session.perms
if (perms?.devices) {
- return Device.getAllDevices({ areaRestrictions: [] }, Utility.dbSelection('device').type === 'mad')
+ return Device.getAllDevices(perms, Utility.dbSelection('device').type === 'mad')
}
return []
},
@@ -32,7 +39,7 @@ module.exports = {
gyms: (parent, args, { req }) => {
const perms = req.user ? req.user.perms : req.session.perms
if (perms?.gyms || perms?.raids) {
- return Gym.getAllGyms(args, perms, Utility.dbSelection('gym').type === 'mad')
+ return Gym.getAllGyms(args, perms, Utility.dbSelection('gym').type === 'mad', req?.user?.id)
}
return []
},
@@ -138,15 +145,18 @@ module.exports = {
},
scanAreas: (parent, args, { req }) => {
const perms = req.user ? req.user.perms : req.session.perms
- if (perms?.scanAreas && config.scanAreas.features.length) {
+ const scanAreas = config.scanAreas[req.headers.host]
+ ? config.scanAreas[req.headers.host]
+ : config.scanAreas.main
+ if (perms?.scanAreas && scanAreas.features.length) {
try {
- config.scanAreas.features = config.scanAreas.features
+ scanAreas.features = scanAreas.features
.sort((a, b) => (a.properties.name > b.properties.name) ? 1 : -1)
} catch (e) {
console.warn('Failed to sort scan areas', e.message)
}
}
- return [config.scanAreas]
+ return [scanAreas]
},
search: async (parent, args, { req }) => {
const perms = req.user ? req.user.perms : req.session.perms
@@ -311,5 +321,22 @@ module.exports = {
.where('username', args.username)
return Boolean(results.length)
},
+ setGymBadge: async (parent, args, { req }) => {
+ const perms = req.user ? req.user.perms : false
+ if (perms?.gymBadges && req?.user?.id) {
+ if (await Badge.query().where('gymId', args.gymId).andWhere('userId', req.user.id).first()) {
+ await Badge.query().where('gymId', args.gymId).andWhere('userId', req.user.id)
+ .update({ badge: args.badge })
+ } else {
+ await Badge.query().insert({
+ badge: args.badge,
+ gymId: args.gymId,
+ userId: req.user.id,
+ })
+ }
+ return true
+ }
+ return false
+ },
},
}
diff --git a/server/src/graphql/scannerTypes.js b/server/src/graphql/scannerTypes.js
index 5663a5882..0313e70ff 100644
--- a/server/src/graphql/scannerTypes.js
+++ b/server/src/graphql/scannerTypes.js
@@ -42,6 +42,7 @@ module.exports = gql`
raid_pokemon_gender: Int
raid_pokemon_evolution: Int
ar_scan_eligible: Boolean
+ badge: Int
}
type Nest {
diff --git a/server/src/graphql/typeDefs.js b/server/src/graphql/typeDefs.js
index eb68e7248..b2651340a 100644
--- a/server/src/graphql/typeDefs.js
+++ b/server/src/graphql/typeDefs.js
@@ -11,6 +11,7 @@ module.exports = gql`
scalar JSON
type Query {
+ badges: [Badge]
devices: [Device]
geocoder(search: String, name: String): [Geocoder]
gyms(minLat: Float, maxLat: Float, minLon: Float, maxLon: Float, ts: Int, filters: JSON): [Gym]
@@ -38,5 +39,6 @@ module.exports = gql`
tutorial(tutorial: Boolean): Boolean
strategy(strategy: String): Boolean
checkUsername(username: String): Boolean
+ setGymBadge(gymId: String, badge: Int): Boolean
}
`
diff --git a/server/src/models/Badge.js b/server/src/models/Badge.js
new file mode 100644
index 000000000..b532ef254
--- /dev/null
+++ b/server/src/models/Badge.js
@@ -0,0 +1,34 @@
+const { Model } = require('objection')
+const {
+ database: { settings: { userTableName, gymBadgeTableName } },
+} = require('../services/config')
+
+module.exports = class Badge extends Model {
+ static get tableName() {
+ return gymBadgeTableName
+ }
+
+ $beforeInsert() {
+ this.createdAt = Math.floor(Date.now() / 1000)
+ this.updatedAt = Math.floor(Date.now() / 1000)
+ }
+
+ $beforeUpdate() {
+ this.updatedAt = Math.floor(Date.now() / 1000)
+ }
+
+ static get relationMappings() {
+ // eslint-disable-next-line global-require
+ const User = require('./User')
+ return {
+ user: {
+ relation: Model.BelongsToOneRelation,
+ modelClass: User,
+ join: {
+ from: `${gymBadgeTableName}.userId`,
+ to: `${userTableName}.id`,
+ },
+ },
+ }
+ }
+}
diff --git a/server/src/models/Filters.js b/server/src/models/Filters.js
index 0d4c6e990..b330a6842 100644
--- a/server/src/models/Filters.js
+++ b/server/src/models/Filters.js
@@ -9,7 +9,7 @@ class GenericFilter {
}
class PokemonFilter extends GenericFilter {
- constructor(iv, level, atk, def, sta, pvp, enabled, size) {
+ constructor(enabled, size, iv, level, atk, def, sta, pvp) {
super(enabled, size)
this.iv = iv || [0, 100]
this.atk_iv = atk || [0, 15]
diff --git a/server/src/models/Gym.js b/server/src/models/Gym.js
index 385eeb1a7..bd1add97f 100644
--- a/server/src/models/Gym.js
+++ b/server/src/models/Gym.js
@@ -5,7 +5,13 @@ const fetchRaids = require('../services/api/fetchRaids')
const { pokemon: masterfile } = require('../data/masterfile.json')
const dbSelection = require('../services/functions/dbSelection')
const getAreaSql = require('../services/functions/getAreaSql')
-const { api: { searchResultsLimit } } = require('../services/config')
+const {
+ api: { searchResultsLimit, queryLimits },
+ database: { schemas, settings: { gymBadgeTableName, joinGymBadgeTable } },
+} = require('../services/config')
+const Badge = require('./Badge')
+
+const gymBadgeDb = schemas.find(x => x.useFor.includes('user'))
module.exports = class Gym extends Model {
static get tableName() {
@@ -17,10 +23,10 @@ module.exports = class Gym extends Model {
? 'gym_id' : 'id'
}
- static async getAllGyms(args, perms, isMad) {
- const { gyms: gymPerms, raids: raidPerms, areaRestrictions } = perms
+ static async getAllGyms(args, perms, isMad, userId) {
+ const { gyms: gymPerms, raids: raidPerms, areaRestrictions, gymBadges } = perms
const {
- onlyAllGyms, onlyRaids, onlyExEligible, onlyInBattle, onlyArEligible, onlyOrRaids, ts,
+ onlyAllGyms, onlyRaids, onlyExEligible, onlyInBattle, onlyArEligible, onlyOrRaids, onlyGymBadges, onlyBadge, ts,
} = args.filters
const safeTs = ts || Math.floor((new Date()).getTime() / 1000)
const query = this.query()
@@ -68,6 +74,13 @@ module.exports = class Gym extends Model {
const teams = []
const eggs = []
const slots = []
+ const actualBadge = onlyBadge === 'all' ? 'all' : onlyBadge && +onlyBadge.replace('badge_', '')
+
+ const userBadges = onlyGymBadges && gymBadges && userId
+ ? await Badge.query()
+ .where('userId', userId)
+ .andWhere('badge', ...(actualBadge === 'all' ? ['>', 0] : [actualBadge]))
+ : []
Object.keys(args.filters).forEach(gym => {
switch (gym.charAt(0)) {
@@ -82,6 +95,7 @@ module.exports = class Gym extends Model {
default: raidBosses.add(gym.split('-')[0]); break
}
})
+
if (!onlyOrRaids) raidBosses.add(0)
const finalTeams = []
const finalSlots = {
@@ -141,6 +155,9 @@ module.exports = class Gym extends Model {
})
}
}
+ if (userBadges.length) {
+ gym.orWhereIn(isMad ? 'gym.gym_id' : 'id', userBadges.map(badge => badge.gymId))
+ }
if (onlyRaids && raidPerms) {
if (raidBosses.size) {
gym.orWhere(raid => {
@@ -170,22 +187,30 @@ module.exports = class Gym extends Model {
const secondaryFilter = queryResults => {
const filteredResults = []
+ const userBadgeObj = Object.fromEntries(userBadges.map(b => [b.gymId, b.badge]))
for (let i = 0; i < queryResults.length; i += 1) {
const gym = queryResults[i]
-
if (gym.availble_slots !== undefined) {
gym.available_slots = gym.availble_slots
}
+ if (userBadgeObj[gym.id]) {
+ gym.badge = userBadgeObj[gym.id]
+ }
if (!gymPerms) {
gym.team_id = 0
gym.available_slots = 6
}
- if (!gym.raid_pokemon_id && (args.filters[`e${gym.raid_level}`] || args.filters[`r${gym.raid_level}`])) {
+ if (onlyRaids && !gym.raid_pokemon_id && (args.filters[`e${gym.raid_level}`] || args.filters[`r${gym.raid_level}`])) {
filteredResults.push(gym)
- } else if (args.filters[`${gym.raid_pokemon_id}-${gym.raid_pokemon_form}`] || args.filters[`r${gym.raid_level}`]) {
+ } else if (onlyRaids && (args.filters[`${gym.raid_pokemon_id}-${gym.raid_pokemon_form}`] || args.filters[`r${gym.raid_level}`])) {
filteredResults.push(gym)
- } else if (gymPerms && (onlyAllGyms || onlyArEligible || onlyExEligible || onlyInBattle)) {
+ } else if (gymPerms
+ && (onlyAllGyms
+ || (onlyArEligible && gym.ar_scan_eligible)
+ || (onlyExEligible && gym.ex_raid_eligible)
+ || (onlyInBattle && gym.in_battle)
+ || (userBadges.length && (actualBadge === 'all' || userBadgeObj[gym.id] === actualBadge)))) {
if (args.filters[`t${gym.team_id}-0`]) {
gym.raid_end_timestamp = null
gym.raid_battle_timestamp = null
@@ -197,7 +222,7 @@ module.exports = class Gym extends Model {
}
return filteredResults
}
- return secondaryFilter(await query)
+ return secondaryFilter(await query.limit(queryLimits.gyms))
}
static async getAvailableRaidBosses(isMad) {
@@ -285,4 +310,52 @@ module.exports = class Gym extends Model {
}
return query
}
+
+ static async getGymBadges(isMad, userId) {
+ const query = this.query()
+ .select([
+ '*',
+ isMad ? 'gym.gym_id AS id' : 'gym.id',
+ isMad ? 'latitude AS lat' : 'lat',
+ isMad ? 'longitude AS lon' : 'lon',
+ isMad ? 'enabled' : 'deleted',
+ ])
+
+ if (isMad) {
+ query.leftJoin('gymdetails', 'gym.gym_id', 'gymdetails.gym_id')
+ }
+
+ if (joinGymBadgeTable) {
+ query.leftJoin(`${gymBadgeDb.database}.${gymBadgeTableName}`, isMad ? 'gym.gym_id' : 'gym.id', `${gymBadgeTableName}.gymId`)
+ .where('userId', userId)
+ .andWhere('badge', '>', 0)
+ .orderBy('updatedAt')
+ const results = await query
+ return isMad ? results.map(gym => gym.deleted = !gym.enabled) : results
+ }
+
+ const userGyms = await Badge.query()
+ .where('userId', userId)
+ .andWhere('badge', '>', 0)
+
+ const results = await query
+ .whereIn(isMad ? 'gym.gym_id' : 'gym.id', userGyms.map(gym => gym.gymId))
+
+ return results
+ .map(gym => {
+ if (typeof gym.enabled === 'boolean') {
+ gym.deleted = !gym.enabled
+ }
+ const gymBadge = userGyms.find(userGym => userGym.gymId === gym.id)
+
+ if (gymBadge) {
+ gym.badge = gymBadge.badge
+ gym.updatedAt = gymBadge.updatedAt
+ gym.createdAt = gymBadge.createdAt
+ }
+ return gym
+ })
+ .sort((a, b) => a.updatedAt - b.updatedAt)
+ .reverse()
+ }
}
diff --git a/server/src/models/Nest.js b/server/src/models/Nest.js
index e9058701e..19f4f5019 100644
--- a/server/src/models/Nest.js
+++ b/server/src/models/Nest.js
@@ -3,7 +3,10 @@ const i18next = require('i18next')
const { pokemon: masterfile } = require('../data/masterfile.json')
const getAreaSql = require('../services/functions/getAreaSql')
const { pokemon: masterPkmn } = require('../data/masterfile.json')
-const { api: { searchResultsLimit } } = require('../services/config')
+const {
+ api: { searchResultsLimit, queryLimits },
+ defaultFilters: { nests: { avgFilter } },
+} = require('../services/config')
module.exports = class Nest extends Model {
static get tableName() {
@@ -18,7 +21,7 @@ module.exports = class Nest extends Model {
const { areaRestrictions } = perms
const pokemon = []
Object.keys(args.filters).forEach(pkmn => {
- if (!pkmn.startsWith('g')) {
+ if (!pkmn.startsWith('o')) {
pokemon.push(pkmn.split('-')[0])
}
})
@@ -26,10 +29,13 @@ module.exports = class Nest extends Model {
.whereBetween('lat', [args.minLat, args.maxLat])
.andWhereBetween('lon', [args.minLon, args.maxLon])
.whereIn('pokemon_id', pokemon)
+ if (!avgFilter.every((x, i) => x === args.filters.onlyAvgFilter[i])) {
+ query.andWhereBetween('pokemon_avg', args.filters.onlyAvgFilter)
+ }
if (areaRestrictions?.length) {
getAreaSql(query, areaRestrictions)
}
- const results = await query
+ const results = await query.limit(queryLimits.nests)
const fixedForms = queryResults => {
const returnedResults = []
diff --git a/server/src/models/Pokemon.js b/server/src/models/Pokemon.js
index d544b935e..d479e47f0 100644
--- a/server/src/models/Pokemon.js
+++ b/server/src/models/Pokemon.js
@@ -4,7 +4,7 @@ const Ohbem = require('ohbem')
const { pokemon: masterfile } = require('../data/masterfile.json')
const legacyFilter = require('../services/legacyFilter')
const {
- api: { pvp: { minCp: pvpMinCp, leagues, reactMapHandlesPvp, levels } },
+ api: { pvp: { minCp: pvpMinCp, leagues, reactMapHandlesPvp, levels }, queryLimits },
} = require('../services/config')
const dbSelection = require('../services/functions/dbSelection')
const getAreaSql = require('../services/functions/getAreaSql')
@@ -258,7 +258,7 @@ module.exports = class Pokemon extends Model {
getAreaSql(query, areaRestrictions, isMad, 'pokemon')
}
- const results = await query
+ const results = await query.limit(queryLimits.pokemon)
const finalResults = []
const pvpResults = []
const listOfIds = []
@@ -319,7 +319,7 @@ module.exports = class Pokemon extends Model {
if (areaRestrictions?.length) {
getAreaSql(pvpQuery, areaRestrictions, isMad, 'pokemon')
}
- pvpResults.push(...await pvpQuery)
+ pvpResults.push(...await pvpQuery.limit(queryLimits.pokemonPvp - results.length))
}
// filter pokes with pvp data
diff --git a/server/src/models/Pokestop.js b/server/src/models/Pokestop.js
index 280197df3..e739de816 100644
--- a/server/src/models/Pokestop.js
+++ b/server/src/models/Pokestop.js
@@ -6,7 +6,7 @@ const fetchQuests = require('../services/api/fetchQuests')
const dbSelection = require('../services/functions/dbSelection')
const getAreaSql = require('../services/functions/getAreaSql')
const {
- api: { searchResultsLimit },
+ api: { searchResultsLimit, queryLimits },
database: { settings },
map,
} = require('../services/config')
@@ -112,7 +112,7 @@ module.exports = class Pokestop extends Model {
// returns everything if all pokestops are on
if (onlyAllPokestops && pokestopPerms) {
- const results = await query
+ const results = await query.limit(queryLimits.pokestops)
const normalized = isMad ? this.mapMAD(results, safeTs) : this.mapRDM(results, safeTs)
return this.secondaryFilter(normalized, args.filters, isMad, midnight)
}
@@ -247,7 +247,7 @@ module.exports = class Pokestop extends Model {
})
}
})
- const results = await query
+ const results = await query.limit(queryLimits.pokestops)
const normalized = isMad ? this.mapMAD(results, safeTs) : this.mapRDM(results, safeTs)
return this.secondaryFilter(normalized, args.filters, isMad, midnight)
}
diff --git a/server/src/models/Portal.js b/server/src/models/Portal.js
index dc7ae8709..5251b15d7 100644
--- a/server/src/models/Portal.js
+++ b/server/src/models/Portal.js
@@ -1,7 +1,7 @@
const { Model } = require('objection')
const getAreaSql = require('../services/functions/getAreaSql')
const {
- api: { searchResultsLimit, portalUpdateLimit },
+ api: { searchResultsLimit, portalUpdateLimit, queryLimits },
} = require('../services/config')
module.exports = class Portal extends Model {
@@ -18,7 +18,7 @@ module.exports = class Portal extends Model {
if (areaRestrictions?.length) {
getAreaSql(query, areaRestrictions)
}
- return query
+ return query.limit(queryLimits.portals)
}
static async search(args, perms, isMad, distance) {
diff --git a/server/src/models/ScanCell.js b/server/src/models/ScanCell.js
index 0b6b79800..9f961eae1 100644
--- a/server/src/models/ScanCell.js
+++ b/server/src/models/ScanCell.js
@@ -2,6 +2,7 @@ const { Model, ref } = require('objection')
const dbSelection = require('../services/functions/dbSelection')
const getPolyVector = require('../services/functions/getPolyVector')
const getAreaSql = require('../services/functions/getAreaSql')
+const { api: { queryLimits } } = require('../services/config')
module.exports = class ScanCell extends Model {
static get tableName() {
@@ -20,7 +21,7 @@ module.exports = class ScanCell extends Model {
if (areaRestrictions?.length) {
getAreaSql(query, areaRestrictions, isMad, 's2cell')
}
- const results = await query
+ const results = await query.limit(queryLimits.scanCells)
return results.map(cell => ({
...cell,
polygon: getPolyVector(cell.id, 'polygon'),
diff --git a/server/src/models/Spawnpoint.js b/server/src/models/Spawnpoint.js
index 8fb4e787b..952ad4ef9 100644
--- a/server/src/models/Spawnpoint.js
+++ b/server/src/models/Spawnpoint.js
@@ -1,6 +1,7 @@
const { Model, raw } = require('objection')
const dbSelection = require('../services/functions/dbSelection')
const getAreaSql = require('../services/functions/getAreaSql')
+const { api: { queryLimits } } = require('../services/config')
module.exports = class Spawnpoint extends Model {
static get tableName() {
@@ -32,6 +33,6 @@ module.exports = class Spawnpoint extends Model {
if (areaRestrictions?.length) {
getAreaSql(query, areaRestrictions, isMad)
}
- return query
+ return query.limit(queryLimits.spawnpoints)
}
}
diff --git a/server/src/models/User.js b/server/src/models/User.js
index 5efb81d89..c14889c7d 100644
--- a/server/src/models/User.js
+++ b/server/src/models/User.js
@@ -1,6 +1,8 @@
/* eslint-disable no-console */
const { Model } = require('objection')
-const { database: { settings: { userTableName } } } = require('../services/config')
+const {
+ database: { settings: { userTableName, gymBadgeTableName } },
+} = require('../services/config')
module.exports = class User extends Model {
static get tableName() {
@@ -13,4 +15,19 @@ module.exports = class User extends Model {
.where({ [`${strategy}Id`]: userId })
.then(() => console.log(`[${botName}] Cleared ${strategy} perms for user ${userId}`))
}
+
+ static get relationMappings() {
+ // eslint-disable-next-line global-require
+ const Badge = require('./Badge')
+ return {
+ badges: {
+ relation: Model.HasManyRelation,
+ modelClass: Badge,
+ join: {
+ from: `${userTableName}.id`,
+ to: `${gymBadgeTableName}.userId`,
+ },
+ },
+ }
+ }
}
diff --git a/server/src/models/index.js b/server/src/models/index.js
index 4e59eb07f..42727ab8a 100644
--- a/server/src/models/index.js
+++ b/server/src/models/index.js
@@ -1,3 +1,4 @@
+const Badge = require('./Badge')
const Device = require('./Device')
const Gym = require('./Gym')
const Nest = require('./Nest')
@@ -13,6 +14,7 @@ const Weather = require('./Weather')
const { PokemonFilter, GenericFilter } = require('./Filters')
module.exports = {
+ Badge,
Device,
Gym,
Nest,
diff --git a/server/src/routes/rootRouter.js b/server/src/routes/rootRouter.js
index f463bd550..0e9624e46 100644
--- a/server/src/routes/rootRouter.js
+++ b/server/src/routes/rootRouter.js
@@ -30,8 +30,11 @@ rootRouter.get('/area/:area/:zoom?', (req, res) => {
const { area, zoom } = req.params
try {
const { scanAreas, manualAreas } = config
- if (scanAreas.features.length) {
- const foundArea = scanAreas.features.find(a => a.properties.name.toLowerCase() === area.toLowerCase())
+ const validScanAreas = scanAreas[req.headers.host]
+ ? scanAreas[req.headers.host]
+ : scanAreas.main
+ if (validScanAreas.features.length) {
+ const foundArea = validScanAreas.features.find(a => a.properties.name.toLowerCase() === area.toLowerCase())
if (foundArea) {
const [lon, lat] = center(foundArea).geometry.coordinates
return res.redirect(`/@/${lat}/${lon}/${zoom || 18}`)
@@ -50,11 +53,12 @@ rootRouter.get('/area/:area/:zoom?', (req, res) => {
rootRouter.get('/settings', async (req, res) => {
try {
- if (!config.authMethods.length || config.authentication.alwaysEnabledPerms.length) {
+ if (config.authentication.alwaysEnabledPerms.length || !config.authMethods.length) {
+ if (req.session.tutorial === undefined) {
+ req.session.tutorial = !config.map.forceTutorial
+ }
req.session.perms = { areaRestrictions: [], webhooks: [] }
- req.session.save()
- }
- if (config.authentication.alwaysEnabledPerms.length) {
+
config.authentication.alwaysEnabledPerms.forEach(perm => {
if (config.authentication.perms[perm]) {
req.session.perms[perm] = true
@@ -187,7 +191,7 @@ rootRouter.get('/settings', async (req, res) => {
serverSettings.available[category].forEach(item => {
if (!serverSettings.defaultFilters[category].filter[item] && !item.startsWith('132')) {
serverSettings.defaultFilters[category].filter[item] = category === 'pokemon'
- ? new PokemonFilter()
+ ? new PokemonFilter(config.defaultFilters.pokemon.allPokemon)
: new GenericFilter()
if (!Number.isNaN(parseInt(item.charAt(0)))) {
const masterfileRef = masterfile.pokemon[item.split('-')[0]]
diff --git a/server/src/services/areas.js b/server/src/services/areas.js
index 58e76e6bc..901bd47d4 100644
--- a/server/src/services/areas.js
+++ b/server/src/services/areas.js
@@ -1,39 +1,39 @@
/* eslint-disable no-console */
-/* eslint-disable no-restricted-syntax */
const config = require('./config')
const loadAreas = () => {
- let areas = {}
try {
- // eslint-disable-next-line global-require
- const data = config.scanAreas || Error('Areas file not found')
- areas = data
+ const normalized = { type: 'FeatureCollection', features: [] }
+ Object.values(config.scanAreas).forEach(area => {
+ if (area?.features.length) {
+ normalized.features.push(...area.features)
+ }
+ })
+ return normalized
} catch (err) {
- const showWarning = config.authentication.areaRestrictions.some(rule => rule.roles.length)
- if (showWarning) {
+ if (config.authentication.areaRestrictions.some(rule => rule.roles.length)) {
console.warn('[Area Restrictions] Disabled - `areas.json` file is missing or broken.')
}
}
- return areas
}
const parseAreas = (areasObj) => {
- let names = {}
const polygons = {}
+ const names = []
- if (Object.keys(areasObj).length === 0) {
+ if (!areasObj) {
return { names, polygons }
}
-
areasObj.features.forEach(feature => {
- if (feature.geometry.type == 'Polygon' && feature.properties.name) {
- polygons[feature.properties.name] = []
- for (const polygonCoordinates of feature.geometry.coordinates) {
- polygons[feature.properties.name].push(...polygonCoordinates)
- }
+ const { name } = feature.properties
+ if (feature.geometry.type == 'Polygon' && name) {
+ polygons[name] = []
+ feature.geometry.coordinates.forEach(coordPair => {
+ polygons[name].push(...coordPair)
+ })
+ names.push(name)
}
})
- names = Object.keys(polygons)
return { names, polygons }
}
diff --git a/server/src/services/checkForUpdates.js b/server/src/services/checkForUpdates.js
index 3cb701fb3..bc789f613 100644
--- a/server/src/services/checkForUpdates.js
+++ b/server/src/services/checkForUpdates.js
@@ -2,6 +2,8 @@
const { exec } = require('child_process')
const fs = require('fs')
+let isDocker = false
+
try {
exec('git branch --show-current', async (err, stdout) => {
try {
@@ -10,6 +12,9 @@ try {
if (!gitRef && (err || typeof stdout !== 'string' || !stdout.trim())) {
throw new Error('Unable to get current branch', err)
}
+ if (typeof gitRef === 'string' && gitRef.trim()) {
+ isDocker = true
+ }
const branch = typeof gitRef === 'string' && gitRef.trim()
? gitRef.split('/')[2].trim()
: stdout.trim()
@@ -33,7 +38,7 @@ try {
const remoteSha = stdout3.split('\t')[0]
if (remoteSha !== sha) {
- console.log('There is a new version available:', remoteSha)
+ console.log('There is a new version available: ', remoteSha, isDocker ? 'docker-compose pull' : 'git pull', ' to update')
}
} catch (e) {
console.warn('Unable to get remote SHA', e.message, 'Branch:', branch, 'Local SHA:', sha)
diff --git a/server/src/services/config.js b/server/src/services/config.js
index 294bd97ae..06ad18fb0 100644
--- a/server/src/services/config.js
+++ b/server/src/services/config.js
@@ -1,3 +1,4 @@
+/* eslint-disable import/no-dynamic-require */
/* eslint-disable global-require */
/* eslint-disable no-console */
process.env.NODE_CONFIG_DIR = `${__dirname}/../configs`
@@ -46,9 +47,6 @@ config.authMethods = [...new Set(config.authentication.strategies
})),
]
-// Auto check for scan overlay settings
-config.map.noScanAreaOverlay = Boolean(config.manualAreas.length)
-
// initialize webhooks
if (config.webhooks.length) {
(async () => {
@@ -61,11 +59,19 @@ if (config.webhooks.length) {
if (!config[opt].length) console.warn(`[${opt}] is empty, you need to add options to it or remove the empty array from your config.`)
})
-// Check if an areas.json exists
-config.scanAreas = fs.existsSync(`${__dirname}/../configs/areas.json`)
- ? require('../configs/areas.json')
+// Load each areas.json
+const loadScanPolygons = (fileName) => fs.existsSync(`${__dirname}/../configs/${fileName}`)
+ ? require(`../configs/${fileName}`)
: { features: [] }
+// Check if an areas.json exists
+config.scanAreas = {
+ main: loadScanPolygons(config.map.geoJsonFileName),
+ ...Object.fromEntries(
+ config.multiDomains.map(d => [d.general?.geoJsonFileName ? d.domain : 'main', loadScanPolygons(d.general?.geoJsonFileName || config.map.geoJsonFileName)]),
+ ),
+}
+
// Map manual areas
config.manualAreas = Object.fromEntries(config.manualAreas.map(area => [area.name, area]))
diff --git a/server/src/services/defaultFilters/buildDefaultFilters.js b/server/src/services/defaultFilters/buildDefaultFilters.js
index 35d7af63a..35368c1dd 100644
--- a/server/src/services/defaultFilters/buildDefaultFilters.js
+++ b/server/src/services/defaultFilters/buildDefaultFilters.js
@@ -7,8 +7,8 @@ const buildPokestops = require('./buildPokestops')
const buildGyms = require('./buildGyms')
const { GenericFilter, PokemonFilter } = require('../../models/index')
-const base = new PokemonFilter()
-const custom = new PokemonFilter(...Object.values(defaultFilters.pokemon.globalValues))
+const base = new PokemonFilter(defaultFilters.pokemon.allPokemon)
+const custom = new PokemonFilter(defaultFilters.pokemon.allPokemon, 'md', ...Object.values(defaultFilters.pokemon.globalValues))
module.exports = function buildDefault(perms) {
const stopReducer = perms.pokestops || perms.lures || perms.quests || perms.invasions
@@ -24,6 +24,8 @@ module.exports = function buildDefault(perms) {
exEligible: perms.gyms ? defaultFilters.gyms.exEligible : undefined,
inBattle: perms.gyms ? defaultFilters.gyms.exEligible : undefined,
arEligible: perms.gyms ? false : undefined,
+ gymBadges: perms.gymBadges ? defaultFilters.gyms.gymBadges : undefined,
+ badge: perms.gymBadges ? 'all' : undefined,
filter: {
...buildGyms(perms, defaultFilters.gyms),
...pokemon.raids,
@@ -33,6 +35,7 @@ module.exports = function buildDefault(perms) {
enabled: defaultFilters.nests.enabled,
pokemon: defaultFilters.nests.pokemon,
polygons: defaultFilters.nests.polygons,
+ avgFilter: defaultFilters.nests.avgFilter,
filter: pokemon.nests,
} : undefined,
pokestops: stopReducer ? {
diff --git a/server/src/services/functions/areaPerms.js b/server/src/services/functions/areaPerms.js
index 58dc17e4d..3dd2cfad3 100644
--- a/server/src/services/functions/areaPerms.js
+++ b/server/src/services/functions/areaPerms.js
@@ -3,7 +3,7 @@ const config = require('../config')
module.exports = function areaPerms(roles) {
let perms = []
- if (Object.keys(areas.names).length) {
+ if (areas.names.length) {
roles.forEach(group => {
config.authentication.areaRestrictions.forEach(rule => {
if (rule.roles.includes(group)) {
diff --git a/server/src/services/initWebhooks.js b/server/src/services/initWebhooks.js
index 4c68bdc31..0f6e13ea1 100644
--- a/server/src/services/initWebhooks.js
+++ b/server/src/services/initWebhooks.js
@@ -44,7 +44,6 @@ module.exports = async function initWebhooks(config) {
if (areas.geoJSON?.features) {
areas.geoJSON.features = areas.geoJSON.features
- .sort((a, b) => a.properties.name.localeCompare(b.properties.name))
.filter(x => !webhook.areasToSkip.includes(x.properties.name.toLowerCase()))
} else {
console.warn('No geofences found')
diff --git a/server/src/services/ui/clientOptions.js b/server/src/services/ui/clientOptions.js
index 713a0f5a7..72e6305a6 100644
--- a/server/src/services/ui/clientOptions.js
+++ b/server/src/services/ui/clientOptions.js
@@ -19,6 +19,7 @@ module.exports = function clientOptions(perms) {
showArBadge: { type: 'bool', perm: ['gyms'] },
raidLevelBadges: { type: 'bool', perm: ['raids'] },
raidsOr: { type: 'bool', perm: ['raids'] },
+ gymBadgeDiamonds: { type: 'bool', perm: ['gymBadges'] },
},
pokestops: {
clustering: { type: 'bool', perm: ['pokestops', 'quests', 'invasions'] },
diff --git a/server/src/services/ui/primary.js b/server/src/services/ui/primary.js
index 553f8a9d7..22516a821 100644
--- a/server/src/services/ui/primary.js
+++ b/server/src/services/ui/primary.js
@@ -1,16 +1,27 @@
/* eslint-disable no-restricted-syntax */
-const { api: { pvp: { leagues } } } = require('../config')
+const {
+ api: { pvp: { leagues } },
+ defaultFilters: { nests: { avgSliderStep } },
+} = require('../config')
module.exports = function generateUi(filters, perms) {
const ui = {}
- const ignoredKeys = ['enabled', 'filter', 'showQuestSet']
+ const ignoredKeys = ['enabled', 'filter', 'showQuestSet', 'badge', 'avgFilter']
// builds the initial categories
for (const [key, value] of Object.entries(filters)) {
let sliders
if (value) {
switch (key) {
- default: ui[key] = {}; break
+ case 'nests':
+ ui[key] = {}
+ sliders = {
+ secondary: [
+ {
+ name: 'avgFilter', i18nKey: 'spawns_per_hour', label: '', min: filters.nests.avgFilter[0], max: filters.nests.avgFilter[1], perm: 'nests', step: avgSliderStep,
+ },
+ ],
+ }; break
case 'pokemon':
ui[key] = {}
sliders = {
@@ -46,6 +57,7 @@ module.exports = function generateUi(filters, perms) {
case 'devices':
if (!ui.admin) ui.admin = {}
ui.admin[key] = true; break
+ default: ui[key] = {}; break
}
// builds each subcategory
for (const [subKey, subValue] of Object.entries(value)) {
diff --git a/src/assets/scss/loading.scss b/src/assets/scss/loading.scss
new file mode 100644
index 000000000..fd88fa135
--- /dev/null
+++ b/src/assets/scss/loading.scss
@@ -0,0 +1,78 @@
+.loader-flex {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ width: 100%;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: -1;
+ background-color: rgba(0, 0, 0, 0.5);
+ -webkit-tap-highlight-color: transparent;
+ flex-direction: column;
+}
+
+@keyframes circular-rotate {
+ 0% {
+ transform-origin: 50% 50%;
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes circular-dash {
+ 0% {
+ stroke-dasharray: 1px, 200px;
+ stroke-dashoffset: 0px;
+ }
+
+ 50% {
+ stroke-dasharray: 100px, 200px;
+ stroke-dashoffset: -15px;
+ }
+
+ 100% {
+ stroke-dasharray: 100px, 200px;
+ stroke-dashoffset: -125px;
+ }
+}
+
+.progress-indeterminate {
+ animation: circular-rotate 1.4s linear infinite;
+}
+
+.progress-root {
+ display: inline-block;
+}
+
+.progress-svg {
+ display: block;
+}
+
+.color-primary {
+ color: #ff5722;
+}
+
+.circle-indeterminate {
+ animation: circular-dash 1.4s ease-in-out infinite;
+ stroke-dasharray: 80px, 200px;
+ stroke-dashoffset: 0px;
+}
+
+.progress-circle {
+ stroke: currentColor;
+}
+
+.loading-text {
+ font-size: 1.5625rem;
+ font-family: "Roboto", "Helvetica", "Arial", sans-serif;
+ font-weight: 400;
+ line-height: 1.235;
+ letter-spacing: 0.00735em;
+ color: #00b0ff;
+ margin: 0;
+}
diff --git a/src/assets/scss/main.scss b/src/assets/scss/main.scss
index 1a37bad25..e5a477c18 100644
--- a/src/assets/scss/main.scss
+++ b/src/assets/scss/main.scss
@@ -1,6 +1,7 @@
@import "~react-leaflet-markercluster/dist/styles.min.css";
@import "~leaflet.locatecontrol/dist/L.Control.Locate.min.css";
@import "holiday.scss";
+@import "loading.scss";
.leaflet-container {
position: absolute;
@@ -19,10 +20,7 @@
body {
background-color: rgb(53, 53, 53);
-}
-
-.markercluster-map {
- height: 90vh;
+ margin: 0;
}
.MuiDrawer-paper {
@@ -258,7 +256,7 @@ img {
padding: 4px 3px 0 3px;
}
-.weather-fancy {
+.weather-icon {
background-color: rgba(255, 255, 255, 0.5);
border: 2px solid rgb(41, 102, 122);
border-radius: 48px;
@@ -267,7 +265,7 @@ img {
height: 30px;
}
-.weather-fancy > img {
+.weather-icon > .fancy {
filter: invert(75%) sepia(100%) hue-rotate(151deg) saturate(3.1);
}
@@ -370,3 +368,15 @@ img {
input[type="time"]::-webkit-calendar-picker-indicator {
filter: invert(100%);
}
+
+.badge_3 {
+ color: #ffd700;
+}
+
+.badge_2 {
+ color: #c0c0c0;
+}
+
+.badge_1 {
+ color: #cd7f32;
+}
diff --git a/src/components/App.jsx b/src/components/App.jsx
index 799d92dbf..01f7980e8 100644
--- a/src/components/App.jsx
+++ b/src/components/App.jsx
@@ -1,83 +1,41 @@
import '../assets/scss/main.scss'
-import React, { Suspense, useEffect, useState, useCallback } from 'react'
+import React, { Suspense } from 'react'
import { ApolloProvider } from '@apollo/client'
-import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
-import { ThemeProvider } from '@material-ui/styles'
-
-import setTheme from '@assets/mui/theme'
-import UIcons from '@services/Icons'
-import Fetch from '@services/Fetch'
import client from '@services/apollo'
-import Auth from './layout/auth/Auth'
-import Login from './layout/auth/Login'
-import RouteChangeTracker from './RouteChangeTracker'
-import Errors from './Errors'
-import ClearStorage from './ClearStorage'
-import HolidayEffects from './HolidayEffects'
-
-export default function App() {
- const [serverSettings, setServerSettings] = useState(null)
+import ReactRouter from './ReactRouter'
- const getServerSettings = useCallback(async () => {
- const data = await Fetch.getSettings()
- const Icons = data.masterfile ? new UIcons(data.config.icons, data.masterfile.questRewardTypes) : null
- if (Icons) {
- await Icons.fetchIcons(data.config.icons.styles)
- if (data.config.icons.defaultIcons) {
- Icons.setSelection(data.config.icons.defaultIcons)
- }
- }
- if (data.ui?.pokestops?.invasions && data.config?.map.fetchLatestInvasions) {
- const invasionCache = JSON.parse(localStorage.getItem('invasions_cache'))
- const cacheTime = data.config.map.invasionCacheHrs * 60 * 60 * 1000
- if (invasionCache && invasionCache.lastFetched + cacheTime > Date.now()) {
- data.masterfile.invasions = invasionCache
- } else {
- data.masterfile.invasions = await Fetch.getInvasions(data.masterfile.invasions)
- }
- }
- setServerSettings({ ...data, Icons })
- }, [])
+const SetText = () => {
+ const locales = {
+ de: 'Übersetzungen werden geladen',
+ en: 'Loading Translations',
+ es: 'Cargando Traducciones',
+ fr: 'Chargement des traductions',
+ it: 'Caricamento Traduzioni',
+ ja: '翻訳を読み込み中',
+ ko: '번역 로드 중',
+ nl: 'Vertalingen worden geladen',
+ pl: 'Ładowanie tłumaczeń',
+ 'pt-br': 'Carregando Traduções',
+ ru: 'Загрузка переводов',
+ sv: 'Laddar Översättningar',
+ th: 'กำลังโหลดการแปล',
+ 'zh-tw': '載入翻譯',
+ }
+ const locale = localStorage?.getItem('i18nextLng') || 'en'
+ const loadingText = document.getElementById('loading-text')
+ if (loadingText) loadingText.innerText = locales[locale.toLowerCase()]
+ return <>>
+}
- useEffect(() => {
- getServerSettings()
- }, [])
+export default function App() {
+ document.body.classList.add('dark')
return (
-
+ }>
-
-
- {(process.env && process.env.GOOGLE_ANALYTICS_ID) && }
-
-
-
-
-
- {serverSettings && }
-
-
- {serverSettings && (
-
- )}
-
-
- {serverSettings && }
-
-
- {serverSettings && }
-
-
-
-
-
-
+
)
diff --git a/src/components/ConfigSettings.jsx b/src/components/ConfigSettings.jsx
index 25a84ef58..4b6a2a6c9 100644
--- a/src/components/ConfigSettings.jsx
+++ b/src/components/ConfigSettings.jsx
@@ -15,8 +15,7 @@ export default function ConfigSettings({
}) {
Utility.analytics('User', serverSettings.user ? `${serverSettings.user.username} (${serverSettings.user.id})` : 'Not Logged In', 'Permissions', true)
- document.title = serverSettings.config.map.headerTitle
- document.body.classList.add('dark')
+ document.title = serverSettings.config?.map?.headerTitle
const setUserSettings = useStore(state => state.setUserSettings)
const setSettings = useStore(state => state.setSettings)
diff --git a/src/components/Map.jsx b/src/components/Map.jsx
index 83b5b61f5..0379fe2d0 100644
--- a/src/components/Map.jsx
+++ b/src/components/Map.jsx
@@ -95,8 +95,7 @@ export default function Map({ serverSettings: { config: { map: config, tileServe
Icons={Icons}
/>
) : (
- Object.entries({ ...ui, ...ui.wayfarer, ...ui.admin }).map(each => {
- const [category, value] = each
+ Object.entries({ ...ui, ...ui.wayfarer, ...ui.admin }).map(([category, value]) => {
let enabled = false
switch (category) {
@@ -110,7 +109,8 @@ export default function Map({ serverSettings: { config: { map: config, tileServe
|| (filters[category].raids && value.raids)
|| (filters[category].exEligible && value.exEligible)
|| (filters[category].inBattle && value.inBattle)
- || (filters[category].arEligible && value.arEligible))
+ || (filters[category].arEligible && value.arEligible)
+ || (filters[category].gymBadges && value.gymBadges))
&& !webhookMode) {
enabled = true
} break
diff --git a/src/components/ReactRouter.jsx b/src/components/ReactRouter.jsx
new file mode 100644
index 000000000..b967437c7
--- /dev/null
+++ b/src/components/ReactRouter.jsx
@@ -0,0 +1,95 @@
+import React, { useEffect, useState, useCallback } from 'react'
+import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
+import { useTranslation } from 'react-i18next'
+import { ThemeProvider } from '@material-ui/styles'
+
+import setTheme from '@assets/mui/theme'
+import UIcons from '@services/Icons'
+import Fetch from '@services/Fetch'
+
+import Auth from './layout/auth/Auth'
+import Login from './layout/auth/Login'
+import RouteChangeTracker from './RouteChangeTracker'
+import Errors from './Errors'
+import ClearStorage from './ClearStorage'
+import HolidayEffects from './HolidayEffects'
+
+const rootLoading = document.getElementById('loader')
+const loadingText = document.getElementById('loading-text')
+
+export default function ReactRouter() {
+ const { t } = useTranslation()
+ const [serverSettings, setServerSettings] = useState(null)
+
+ if (rootLoading) {
+ if (serverSettings) {
+ rootLoading.style.display = 'none'
+ }
+ }
+
+ const getServerSettings = useCallback(async () => {
+ const data = await Fetch.getSettings()
+ const Icons = data.masterfile ? new UIcons(data.config.icons, data.masterfile.questRewardTypes) : null
+ if (Icons) {
+ if (loadingText) {
+ loadingText.innerText = t('loading_icons')
+ }
+ await Icons.fetchIcons(data.config.icons.styles)
+ if (data.config.icons.defaultIcons) {
+ Icons.setSelection(data.config.icons.defaultIcons)
+ }
+ }
+ if (data.ui?.pokestops?.invasions && data.config?.map.fetchLatestInvasions) {
+ const invasionCache = JSON.parse(localStorage.getItem('invasions_cache'))
+ const cacheTime = data.config.map.invasionCacheHrs * 60 * 60 * 1000
+ if (invasionCache && invasionCache.lastFetched + cacheTime > Date.now()) {
+ data.masterfile.invasions = invasionCache
+ } else {
+ if (loadingText) {
+ loadingText.innerText = t('loading_invasions')
+ }
+ data.masterfile.invasions = await Fetch.getInvasions(data.masterfile.invasions)
+ }
+ }
+ setServerSettings({ ...data, Icons })
+ }, [])
+
+ useEffect(() => {
+ if (!serverSettings) {
+ getServerSettings()
+ }
+ }, [])
+
+ return (
+
+
+ {(process.env && process.env.GOOGLE_ANALYTICS_ID) && }
+
+
+
+
+
+ {serverSettings && }
+
+
+ {serverSettings && (
+
+ )}
+
+
+ {serverSettings && }
+
+
+ {serverSettings && }
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/layout/FloatingBtn.jsx b/src/components/layout/FloatingBtn.jsx
index 17e5503df..364229924 100644
--- a/src/components/layout/FloatingBtn.jsx
+++ b/src/components/layout/FloatingBtn.jsx
@@ -1,7 +1,7 @@
import React, { useEffect, useRef } from 'react'
import { Grid, Fab } from '@material-ui/core'
import {
- Menu, LocationOn, ZoomIn, ZoomOut, Search, NotificationsActive, Save, CardMembership, AttachMoney, EuroSymbol,
+ Menu, LocationOn, ZoomIn, ZoomOut, Search, NotificationsActive, Save, CardMembership, AttachMoney, EuroSymbol, Person,
} from '@material-ui/icons'
import { useTranslation } from 'react-i18next'
import { useMap } from 'react-leaflet'
@@ -9,7 +9,7 @@ import L from 'leaflet'
import useStyles from '@hooks/useStyles'
import useLocation from '@hooks/useLocation'
-import { useStore } from '@hooks/useStore'
+import { useStore, useStatic } from '@hooks/useStore'
const DonationIcons = {
dollar: AttachMoney,
@@ -21,8 +21,11 @@ export default function FloatingButtons({
toggleDrawer, toggleDialog, safeSearch,
isMobile, perms, webhookMode, setWebhookMode,
settings, webhooks, donationPage, setDonorPage,
+ setUserProfile,
}) {
const { t } = useTranslation()
+ const { map: { enableFloatingProfileButton } } = useStatic(state => state.config)
+ const { loggedIn } = useStatic(state => state.auth)
const map = useMap()
const ref = useRef(null)
const classes = useStyles()
@@ -54,6 +57,13 @@ export default function FloatingButtons({
+ {enableFloatingProfileButton && loggedIn && (
+
+ setUserProfile(true)} title={t('user_profile')} disabled={Boolean(webhookMode)}>
+
+
+
+ )}
{safeSearch.length ? (
diff --git a/src/components/layout/Nav.jsx b/src/components/layout/Nav.jsx
index ba3132053..b4fe70285 100644
--- a/src/components/layout/Nav.jsx
+++ b/src/components/layout/Nav.jsx
@@ -18,6 +18,7 @@ import Search from './dialogs/Search'
import Motd from './dialogs/Motd'
import DonorPage from './dialogs/DonorPage'
import Feedback from './dialogs/Feedback'
+import ResetFilters from './dialogs/ResetFilters'
const searchable = ['quests', 'pokestops', 'raids', 'gyms', 'portals', 'nests']
@@ -38,6 +39,7 @@ export default function Nav({
const setUserProfile = useStatic(state => state.setUserProfile)
const feedback = useStatic(state => state.feedback)
const setFeedback = useStatic(state => state.setFeedback)
+ const resetFilters = useStatic(state => state.resetFilters)
const filters = useStore(state => state.filters)
const setFilters = useStore(state => state.setFilters)
@@ -121,29 +123,31 @@ export default function Nav({
settings={settings}
donationPage={donationPage}
setDonorPage={setDonorPage}
+ setUserProfile={setUserProfile}
/>
)}
- {userProfile ? (
-
- ) : (
-
- )}
+
+
+
setWebhookAlert({ open: false, severity: 'info', message: '' })}
diff --git a/src/components/layout/auth/Login.jsx b/src/components/layout/auth/Login.jsx
index 911f5674b..0eb94dc40 100644
--- a/src/components/layout/auth/Login.jsx
+++ b/src/components/layout/auth/Login.jsx
@@ -41,7 +41,7 @@ const Login = ({ clickedTwice, location, serverSettings, getServerSettings }) =>
direction="column"
justifyContent="center"
alignItems="center"
- style={{ minHeight: '95vh' }}
+ style={{ minHeight: '95vh', width: '100%' }}
>
@@ -49,17 +49,17 @@ const Login = ({ clickedTwice, location, serverSettings, getServerSettings }) =>
{serverSettings?.authMethods?.includes('discord') && (
-
+
{serverSettings.config.map.discordInvite && (
-
+
)}
diff --git a/src/components/layout/dialogs/BadgeSelection.jsx b/src/components/layout/dialogs/BadgeSelection.jsx
new file mode 100644
index 000000000..8d227850f
--- /dev/null
+++ b/src/components/layout/dialogs/BadgeSelection.jsx
@@ -0,0 +1,47 @@
+import React from 'react'
+import { DialogContent, ButtonGroup, Button } from '@material-ui/core'
+import { useTranslation } from 'react-i18next'
+import { useMutation } from '@apollo/client'
+
+import Query from '@services/Query'
+
+import Header from '../general/Header'
+import Footer from '../general/Footer'
+
+export default function BadgeSelection({ gym, setBadgeMenu, badge, setBadge }) {
+ const { t } = useTranslation()
+
+ const [setBadgeInDb] = useMutation(Query.user('setGymBadge'), {
+ refetchQueries: ['GetBadgeInfo'],
+ })
+ return (
+ <>
+ setBadgeMenu(false)} />
+
+
+ {[0, 1, 2, 3].map(i => (
+
+ ))}
+
+
+
{refreshing && }
>
)
@@ -172,46 +180,166 @@ const ProfilePermissions = ({ perms, excludeList, t }) => (
}
return (
-
- {!perms[perm] && }
- {perm !== 'areaRestrictions' && perm !== 'webhooks' ? (
-
- ) : (
-
- {perms[perm].map(area => (
-
-
- {Utility.getProperName(area)}
-
-
- ))}
-
- )}
-
-
- {t(Utility.camelToSnake(perm))}
-
-
- {t(`${Utility.camelToSnake(perm)}_subtitle`)}
-
-
-
+
)
})}
)
+
+const PermCard = ({ perms, perm, t }) => (
+
+ {!perms[perm] && }
+ {perm !== 'areaRestrictions' && perm !== 'webhooks' ? (
+
+ ) : (
+
+ {perms[perm].map(area => (
+
+
+ {Utility.getProperName(area)}
+
+
+ ))}
+
+ )}
+
+
+ {t(Utility.camelToSnake(perm))}
+
+
+ {t(`${Utility.camelToSnake(perm)}_subtitle`)}
+
+
+
+)
+
+const GymBadges = ({ isMobile, t }) => {
+ const { data } = useQuery(Query.gyms('badges'), {
+ fetchPolicy: 'network-only',
+ })
+ const Icons = useStatic(s => s.Icons)
+ const map = useMap()
+
+ let gold = 0
+ let silver = 0
+ let bronze = 0
+
+ if (data?.badges) {
+ data.badges.forEach(gym => {
+ switch (gym.badge) {
+ case 3: gold += 1; break
+ case 2: silver += 1; break
+ case 1: bronze += 1; break
+ default:
+ }
+ })
+ }
+
+ return data ? (
+
+
+
+ {t('gym_badges')}
+
+
+ {[bronze, silver, gold].map((count, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+
+ {t(`badge_${i + 1}`)}: {count}
+
+
+ ))}
+
+
+
+
+ ) : null
+}
+
+const BadgeTile = ({ data, rowIndex, columnIndex, style }) => {
+ const { badges, columnCount, Icons, t, map } = data
+ const item = badges[rowIndex * columnCount + columnIndex]
+ const [badge, setBadge] = useState(item?.badge)
+ const [badgeMenu, setBadgeMenu] = useState(false)
+
+ return item && badge ? (
+
+ {item.deleted && }
+ setBadgeMenu(true)}
+ >
+
+
+ map.flyTo([item.lat, item.lon], 16)}>
+
+ {badge && (
+
+ )}
+
+
+
+ {item.name || t('unknown_gym')}
+
+
+
+
+ ) : null
+}
diff --git a/src/components/layout/dialogs/filters/SliderTile.jsx b/src/components/layout/dialogs/filters/SliderTile.jsx
index 58c4de421..9e404795a 100644
--- a/src/components/layout/dialogs/filters/SliderTile.jsx
+++ b/src/components/layout/dialogs/filters/SliderTile.jsx
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
export default function SliderTile({
filterSlide: {
- name, min, max, color, disabled, label, low, high,
+ name, min, max, color, disabled, label, low, high, step, i18nKey,
}, handleChange, filterValues,
}) {
const { t } = useTranslation()
@@ -57,7 +57,7 @@ export default function SliderTile({
>
setFullName(!fullName)} style={{ color: textColor }}>
- {t(`slider_${name}`)}
+ {t(i18nKey || `slider_${name}`)}
{['min', 'max'].map((each, index) => (
@@ -96,6 +96,7 @@ export default function SliderTile({
}}
disabled={disabled}
valueLabelDisplay="auto"
+ step={step}
/>
diff --git a/src/components/layout/drawer/Drawer.jsx b/src/components/layout/drawer/Drawer.jsx
index fd6c87f96..41f6d376c 100644
--- a/src/components/layout/drawer/Drawer.jsx
+++ b/src/components/layout/drawer/Drawer.jsx
@@ -9,7 +9,7 @@ import Utility from '@services/Utility'
import SettingsMenu from './Settings'
import WithSubItems from './WithSubItems'
-import WithSliders from './WithSliders'
+import PokemonSection from './Pokemon'
import useStyles from '../../../hooks/useStyles'
import { useStore, useStatic } from '../../../hooks/useStore'
import Areas from './Areas'
@@ -41,6 +41,7 @@ export default function Sidebar({
+ {items.map(item => (
+
+ ))}
+
+ )
+}
diff --git a/src/components/layout/drawer/WithSliders.jsx b/src/components/layout/drawer/Pokemon.jsx
similarity index 100%
rename from src/components/layout/drawer/WithSliders.jsx
rename to src/components/layout/drawer/Pokemon.jsx
diff --git a/src/components/layout/drawer/Settings.jsx b/src/components/layout/drawer/Settings.jsx
index 02a11f931..36aa671e7 100644
--- a/src/components/layout/drawer/Settings.jsx
+++ b/src/components/layout/drawer/Settings.jsx
@@ -1,8 +1,8 @@
-import React, { useState } from 'react'
+import React from 'react'
import {
FormControl, Grid, InputLabel, MenuItem, Select, Button,
} from '@material-ui/core'
-import { Link, Redirect } from 'react-router-dom'
+import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useStore, useStatic } from '@hooks/useStore'
@@ -16,6 +16,7 @@ export default function Settings({ Icons }) {
const setStaticIcons = useStatic(state => state.setIcons)
const setUserProfile = useStatic(state => state.setUserProfile)
const setFeedback = useStatic(state => state.setFeedback)
+ const setResetFilters = useStatic(state => state.setResetFilters)
const setTutorial = useStore(state => state.setTutorial)
const settings = useStore(state => state.settings)
@@ -23,8 +24,6 @@ export default function Settings({ Icons }) {
const icons = useStore(state => state.icons)
const setIcons = useStore(state => state.setIcons)
- const [redirect, setRedirect] = useState(false)
-
const handleChange = event => {
setSettings({
...settings,
@@ -68,9 +67,6 @@ export default function Settings({ Icons }) {
setTimeout(() => window.location.reload(), 1500)
}
- if (redirect) {
- return
- }
return (
setRedirect(true)}
+ onClick={() => setResetFilters(true)}
>
{t('reset_filters')}
diff --git a/src/components/layout/drawer/WithSubItems.jsx b/src/components/layout/drawer/WithSubItems.jsx
index 2a4f2c32f..6ac949ab5 100644
--- a/src/components/layout/drawer/WithSubItems.jsx
+++ b/src/components/layout/drawer/WithSubItems.jsx
@@ -1,23 +1,24 @@
import React from 'react'
import {
- Grid, Typography, Switch, ButtonGroup, Button,
+ Grid, Typography, Switch,
} from '@material-ui/core'
import { useTranslation } from 'react-i18next'
import Utility from '@services/Utility'
+import MultiSelector from './MultiSelector'
+import SliderTile from '../dialogs/filters/SliderTile'
export default function WithSubItems({
- category, filters, setFilters, subItem, noScanAreaOverlay, enableQuestSetSelector,
+ category, filters, setFilters, subItem, noScanAreaOverlay, enableQuestSetSelector, data,
}) {
const { t } = useTranslation()
- let filterCategory
if (category === 'scanAreas' && noScanAreaOverlay) {
return null
}
- if (category === 'wayfarer' || category === 'admin') {
- filterCategory = (
+ const filterCategory = category === 'wayfarer' || category === 'admin'
+ ? (
{
@@ -31,8 +32,7 @@ export default function WithSubItems({
}}
/>
)
- } else {
- filterCategory = (
+ : (
{
@@ -46,6 +46,23 @@ export default function WithSubItems({
}}
/>
)
+
+ if (category === 'nests' && subItem === 'sliders') {
+ return (
+
+ setFilters({
+ ...filters,
+ [category]: {
+ ...filters[category],
+ avgFilter: values,
+ },
+ })}
+ filterValues={filters[category]}
+ />
+
+ )
}
return (
@@ -56,28 +73,26 @@ export default function WithSubItems({
{filterCategory}
- {enableQuestSetSelector === true && category === 'pokestops' && subItem === 'quests' && filters.pokestops.quests === true && (
+ {enableQuestSetSelector === true && category === 'pokestops' && subItem === 'quests' && filters[category].quests === true && (
+
+
+
+ )}
+ {category === 'gyms' && subItem === 'gymBadges' && filters[category].gymBadges === true && (
-
- {['with_ar', 'both', 'without_ar'].map(questSet => (
-
- ))}
-
+
)}
>
diff --git a/src/components/markers/device.js b/src/components/markers/device.js
index fdc819683..49beb92bd 100644
--- a/src/components/markers/device.js
+++ b/src/components/markers/device.js
@@ -2,12 +2,12 @@ import { Icon } from 'leaflet'
export default function getDeviceMarkers(status, Icons) {
const size = Icons.getSize('device')
- const { x, y } = Icons.getPopupOffset('device')
+ const { offsetX, offsetY, popupX, popupY, sizeMultiplier } = Icons.getModifiers('device')
return new Icon({
iconUrl: Icons.getMisc(status),
- iconSize: [size, size],
- iconAnchor: [20, 33.96],
- popupAnchor: [-5 + x, -37 + y],
+ iconSize: [size * sizeMultiplier, size * sizeMultiplier],
+ iconAnchor: [20 * offsetX, 33.96 * offsetY],
+ popupAnchor: [-5 + popupX, -37 + popupY],
className: 'marker',
})
}
diff --git a/src/components/markers/gym.jsx b/src/components/markers/gym.jsx
index 9ad9cd4cb..585c189a5 100644
--- a/src/components/markers/gym.jsx
+++ b/src/components/markers/gym.jsx
@@ -15,7 +15,7 @@ const getBadgeColor = (raidLevel) => {
}
}
-export default function GymMarker(gym, hasHatched, hasRaid, filters, Icons, userSettings) {
+export default function GymMarker(gym, hasHatched, hasRaid, filters, Icons, userSettings, badge) {
const {
in_battle, team_id, available_slots, raid_level, ex_raid_eligible, ar_scan_eligible,
} = gym
@@ -32,6 +32,7 @@ export default function GymMarker(gym, hasHatched, hasRaid, filters, Icons, user
let raidIcon
let raidSize = 0
const slotModifier = gymMod[filledSlots] || gymMod['0'] || gymSize * 0.5
+ const showDiamond = filters.gymBadges && userSettings.gymBadgeDiamonds && badge
if (hasRaid) {
const {
@@ -58,51 +59,92 @@ export default function GymMarker(gym, hasHatched, hasRaid, filters, Icons, user
const ReactIcon = (
-
- {Boolean(userSettings.showExBadge && ex_raid_eligible && !gymIcon.includes('_ex')) && (
-
- )}
- {Boolean(userSettings.showArBadge && ar_scan_eligible && !gymIcon.includes('_ar')) && (
-
- )}
- {Boolean(in_battle && !gymIcon.includes('_b')) && (
-
+ {showDiamond ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+ {Boolean(userSettings.showExBadge && ex_raid_eligible && !gymIcon.includes('_ex')) && (
+
+ )}
+ {Boolean(userSettings.showArBadge && ar_scan_eligible && !gymIcon.includes('_ar')) && (
+
+ )}
+ {Boolean(in_battle && !gymIcon.includes('_b')) && (
+
+ )}
+ {Boolean(filters.gymBadges && badge) && (
+
{ switch (badge) { case 1: return 'third'; case 2: return 'second'; default: return 'first' } })())}
+ style={{
+ width: gymSize / 2,
+ height: 'auto',
+ bottom: 18 + gymMod.offsetY,
+ left: `${gymMod.offsetX * 55}%`,
+ transform: 'translateX(50%)',
+ }}
+ />
+ )}
+ >
)}
{raidIcon && (
new Icon({
})
export const fancyMarker = (iconUrl, size, pkmn, glow, ivCircle, Icons, weatherCheck, isNight) => {
- const { pokemon: pokemonMod } = Icons.modifiers
+ const { pokemon: pokemonMod, weather: weatherMod } = Icons.modifiers
let badge
switch (pkmn.bestPvp) {
default: break
@@ -54,20 +54,23 @@ export const fancyMarker = (iconUrl, size, pkmn, glow, ivCircle, Icons, weatherC
)}
{Boolean(weatherCheck) && (
diff --git a/src/components/markers/pokestop.jsx b/src/components/markers/pokestop.jsx
index c9d50303a..ff1c1b29e 100644
--- a/src/components/markers/pokestop.jsx
+++ b/src/components/markers/pokestop.jsx
@@ -165,7 +165,7 @@ export default function stopMarker(pokestop, hasQuest, hasLure, hasInvasion, fil
)
return L.divIcon({
- popupAnchor: [popupX, (pokestopMod.manualPopup
+ popupAnchor: [popupX - 5, (pokestopMod.manualPopup
? pokestopMod.manualPopup - totalInvasionSize * 0.25 - totalQuestSize * 0.1
: -(baseSize + totalInvasionSize + totalQuestSize) / popupYOffset) + popupY],
className: 'pokestop-marker',
diff --git a/src/components/markers/weather.jsx b/src/components/markers/weather.jsx
index b8a055e9c..f3ce373d9 100644
--- a/src/components/markers/weather.jsx
+++ b/src/components/markers/weather.jsx
@@ -3,22 +3,23 @@ import { renderToString } from 'react-dom/server'
import L from 'leaflet'
export default function weatherMarker(weather, Icons, isNight) {
- const { x, y } = Icons.getPopupOffset('weather')
+ const { offsetX, offsetY, popupX, popupY, sizeMultiplier, disableColorShift = false } = Icons.getModifiers('weather')
+
return L.divIcon({
- iconAnchor: [20, 20],
- popupAnchor: [-2.5 + x, -20 + y],
+ iconAnchor: [17 * offsetX, 17 * offsetY],
+ popupAnchor: [popupX + 1, -20 + popupY],
+ iconSize: [30 * sizeMultiplier, 30 * sizeMultiplier],
className: 'weather-icon',
html: renderToString(
-
-
-
,
+
,
),
})
}
diff --git a/src/components/popups/Gym.jsx b/src/components/popups/Gym.jsx
index 269fd12b9..927510c07 100644
--- a/src/components/popups/Gym.jsx
+++ b/src/components/popups/Gym.jsx
@@ -3,7 +3,7 @@ import React, {
Fragment, useState, useEffect,
} from 'react'
import {
- Grid, Typography, Icon, Collapse, IconButton, Divider,
+ Grid, Typography, Icon, Collapse, IconButton, Divider, Dialog,
} from '@material-ui/core'
import { ExpandMore, Map, MoreVert } from '@material-ui/icons'
import { useTranslation, Trans } from 'react-i18next'
@@ -16,9 +16,10 @@ import Utility from '@services/Utility'
import Title from './common/Title'
import Dropdown from './common/Dropdown'
import GenericTimer from './common/Timer'
+import BadgeSelection from '../layout/dialogs/BadgeSelection'
export default function GymPopup({
- gym, hasRaid, ts, Icons, hasHatched,
+ gym, hasRaid, ts, Icons, hasHatched, badge, setBadge,
}) {
const { t } = useTranslation()
const { perms } = useStatic(state => state.auth)
@@ -49,6 +50,8 @@ export default function GymPopup({
perms={perms}
hasRaid={hasRaid}
t={t}
+ badge={badge}
+ setBadge={setBadge}
/>
{perms.gyms && (
@@ -97,7 +100,7 @@ export default function GymPopup({
)}
-
{
const hideList = useStatic(state => state.hideList)
const setHideList = useStatic(state => state.setHideList)
@@ -131,6 +134,7 @@ const MenuActions = ({
const setFilters = useStore(state => state.setFilters)
const [anchorEl, setAnchorEl] = useState(false)
+ const [badgeMenu, setBadgeMenu] = useState(false)
const addWebhook = useWebhook({ category: 'quickGym', selectedWebhook })
const {
@@ -150,6 +154,11 @@ const MenuActions = ({
setHideList([...hideList, id])
}
+ const handleCloseBadge = (open) => {
+ setAnchorEl(null)
+ setBadgeMenu(open)
+ }
+
const excludeTeam = () => {
setAnchorEl(null)
const key = `t${team_id}-0`
@@ -206,6 +215,9 @@ const MenuActions = ({
if (perms.gyms) {
options.push({ name: 'exclude_team', action: excludeTeam })
+ if (perms.gymBadges) {
+ options.push({ name: 'gym_badge_menu', action: () => handleCloseBadge(true) })
+ }
}
if (perms.raids && hasRaid) {
options.push(
@@ -238,6 +250,14 @@ const MenuActions = ({
handleClose={handleClose}
options={options}
/>
+
)
}
@@ -282,10 +302,10 @@ const RaidImage = ({
: Icons.getEggs(raid_level, raid_battle_timestamp < ts, raid_is_exclusive)
const getRaidTypes = (id, form) => {
- if (pokemon[id].forms[form] && pokemon[id].forms[form].types) {
+ if (pokemon[id].forms?.[form]?.types) {
return pokemon[id].forms[form].types
}
- return pokemon[id].types
+ return pokemon[id]?.types || []
}
return (
@@ -507,7 +527,7 @@ const Timer = ({
) : null
}
-const Footer = ({
+const GymFooter = ({
gym, popups, setPopups, hasRaid, perms, Icons,
}) => {
const classes = useStyles()
diff --git a/src/components/popups/Pokemon.jsx b/src/components/popups/Pokemon.jsx
index 1c38b51f1..75d9eeade 100644
--- a/src/components/popups/Pokemon.jsx
+++ b/src/components/popups/Pokemon.jsx
@@ -292,7 +292,7 @@ const Info = ({
pokemon, metaData, perms, Icons, isNight,
}) => {
const { gender, weather, form } = pokemon
- const formTypes = metaData.forms[form].types || metaData.types
+ const formTypes = metaData?.forms?.[form]?.types || metaData?.types || []
return (
- {i ? `${weight.toFixed(2)}${t('kilogram')}` : `${size.toFixed(2)}${t('meter')}`}
+ {i ? `${weight ? weight.toFixed(2) : '? '}${t('kilogram')}` : `${size ? size.toFixed(2) : '? '}${t('meter')}`}
diff --git a/src/components/tiles/Gym.jsx b/src/components/tiles/Gym.jsx
index 98f545f76..537809272 100644
--- a/src/components/tiles/Gym.jsx
+++ b/src/components/tiles/Gym.jsx
@@ -1,5 +1,5 @@
/* eslint-disable camelcase */
-import React, { memo, useState, useRef } from 'react'
+import React, { memo, useState, useRef, useEffect } from 'react'
import { Marker, Popup, Circle } from 'react-leaflet'
import useMarkerTimer from '@hooks/useMarkerTimer'
@@ -25,6 +25,7 @@ const GymTile = ({
const markerRef = useRef({})
const [done, setDone] = useState(false)
const [stateChange, setStateChange] = useState(false)
+ const [badge, setBadge] = useState(item.badge || 0)
const {
raid_battle_timestamp, raid_end_timestamp, raid_level, raid_pokemon_id, raid_pokemon_form, team_id,
@@ -43,6 +44,14 @@ const GymTile = ({
useMarkerTimer(timerToDisplay, item.id, markerRef, '', ts, () => setStateChange(!stateChange))
useForcePopup(item.id, markerRef, params, setParams, done)
+ useEffect(() => {
+ if (filters.gymBadges) {
+ setBadge(item.badge || 0)
+ } else {
+ setBadge(0)
+ }
+ }, [filters.gymBadges, item.badge])
+
return !excludeList.includes(`t${team_id}-0`) && (
{
@@ -52,7 +61,7 @@ const GymTile = ({
}
}}
position={[item.lat, item.lon]}
- icon={gymMarker(item, hasHatched, hasRaid, filters, Icons, userSettings)}
+ icon={gymMarker(item, hasHatched, hasRaid, filters, Icons, userSettings, badge)}
>
{((showTimer || userSettings.raidTimers) && hasRaid) && (
)}
{showCircles && (
@@ -97,16 +108,18 @@ const areEqual = (prev, next) => {
&& prev.item.raid_pokemon_id === next.item.raid_pokemon_id
&& prev.item.raid_level === next.item.raid_level
&& prev.item.in_battle === next.item.in_battle
- && raidLogic()
- && prev.showTimer === next.showTimer
+ && prev.item.badge === next.item.badge
+ && (`badge_${prev.item.badge}` === next.filters.badge || next.filters.badge === 'all')
&& prev.item.team_id === next.item.team_id
&& prev.item.available_slots === next.item.available_slots
+ && raidLogic()
+ && prev.showTimer === next.showTimer
+ && prev.showCircles === next.showCircles
&& !next.excludeList.includes(`${prev.item.raid_pokemon_id}-${prev.item.raid_pokemon_form}`)
&& !next.excludeList.includes(`t${prev.item.team_id}-0`)
&& !next.excludeList.includes(`e${prev.item.raid_level}`)
&& Object.keys(prev.userIcons).every(key => prev.userIcons[key] === next.userIcons[key])
&& Object.keys(prev.userSettings).every(key => prev.userSettings[key] === next.userSettings[key])
- && prev.showCircles === next.showCircles
}
export default memo(GymTile, areEqual)
diff --git a/src/components/tiles/Pokestop.jsx b/src/components/tiles/Pokestop.jsx
index e9fe0f171..c251642c0 100644
--- a/src/components/tiles/Pokestop.jsx
+++ b/src/components/tiles/Pokestop.jsx
@@ -65,7 +65,7 @@ const PokestopTile = ({
/>
{Boolean(timers.length) && (
-
+
)}
{showCircles && (
({
setUserProfile: (userProfile) => set({ userProfile }),
feedback: false,
setFeedback: (feedback) => set({ feedback }),
+ resetFilters: false,
+ setResetFilters: (resetFilters) => set({ resetFilters }),
}))
export { useStore, useStatic }
diff --git a/src/index.jsx b/src/index.jsx
index c8e924356..098776d54 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -24,6 +24,14 @@ if (process.env) {
tracesSampleRate: SENTRY_TRACES_SAMPLE_RATE || 0.1,
release: VERSION,
environment: isDevelopment ? 'development' : 'production',
+ debug: true,
+ beforeSend(event) {
+ if (event?.exception?.values?.[0]?.stacktrace?.frames?.some(f => f.filename.includes('node_modules'))) {
+ // do nothing for external libraries
+ return
+ }
+ return event
+ },
})
// eslint-disable-next-line no-console
console.log('ReactMap Version:', VERSION)
diff --git a/src/services/Icons.js b/src/services/Icons.js
index 3aebd3b50..a73a811ac 100644
--- a/src/services/Icons.js
+++ b/src/services/Icons.js
@@ -137,10 +137,10 @@ export default class UIcons {
: baseSize
}
- getPopupOffset(category) {
+ getModifiers(category) {
return this.modifiers[category]
- ? { x: this.modifiers[category].popupX || 0, y: this.modifiers[category].popupY || 0 }
- : { x: 0, y: 0 }
+ ? this.modifiers[category]
+ : this.modifiers.base
}
getIconById(id) {
diff --git a/src/services/Query.js b/src/services/Query.js
index b339a3643..a4083980f 100644
--- a/src/services/Query.js
+++ b/src/services/Query.js
@@ -23,6 +23,9 @@ export default class Query {
if (filters === 'id') {
return gymIndex.getOne
}
+ if (filters === 'badges') {
+ return gymIndex.getBadges
+ }
const permObj = {
Gyms: filters.raids ? filters.allGyms || perms.allGyms : filters.allGyms && perms.allGyms,
Raids: filters.raids && perms.raids,
@@ -32,7 +35,7 @@ export default class Query {
if (permObj[keyPerm]) query += keyPerm
})
if (query === 'get'
- && (filters.exEligible || filters.inBattle || filters.arEligible)) {
+ && (filters.exEligible || filters.inBattle || filters.arEligible || filters.gymBadges)) {
query += 'Gyms'
}
diff --git a/src/services/apollo.js b/src/services/apollo.js
index d280ac2b0..8e4a18c8f 100644
--- a/src/services/apollo.js
+++ b/src/services/apollo.js
@@ -10,7 +10,12 @@ export default new ApolloClient({
typePolicies: {
Query: {
fields: {
- pokemon: {
+ badges: {
+ merge(existing, incoming) {
+ return incoming
+ },
+ },
+ devices: {
merge(existing, incoming) {
return incoming
},
@@ -20,11 +25,33 @@ export default new ApolloClient({
return incoming
},
},
+ nests: {
+ badges: {
+ merge(existing, incoming) {
+ return incoming
+ },
+ },
+ },
+ pokemon: {
+ merge(existing, incoming) {
+ return incoming
+ },
+ },
pokestops: {
merge(existing, incoming) {
return incoming
},
},
+ portals: {
+ merge(existing, incoming) {
+ return incoming
+ },
+ },
+ spawnpoints: {
+ merge(existing, incoming) {
+ return incoming
+ },
+ },
},
},
SearchQuest: {
diff --git a/src/services/queries/gym.js b/src/services/queries/gym.js
index 8c3a78130..5f25bb77a 100644
--- a/src/services/queries/gym.js
+++ b/src/services/queries/gym.js
@@ -21,6 +21,7 @@ const gym = gql`
in_battle
guarding_pokemon_id
total_cp
+ badge
}
`
@@ -82,3 +83,17 @@ export const getOne = gql`
}
}
`
+
+export const getBadges = gql`
+ query GetBadgeInfo {
+ badges {
+ id
+ name
+ url
+ lat
+ lon
+ badge
+ deleted
+ }
+ }
+`
diff --git a/src/services/queries/user.js b/src/services/queries/user.js
index cbcb750fe..ee0d58d29 100644
--- a/src/services/queries/user.js
+++ b/src/services/queries/user.js
@@ -17,3 +17,9 @@ mutation SetUsername($username: String!) {
checkUsername(username: $username)
}
`
+
+export const setGymBadge = gql`
+mutation SetGymBadge($gymId: String!, $badge: Int!) {
+ setGymBadge(gymId: $gymId, badge: $badge)
+ }
+`
diff --git a/yarn.lock b/yarn.lock
index 437025ffb..536ddebd1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5324,17 +5324,19 @@ node-emoji@^1.10.0:
dependencies:
lodash.toarray "^4.4.0"
-node-fetch@2.6.1, node-fetch@^2.6.1:
+node-fetch@2:
+ version "2.6.7"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
+ integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
-node-fetch@^2.6.0:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.2.tgz#986996818b73785e47b1965cc34eb093a1d464d0"
- integrity sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==
-
-node-fetch@^2.6.5:
+node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.5:
version "2.6.6"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
integrity sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==