diff --git a/Gemfile b/Gemfile index efc05ba..6fcd84a 100644 --- a/Gemfile +++ b/Gemfile @@ -42,6 +42,8 @@ gem "thor" gem "imgproxy" +gem "pagy" + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[mri windows] diff --git a/Gemfile.lock b/Gemfile.lock index 9945c89..7a2cde5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -153,6 +153,7 @@ GEM racc (~> 1.4) nokogiri (1.16.4-x86_64-linux) racc (~> 1.4) + pagy (8.4.4) parallel (1.24.0) parser (3.3.1.0) ast (~> 2.4.1) @@ -301,6 +302,7 @@ DEPENDENCIES erb-formatter imgproxy jsbundling-rails + pagy pry-rails puma (>= 5.0) rails (~> 7.1.3, >= 7.1.3.2) diff --git a/app/assets/builds/application.css b/app/assets/builds/application.css index c2ee331..b458db5 100644 --- a/app/assets/builds/application.css +++ b/app/assets/builds/application.css @@ -1 +1 @@ -/*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.mt-2{margin-top:.5rem}.inline{display:inline}.hidden{display:none}.h-12{height:3rem}.h-8{height:2rem}.max-w-md{max-width:28rem}.resize{resize:both}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-opacity-50{--tw-bg-opacity:0.5}.bg-opacity-100{--tw-bg-opacity:1}.bg-opacity-20{--tw-bg-opacity:0.2}.bg-opacity-30{--tw-bg-opacity:0.3}.p-3{padding:.75rem}.text-left{text-align:left}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.font-bold{font-weight:700}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.bg-blend-screen{background-blend-mode:screen}.bg-blend-overlay{background-blend-mode:overlay}.bg-blend-difference{background-blend-mode:difference}.invert{--tw-invert:invert(100%)}.filter,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-blur-lg,.backdrop-blur-sm{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-lg{--tw-backdrop-blur:blur(16px)} \ No newline at end of file +/*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.mt-2{margin-top:.5rem}.inline{display:inline}.hidden{display:none}.h-8{height:2rem}.max-w-md{max-width:28rem}.resize{resize:both}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-opacity-20{--tw-bg-opacity:0.2}.p-3{padding:.75rem}.text-left{text-align:left}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.font-bold{font-weight:700}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.invert{--tw-invert:invert(100%)}.filter,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-lg{--tw-backdrop-blur:blur(16px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)} \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d1..98ccf6f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,3 @@ class ApplicationController < ActionController::Base + include Pagy::Backend end diff --git a/app/controllers/works_controller.rb b/app/controllers/works_controller.rb index 85d8389..0cbe75a 100644 --- a/app/controllers/works_controller.rb +++ b/app/controllers/works_controller.rb @@ -2,7 +2,9 @@ class WorksController < ApplicationController before_action :authenticate, only: [:show, :index] def index - @work = random_work + # We always pick one work + @pagy, works = pagy(Work.all, items: 1) + @work = works.sole end def show diff --git a/app/javascript/controllers/hints_controller.js b/app/javascript/controllers/hints_controller.js index e6c8c02..cb47cb5 100644 --- a/app/javascript/controllers/hints_controller.js +++ b/app/javascript/controllers/hints_controller.js @@ -15,9 +15,11 @@ export default class extends Controller { this.hint = document.createElement("div"); - this.hint.innerText = isMobile - ? "flip phone to resize" - : "drag corner to resize ⇲"; + this.hint.innerText = "click to go =>, click left edge to go <="; + + this.hint.innerText += isMobile + ? " | flip phone to resize" + : " | drag corner to resize ⇲"; this.hint.style.position = "fixed"; this.hint.style.bottom = "10px"; diff --git a/app/javascript/controllers/random_image_controller.js b/app/javascript/controllers/random_image_controller.js index a3a384d..33a3677 100644 --- a/app/javascript/controllers/random_image_controller.js +++ b/app/javascript/controllers/random_image_controller.js @@ -26,60 +26,64 @@ export default class extends Controller { this.imageDisplayTarget.src = this.imgproxyUrlValue; + const urlParams = new URLSearchParams(window.location.search); + if (!urlParams.has("page")) { + urlParams.set("page", 1); + } // Add event listener to intercept clicks on the viewport document.addEventListener("click", this.handleClick.bind(this)); - - // Update the page URL with the current slug value while keeping all query parameters - const currentSlug = this.slugValue; - console.log("slug value", currentSlug); - - let currentPath = window.location.pathname; - - if (/\/works\/\w+/.test(currentPath)) { - // If "/works/smth" is present, replace it with "/works/${currentSlug}" - currentPath = currentPath.replace( - /\/works\/\w+/, - `/works/${currentSlug}` - ); - } else { - // Otherwise, prepend "/works/${currentSlug}" to the current path - currentPath = `/works/${currentSlug}${currentPath}`; - } - - console.log("currentPath", currentPath); - - const newURL = `${window.location.origin}${currentPath}${window.location.search}${window.location.hash}`; - window.history.replaceState({}, "", newURL); } handleClick(event) { - // Calculate the width of the left 20% of the screen - const leftBoundary = window.innerWidth * 0.2; + // Calculate the width of the left 50% of the screen + const leftBoundary = window.innerWidth * 0.5; // Check if the click target is the draggable box or its children if (!event.target.closest("[data-controller='draggable']")) { - // Check if the click target is the image element - if (event.target === this.imageDisplayTarget) { - // Get the current URL - const url = new URL(window.location.href); + // Check if the click occurred on the left half of the document + if (event.clientX < leftBoundary) { + this.navigateBack(); + } else { + this.navigateForward(); + } + } + } - // Extract the existing prev_slug and session_id values - const prevSlug = url.searchParams.get("ps"); - const sessionId = url.searchParams.get("i"); + navigateBack() { + const url = new URL(window.location.href); + const page = url.searchParams.get("page"); + this.pageValue = parseInt(page); - // Update the session_id value if it's not already present - if (!sessionId) { - url.searchParams.set("i", this.sessionsOutlet.sessionIdValue); + if (!this.pageValue) { + return; + } + + if (this.pageValue > 1) { + let newPage = this.pageValue - 1; + Turbo.visit( + `/works?page=${newPage}&i=${this.sessionsOutlet.sessionIdValue}`, + { + action: "replace", } + ); + } + } - // Construct the new URL with the updated query parameters - let newURL = `/works/rand?ps=${prevSlug || this.slugValue}&i=${sessionId || this.sessionsOutlet.sessionIdValue}`; + navigateForward() { + const url = new URL(window.location.href); + const page = url.searchParams.get("page"); + this.pageValue = parseInt(page); - // Send a Turbo visit request to the new URL - Turbo.visit(newURL, { - action: "replace", - }); - } + if (!this.pageValue) { + this.pageValue = 1; } + + let newPage = this.pageValue + 1; + Turbo.visit( + `/works?page=${newPage}&i=${this.sessionsOutlet.sessionIdValue}`, + { + action: "replace", + } + ); } } diff --git a/app/models/work.rb b/app/models/work.rb index c66677d..9a62806 100644 --- a/app/models/work.rb +++ b/app/models/work.rb @@ -2,9 +2,9 @@ class Work < ApplicationRecord scope :excluding_slugs, ->(slugs) { where.not(slug: slugs) } scope :random, -> { order(Arel.sql("RANDOM()")) } - serialize :medium, JSON - serialize :dimensions, JSON - serialize :meta, JSON + serialize :medium, coder: JSON + serialize :dimensions, coder: JSON + serialize :meta, coder: JSON validates :title, presence: true validates :location, presence: true diff --git a/app/views/works/_image.html.erb b/app/views/works/_image.html.erb new file mode 100644 index 0000000..8599bd9 --- /dev/null +++ b/app/views/works/_image.html.erb @@ -0,0 +1,61 @@ + +
+

+ <%= @work.slug.humanize.split.map(&:capitalize).join(" ") %> +

+

+ 📍 + <%= @work.location %> +
+ 🗓ī¸ + <%= @work.year %> +

+ + + <% if @work.description.present? %> +

+ <%= @work.description %> +

+ <% end %> + + +
+
+ + <% if @work.dimensions.present? %> +

<%= @work.height %> + â•ŗ + <%= @work.width %> + <%= @work.depth %> + cm +

+ <% end %> +

Šī¸ Andy Barnow, Marina Naperstak

+
diff --git a/app/views/works/index.html.erb b/app/views/works/index.html.erb new file mode 100644 index 0000000..4c665dc --- /dev/null +++ b/app/views/works/index.html.erb @@ -0,0 +1 @@ +<%= render "image" %> diff --git a/app/views/works/show.html.erb b/app/views/works/show.html.erb index 8599bd9..4c665dc 100644 --- a/app/views/works/show.html.erb +++ b/app/views/works/show.html.erb @@ -1,61 +1 @@ - -
-

- <%= @work.slug.humanize.split.map(&:capitalize).join(" ") %> -

-

- 📍 - <%= @work.location %> -
- 🗓ī¸ - <%= @work.year %> -

- - - <% if @work.description.present? %> -

- <%= @work.description %> -

- <% end %> - - -
-
- - <% if @work.dimensions.present? %> -

<%= @work.height %> - â•ŗ - <%= @work.width %> - <%= @work.depth %> - cm -

- <% end %> -

Šī¸ Andy Barnow, Marina Naperstak

-
+<%= render "image" %> diff --git a/config/routes.rb b/config/routes.rb index d2c6ade..27fd181 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,7 @@ # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. # Can be used by load balancers and uptime monitors to verify that the app is live. get "up" => "rails/health#show", :as => :rails_health_check - get "/" => "works#show", :status => 301 + get "/" => "works#index", :status => 301 get "works/:slug" => "works#show", :as => "work" + get "/works" => "works#index" end