Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion app/assets/stylesheets/nav.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,26 @@
@apply text-secondary;
}


.nav--search--input {
@apply pl-10 p-1 w-full rounded border-none bg-opacity-0 bg-white transition-colors focus:bg-opacity-90 focus:border focus:border-gray-300 focus:outline-none outline-offset-0 !important;
}

.nav--search--input:has(+ .nav--search--results) {
@apply bg-opacity-90 border border-gray-300;
}

.nav--search--frame {
display: contents;
}

.nav--search--results {
@apply absolute w-[300px] top-0 bg-white mt-8 z-40 shadow-md pt-2;
}

.nav--search--result {
@apply p-2;
}

.nav--logo {
@apply flex h-full items-center self-start;
background: url("/assets/logo.png") no-repeat;
Expand Down
16 changes: 11 additions & 5 deletions app/controllers/search_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
class SearchController < ApplicationController
def index
q = params[:q]
q = params.fetch(:q, "")

if q.blank? || q.length < 3
if !turbo_frame_request? && q.length < 3
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Для превью тоже имеет смысл подождать, пока пользователь введёт первые символы; ограничение можно "продублировать" на клиенте через Values в контроллере, например (или вообще через стандартные HTML атрибуты).

return redirect_back(fallback_location: root_path, alert: "Please, enter at least 3 characters")
end

@artists = Artist.search(q).limit(10)
@albums = Album.search(q).limit(10)
@tracks = Track.search(q).limit(10)
artists = Artist.search(q).limit(10)
albums = Album.search(q).limit(10)
tracks = Track.search(q).limit(10)

if turbo_frame_request?
render partial: "preview", locals: {artists:, albums:, tracks:}
else
render action: :index, locals: {artists:, albums:, tracks:}
end
end
end
81 changes: 81 additions & 0 deletions app/javascript/controllers/live_search_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Controller } from "@hotwired/stimulus";

const buildUrlWithQuery = (baseUrl, queryObj = {}) => {
const queryParams = new URLSearchParams(queryObj);
if (queryParams.toString().length === 0) return baseUrl;

return `${baseUrl}?${queryParams.toString()}`;
};

export default class extends Controller {
static values = {
debounceTimeout: { type: Number, default: 300 },
previewFrameId: String,
searchPath: String,
searchParamName: { type: String, default: "q" },
};

initialize() {
this.previewFrame = document.getElementById(this.previewFrameIdValue);
this.onFrameUpdate = this.onFrameUpdate.bind(this);
}

connect() {
this.previewFrame.addEventListener("turbo:frame-load", this.onFrameUpdate);
}

disconnect() {
this.previewFrame.removeEventListener(
"turbo:frame-load",
this.onFrameUpdate,
);
}

get query() {
return this.element.value;
}

get searchUrl() {
return buildUrlWithQuery(this.searchPathValue, {
[this.searchParamNameValue]: this.query,
});
Comment on lines +39 to +41
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот это красиво. Вроде и запрос конструируем сами, а вроде и всё контролируется извне.

}

search() {
if (this.timer) clearTimeout(this.timer);

this.timer = setTimeout(() => {
this.updateFrame();
}, this.debounceTimeoutValue);
}

updateFrame() {
if (this.query.length >= 3) {
this.reloadFrame();
} else {
this.resetFrame();
}
}

reloadFrame() {
this.previewFrame.src = this.searchUrl;
this.previewFrame.reload();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А разве нужен отдельный reload()? Turbo Frame, на сколько я помню, отслеживает изменения src и сам обновляется.

}

resetFrame() {
this.previewFrame.src = "";
this.previewFrame.innerHTML = "";
}

onFrameUpdate() {
if (this.navigationalElements) {
this.navigationalElements.forEach((el) =>
el.removeEventListener("turbo:click", this.resetFrame),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хорошая идея — использовать turbo:click. Но навешивать на каждую ссылку не обязательно, можно было добавить только на контейнер (или даже в data-action прописать).

);
}
this.navigationalElements = this.previewFrame.querySelectorAll("a");
this.navigationalElements.forEach((link) =>
link.addEventListener("turbo:click", this.resetFrame),
);
}
}
2 changes: 1 addition & 1 deletion app/models/album.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ class Album < ApplicationRecord
normalizes :title, with: -> { _1.squish }

scope :search, ->(q) {
where(arel_table[:title].lower.matches("%#{q.downcase}%"))
q.blank? ? none : where(arel_table[:title].lower.matches("%#{q.downcase}%"))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше так (надо использовать фишки скоупов):

Suggested change
q.blank? ? none : where(arel_table[:title].lower.matches("%#{q.downcase}%"))
where(arel_table[:title].lower.matches("%#{q.downcase}%")) if q.present?

}
end
2 changes: 1 addition & 1 deletion app/models/artist.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ class Artist < ApplicationRecord
where("EXISTS (SELECT 1 FROM json_each(artists.tags) WHERE value = ?)", tag)
}
scope :search, ->(q) {
where(arel_table[:name].lower.matches("%#{q.downcase}%"))
q.blank? ? none : where(arel_table[:name].lower.matches("%#{q.downcase}%"))
}
end
2 changes: 1 addition & 1 deletion app/models/track.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ class Track < ApplicationRecord
scope :ordered, -> { order(position: :asc) }
scope :popularity_ordered, -> { order(listenings_count: :desc) }
scope :search, ->(q) {
where(arel_table[:title].lower.matches("%#{q.downcase}%"))
q.blank? ? none : where(arel_table[:title].lower.matches("%#{q.downcase}%"))
}
end
5 changes: 5 additions & 0 deletions app/views/search/_album.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%# locals: (album:, album_counter: nil, album_iteration: nil) -%>
<li class="nav--search--result">
<%= link_to album.title, album_path(album), class: "hover:text-secondary", target: "_top" %>
<span class="ml-2 text-primary text-xsmall"><%= album.artist.name %></span>
</li>
4 changes: 4 additions & 0 deletions app/views/search/_artist.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<%# locals: (artist:, artist_counter: nil, artist_iteration: nil) -%>
<li class="nav--search--result">
<%= link_to artist.name, artist_path(artist), class: "hover:text-secondary", target: "_top" %>
</li>
10 changes: 10 additions & 0 deletions app/views/search/_preview.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<%# locals: (artists: [], albums: [], tracks: []) -%>
<%= turbo_frame_tag :search_results, class: "nav--search--frame" do %>
<% if [artists, albums, tracks].any?(&:present?) %>
<ul class="nav--search--results">
<%= render collection: artists, partial: "artist" %>
<%= render collection: albums, partial: "album" %>
<%= render collection: tracks, partial: "track" %>
</ul>
<% end %>
<% end %>
5 changes: 5 additions & 0 deletions app/views/search/_track.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%# locals: (track:, track_counter: nil, track_iteration: nil) -%>
<li class="nav--search--result">
<%= link_to track.title, play_track_path(track), class: "hover:text-primary", data: {"turbo-frame" => "player"} %>
<span class="ml-2 text-secondary text-xsmall"><%= track.album.artist.name %></span>
</li>
13 changes: 7 additions & 6 deletions app/views/search/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
<%# locals: (artists: [], albums: [], tracks: []) -%>
<h1 class="mt-4 text-header-2">Search results for: <%= params[:q] %></h1>

<h2 class="mt-4 text-header-3">Artists</h2>
<% if @artists.any? %>
<% if artists.any? %>
<div class="albums-row">
<%= render @artists %>
<%= render artists %>
</div>
<% else %>
<span class="text-small text-gray-600">None</span>
<% end %>

<h2 class="mt-4 text-header-3">Albums</h2>
<% if @albums.any? %>
<% if albums.any? %>
<div class="albums-grid">
<%= render @albums %>
<%= render albums %>
</div>
<% else %>
<span class="text-small text-gray-600">None</span>
<% end %>

<h2 class="mt-4 text-header-3">Tracks</h2>
<% if @tracks.any? %>
<% if tracks.any? %>
<ul class="tracks mt-2">
<%= render @tracks, enqueueble: true %>
<%= render tracks, enqueueble: true %>
</ul>
<% else %>
<span class="text-small text-gray-600">None</span>
Expand Down
22 changes: 15 additions & 7 deletions app/views/shared/_nav.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,26 @@
<% end %>
<% end %>
<%- end -%>
<%= form_with(url: search_path, method: :get, class: "nav--search") do |f| %>
<div class="nav--search--icon">
<%= render "icons/search" %>
</div>
<%= f.search_field :q, value: params[:q], class: "nav--search--input" %>
<% end %>
<div class="nav--search">
<%= form_with(url: search_path, method: :get) do |f| %>
<div class="nav--search--icon">
<%= render "icons/search" %>
</div>
<%= f.search_field :q, class: "nav--search--input", data: {
controller: "live-search",
action: "live-search#search",
"live-search-search-path-value": search_path,
"live-search-preview-frame-id-value": :search_results,
} %>
<% end %>
<%= turbo_frame_tag :search_results, class: "nav--search--frame", src: search_path %>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А нужен ли тут search_path? Кажется, он будет делать запрос при начальной загрузке страницы.

</div>
</div>
<div class="nav--right">
<%- if current_user -%>
<span class="nav--username"><%= User::PREFIX + current_user.username %></span>
<%= button_to "Sign out", sign_out_path, method: :delete, class: "nav--btn--outlined ml-2" %>
<%- else- %>
<%- else -%>
<%= link_to "Sign up", sign_up_path, class: "nav--btn--outlined" %>
<%= link_to "Log in", sign_in_path, class: "nav--btn ml-2" %>
<%- end -%>
Expand Down
1 change: 1 addition & 0 deletions config/importmap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
pin "fake_audio", to: "fake_audio.js"
pin "@hotwired/stimulus", to: "https://ga.jspm.io/npm:@hotwired/stimulus@3.2.1/dist/stimulus.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin "stimulus-use", to: "https://ga.jspm.io/npm:stimulus-use@0.52.0/dist/index.js"
pin_all_from "app/javascript/controllers", under: "controllers"