diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css index bc471f7..107281b 100644 --- a/app/assets/stylesheets/application.tailwind.css +++ b/app/assets/stylesheets/application.tailwind.css @@ -4,3 +4,41 @@ @tailwind components; @tailwind utilities; +.loading { + position: relative; +} + +.loading::after { + content: ""; + position: absolute; + top: 50%; + right: 1rem; + transform: translateY(-50%); + width: 1rem; + height: 1rem; + border: 2px solid transparent; + border-top-color: currentColor; + border-right-color: currentColor; + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: translateY(-50%) rotate(360deg); + } +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; + transform-origin: center center; +} \ No newline at end of file diff --git a/app/controllers/cities_controller.rb b/app/controllers/cities_controller.rb index a89ab26..4949bac 100644 --- a/app/controllers/cities_controller.rb +++ b/app/controllers/cities_controller.rb @@ -15,18 +15,17 @@ class CitiesController < ApplicationController @cities = @cities.by_region(@current_region.id) if @current_region end - if params[:country] - @current_country = Country.friendly.find(params[:country]) - @cities = @cities.by_country(@current_country.id) - end - @cities = @cities.page(params[:page]).per(12) - set_meta_tags( - title: @current_region ? "Cities in #{@current_region.name}" : "Explore Cities", - description: "Discover weather art for cities #{@current_region ? "in #{@current_region.name}" : 'worldwide'}. Real-time AI-generated weather visualization.", - keywords: "#{@current_region&.name}, cities, weather art, AI visualization" - ) + respond_to do |format| + format.html + format.turbo_stream { + render turbo_stream: turbo_stream.update("cities_results", + partial: "cities/results", + locals: { cities: @cities } + ) + } + end end def show diff --git a/app/javascript/controllers/search_controller.js b/app/javascript/controllers/search_controller.js index bcfed0e..2454f0a 100644 --- a/app/javascript/controllers/search_controller.js +++ b/app/javascript/controllers/search_controller.js @@ -2,12 +2,71 @@ import { Controller } from "@hotwired/stimulus" export default class extends Controller { - static targets = ["input"] + static targets = ["input", "clearButton", "spinner", "statusIcon"] + + connect() { + this.pendingRequest = null + } submit() { clearTimeout(this.timeout) this.timeout = setTimeout(() => { - this.element.requestSubmit() + // 如果有待处理的请求,则中止它 + if (this.pendingRequest) { + this.pendingRequest.abort() + } + + const form = this.element + const searchInput = this.inputTarget + const encodedValue = encodeURIComponent(searchInput.value) + + // 更新 URL + const url = new URL(window.location) + url.searchParams.set('query', encodedValue) + window.history.pushState({}, '', url) + + // 显示加载状态 + this.showLoadingState() + + // 发送请求 + this.pendingRequest = new AbortController() + fetch(form.action + '?' + new URLSearchParams(new FormData(form)), { + headers: { + 'Accept': 'text/vnd.turbo-stream.html', + 'Turbo-Frame': 'cities_results' + }, + signal: this.pendingRequest.signal + }) + .then(response => response.text()) + .then(html => { + Turbo.renderStreamMessage(html) + }) + .catch(error => { + if (error.name === 'AbortError') return + console.error('Search error:', error) + }) + .finally(() => { + this.hideLoadingState() + this.pendingRequest = null + }) }, 300) } + + showLoadingState() { + if (this.hasClearButtonTarget) { + this.clearButtonTarget.classList.add('hidden') + } + if (this.hasSpinnerTarget) { + this.spinnerTarget.classList.remove('hidden') + } + } + + hideLoadingState() { + if (this.hasClearButtonTarget) { + this.clearButtonTarget.classList.remove('hidden') + } + if (this.hasSpinnerTarget) { + this.spinnerTarget.classList.add('hidden') + } + } } \ No newline at end of file diff --git a/app/views/cities/_results.html.erb b/app/views/cities/_results.html.erb new file mode 100644 index 0000000..897237b --- /dev/null +++ b/app/views/cities/_results.html.erb @@ -0,0 +1,24 @@ + +
+ <% if cities.empty? %> +
+
+ + + +

<%= t('.no_results_title') %>

+

+ <%= t('.no_results_message') %> +

+
+
+ <% else %> +
+ <%= render partial: 'city', collection: cities %> +
+ + <%= render 'shared/pagination', + collection: cities, + collection_name: 'cities' %> + <% end %> +
\ No newline at end of file diff --git a/app/views/cities/_search_city.html.erb b/app/views/cities/_search_city.html.erb index 9264924..1c72ed1 100644 --- a/app/views/cities/_search_city.html.erb +++ b/app/views/cities/_search_city.html.erb @@ -1,27 +1,49 @@ -
- <%= form_with url: cities_path, method: :get, class: "relative", data: { controller: "search" } do |f| %> +
+ <%= form_with url: cities_path, method: :get, + class: "relative", + data: { + controller: "search", + turbo_frame: "cities_results", + turbo_action: "advance" + } do |f| %>
-
- + +
+
+ + <%= f.text_field :query, value: params[:query] ? URI.decode_www_form_component(params[:query]) : nil, - class: "w-full pl-12 pr-4 py-3 rounded-full bg-base-200/50 backdrop-blur-sm border border-base-300 focus:outline-none focus:ring-2 focus:ring-primary/50", + class: "w-full pl-12 pr-12 py-3 rounded-full bg-base-200/80 backdrop-blur border border-base-300 focus:outline-none focus:ring-2 focus:ring-primary/50 transition", placeholder: "Search cities...", autocomplete: "off", data: { action: "input->search#submit", search_target: "input" } %> - <% if params[:query].present? %> - <%= link_to cities_path, class: "absolute inset-y-0 right-0 flex items-center pr-4" do %> - - - + + + +
+ <% if params[:query].present? %> + <%= link_to cities_path, + class: "text-base-content/50 hover:text-base-content transition", + data: { search_target: "clearButton" } do %> + + + + <% end %> <% end %> - <% end %> + + +
+
<%= f.hidden_field :region, value: params[:region] if params[:region] %> diff --git a/app/views/cities/index.html.erb b/app/views/cities/index.html.erb index b80509e..07782c9 100644 --- a/app/views/cities/index.html.erb +++ b/app/views/cities/index.html.erb @@ -22,8 +22,6 @@ Discover AI-generated weather art from cities around the world

- <%= render 'cities/search_city' %> - <% if featured_art %>
@@ -34,11 +32,15 @@ <%= featured_art.weather_date.strftime("%B %d, %Y") %>
<% end %> + <%= render 'cities/search_city' %> +
+ +
@@ -113,34 +115,8 @@
+ <%= turbo_frame_tag "cities_results" do %> + <%= render "cities/results", cities: @cities %> + <% end %> - -
-
- - <% if @cities.empty? %> -
-
- - - -

No cities found

-

- Try adjusting your search or filters to find what you're looking for. -

-
-
- <% else %> - -
- <%= render partial: 'city', collection: @cities %> -
- <% end %> - - <%= render 'shared/pagination', - collection: @cities, - collection_name: 'cities' %> -
- -
\ No newline at end of file