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 => ( + + ))} + + +