Skip to content

Commit

Permalink
Global search: Release candidate
Browse files Browse the repository at this point in the history
  • Loading branch information
JanMikes committed Jan 28, 2025
1 parent 4bec7ef commit 99794af
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 144 deletions.
39 changes: 17 additions & 22 deletions assets/controllers/global_search_controller.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static targets = ["searchBar", "searchResults"];
static targets = ["searchInput"];

connect() {
this.ignoreNextClick = false; // Prevents conflict on initial click
this.handleOutsideClick = this.handleOutsideClick.bind(this);
}

toggleSearchBar(event) {
event.preventDefault(); // Prevent default link behavior
event.preventDefault(); // Prevent default anchor behavior

if (this.isSearchBarOpen()) {
this.closeSearchBar();
Expand All @@ -19,23 +19,19 @@ export default class extends Controller {
}

openSearchBar() {
// Add "search-overlay" class to <body>
document.body.classList.add("search-overlay");
// Remove "not-shown" class from the search bar
this.searchBarTarget.classList.remove("not-shown");
// Ignore the next click to avoid immediate close
this.ignoreNextClick = true;
// Add event listener to detect clicks outside the search bar
document.addEventListener("click", this.handleOutsideClick);
document.body.classList.add("global-search-shown"); // Add class to <body>
this.searchInputTarget.focus(); // Focus the input field
this.ignoreNextClick = true; // Prevent immediate close
document.addEventListener("click", this.handleOutsideClick); // Add outside click listener
}

closeSearchBar() {
// Remove "search-overlay" class from <body>
document.body.classList.remove("search-overlay");
// Add "not-shown" class to the search bar
this.searchBarTarget.classList.add("not-shown");
// Remove event listener for outside clicks
document.removeEventListener("click", this.handleOutsideClick);
closeSearchBar(event) {
if (event) {
event.preventDefault(); // Prevent default behavior
}

document.body.classList.remove("global-search-shown"); // Remove class from <body>
document.removeEventListener("click", this.handleOutsideClick); // Remove outside click listener
}

handleOutsideClick(event) {
Expand All @@ -44,16 +40,15 @@ export default class extends Controller {
return;
}

const isClickInsideSearch =
this.searchBarTarget.contains(event.target) ||
this.searchResultsTargets.some((target) => target.contains(event.target));
const clickedOverlay = event.target.classList.contains("global-search-overlay");

if (!isClickInsideSearch) {
// Close if the click is on the overlay or outside the search elements
if (clickedOverlay || !event.target.closest(".global-search")) {
this.closeSearchBar();
}
}

isSearchBarOpen() {
return !this.searchBarTarget.classList.contains("not-shown");
return document.body.classList.contains("global-search-shown");
}
}
56 changes: 27 additions & 29 deletions assets/styles/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ td.rank {

.navbar-brand {
padding: 4px 0;
margin-right: 0 !important;
}

@include media-breakpoint-up(lg) {
Expand Down Expand Up @@ -602,21 +603,32 @@ canvas {
border-radius: 0 !important;
}

body::before {
content: '';
opacity: 0;
}

body.search-overlay::before {
.global-search-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
z-index: 5;
background-color: rgba(0, 0, 0, 0.6);
z-index: 10;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease-in-out;
opacity: 1;
}

.global-search-shown {
overflow: hidden;

.global-search-overlay {
opacity: 1;
pointer-events: all;
}

.global-search {
opacity: 1;
transform: translateX(-50%) translateY(0);
pointer-events: all;
}
}

.global-search {
Expand All @@ -631,37 +643,23 @@ body.search-overlay::before {
opacity: 0;
margin: 0 auto;
transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
pointer-events: none;

.input-group {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
border-radius: 5px;
}

&.not-shown {
display: block; /* Keep block but hidden with opacity and transform */
opacity: 0;
transform: translateX(-50%) translateY(-20px); /* Hide by moving up */
}
}

body.search-overlay .global-search {
opacity: 1;
transform: translateX(-50%) translateY(0); /* Slide to original position */
}

.global-search-results {
z-index: 20;
width: 100%;
margin: 15px 0 0;
background: #f9f9f9;
background: #fff;
padding: 15px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
border-radius: 8px;
opacity: 0;
transform: translateY(-10px);
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out;

body.search-overlay & {
opacity: 1;
transform: translateY(0);
}
max-height: calc(100vh - 210px);
overflow-y: auto; /* Enable vertical scrolling */
overflow-x: hidden; /* Disable horizontal scrolling (optional) */
}
19 changes: 9 additions & 10 deletions src/Query/SearchPlayers.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,19 @@ public function fulltext(string $search, null|int $limit = null): array
country AS player_country,
code AS player_code,
CASE
WHEN LOWER(name) = LOWER(:searchQuery) OR LOWER(code) = LOWER(:searchQuery) THEN 5 -- Exact match with diacritics
WHEN LOWER(unaccent(name)) = LOWER(unaccent(:searchQuery)) OR LOWER(unaccent(code)) = LOWER(unaccent(:searchQuery)) THEN 4 -- Exact match without diacritics
WHEN LOWER(name) LIKE LOWER(:searchEndLikeQuery) OR LOWER(name) LIKE LOWER(:searchStartLikeQuery) OR LOWER(code) LIKE LOWER(:searchEndLikeQuery) OR LOWER(code) LIKE LOWER(:searchStartLikeQuery) THEN 3 -- Starts or ends with the query with diacritics
WHEN LOWER(unaccent(name)) LIKE LOWER(unaccent(:searchEndLikeQuery)) OR LOWER(unaccent(name)) LIKE LOWER(unaccent(:searchStartLikeQuery)) OR LOWER(unaccent(code)) LIKE LOWER(unaccent(:searchEndLikeQuery)) OR LOWER(unaccent(code)) LIKE LOWER(unaccent(:searchStartLikeQuery)) THEN 2 -- Starts or ends with the query without diacritics
WHEN LOWER(name) LIKE LOWER(:searchFullLikeQuery) OR LOWER(code) LIKE LOWER(:searchFullLikeQuery) THEN 1 -- Partial match with diacritics
ELSE 0 -- Partial match without diacritics or any other case
WHEN LOWER(code) = LOWER(:searchQuery) THEN 6 -- Exact match on code with diacritics
WHEN LOWER(name) = LOWER(:searchQuery) THEN 5 -- Exact match on name with diacritics
WHEN LOWER(unaccent(code)) = LOWER(unaccent(:searchQuery)) THEN 4 -- Exact match on code without diacritics
WHEN LOWER(unaccent(name)) = LOWER(unaccent(:searchQuery)) THEN 3 -- Exact match on name without diacritics
WHEN LOWER(code) LIKE LOWER(:searchFullLikeQuery) THEN 3 -- Partial match on code with diacritics
WHEN LOWER(name) LIKE LOWER(:searchFullLikeQuery) THEN 2 -- Partial match on name with diacritics
WHEN LOWER(unaccent(code)) LIKE LOWER(unaccent(:searchFullLikeQuery)) THEN 2 -- Partial match on code without diacritics
WHEN LOWER(unaccent(name)) LIKE LOWER(unaccent(:searchFullLikeQuery)) THEN 1 -- Partial match on name without diacritics
ELSE 0 -- Other cases
END as match_score
FROM player
WHERE LOWER(name) LIKE LOWER(:searchFullLikeQuery) OR LOWER(code) LIKE LOWER(:searchFullLikeQuery)
OR LOWER(unaccent(name)) LIKE LOWER(unaccent(:searchFullLikeQuery)) OR LOWER(unaccent(code)) LIKE LOWER(unaccent(:searchFullLikeQuery))
OR LOWER(name) = LOWER(:searchQuery) OR LOWER(code) = LOWER(:searchQuery)
OR LOWER(unaccent(name)) = LOWER(unaccent(:searchQuery)) OR LOWER(unaccent(code)) = LOWER(unaccent(:searchQuery))
OR LOWER(name) LIKE LOWER(:searchEndLikeQuery) OR LOWER(name) LIKE LOWER(:searchStartLikeQuery) OR LOWER(code) LIKE LOWER(:searchEndLikeQuery) OR LOWER(code) LIKE LOWER(:searchStartLikeQuery)
OR LOWER(unaccent(name)) LIKE LOWER(unaccent(:searchEndLikeQuery)) OR LOWER(unaccent(name)) LIKE LOWER(unaccent(:searchStartLikeQuery)) OR LOWER(unaccent(code)) LIKE LOWER(unaccent(:searchEndLikeQuery)) OR LOWER(unaccent(code)) LIKE LOWER(unaccent(:searchStartLikeQuery))
ORDER BY match_score DESC
SQL;

Expand Down
3 changes: 0 additions & 3 deletions src/Query/SearchPuzzle.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ public function countByUserInput(

$count = $this->database
->executeQuery($query, [
'searchQuery' => $search,
'searchStartLikeQuery' => "%$search",
'searchEndLikeQuery' => "$search%",
'searchFullLikeQuery' => "%$search%",
'brandId' => $brandId,
'minPieces' => $pieces->minPieces(),
Expand Down
21 changes: 12 additions & 9 deletions templates/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,11 @@
<!-- Toolbar -->
<div class="navbar-toolbar d-flex align-items-center order-lg-3">
{% if logged_user.profile and logged_user.profile.isAdmin %}
<a class="navbar-tool ms-1 ms-lg-0 me-n1 me-lg-2" data-action="click->global-search#openSearchBar" href="#">
<a class="navbar-tool ms-1 ms-lg-0 me-n1 me-lg-2" data-action="click->global-search#toggleSearchBar" href="#">
<div class="navbar-tool-icon-box">
<i class="navbar-tool-icon ci-search"></i>
</div>
<div class="navbar-tool-text ms-n3"><small>Search</small></div>
</a>
{% endif %}

Expand All @@ -123,13 +124,6 @@
<div class="navbar-tool-text ms-n3"><small>{{ 'menu.add_time'|trans }}</small></div>
</a>

<a class="navbar-tool ms-1 ms-lg-0 me-n1 me-lg-2 {% if app.request.pathInfo == path('stopwatch') %}active{% endif %}" rel="nofollow" href="{{ path('stopwatch') }}">
<div class="navbar-tool-icon-box">
<i class="navbar-tool-icon ci-time"></i>
</div>
<div class="navbar-tool-text ms-n3"><small>{{ 'menu.stopwatch'|trans }}</small></div>
</a>

{% set notifications_count = notifications_count|default(null) ?: logged_user.profile is not null ? get_notifications.countUnreadForPlayer(logged_user.profile.playerId) :0 %}

<a class="navbar-tool ms-1 ms-lg-0 me-1 me-lg-2 {% if app.request.pathInfo == path('notifications') %}active{% endif %}" rel="nofollow" href="{{ path('notifications') }}" data-turbo-prefetch="false">
Expand Down Expand Up @@ -173,12 +167,19 @@
{{ 'menu.players'|trans }}
</a>
</li>
<li class="nav-item">
<a rel="nofollow" class="nav-link {% if app.request.pathInfo == path('stopwatch') %}active{% endif %}" href="{{ path('stopwatch') }}">
<i class="me-1 me-lg-0 ci-time"></i>
{{ 'menu.stopwatch'|trans }}
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if app.request.pathInfo == path('faq') %}active{% endif %}" href="{{ path('faq') }}">
<i class="me-1 me-lg-0 bi bi-patch-question"></i>
{{ 'menu.faq'|trans }}
</a>
</li>

{# <li class="nav-item"><a class="nav-link {% if app.request.pathInfo == path('scan') %}active{% endif %}" href="{{ path('scan') }}">{{ 'menu.scan'|trans }}<span class="ms-2 badge rounded-pill bg-accent">beta</span></a></li> #}
</ul>
</div>
Expand All @@ -187,7 +188,7 @@
</header>
<main>
{% if logged_user.profile and logged_user.profile.isAdmin %}
<div data-global-search-target="searchBar" class="not-shown global-search">
<div data-global-search-target="searchBar" class="global-search">
<twig:GlobalSearch />
</div>
{% endif %}
Expand Down Expand Up @@ -416,5 +417,7 @@
{% endif %}

{{ encore_entry_script_tags('app') }}

<div class="global-search-overlay" data-global-search-target="overlay"></div>
</body>
</html>
Loading

0 comments on commit 99794af

Please sign in to comment.