Refactor PostFiles component to use URL parameters and simplify implementation
- Change from hash fragments (#file=2) to URL parameters (?idx=2) for server-side prerendering support - Simplify React component by removing complex client-side hydration logic - Remove unnecessary props: totalFiles, hasMultipleFiles (derive from files.length) - Remove redundant useCallback and popstate handlers - Update Rails helper to read URL parameter and pass correct initialSelectedIndex - Maintain all functionality: carousel, keyboard navigation, URL state management
This commit is contained in:
@@ -195,11 +195,12 @@ module Domain::PostsHelper
|
||||
end
|
||||
|
||||
sig do
|
||||
params(ok_files: T::Array[Domain::PostFile]).returns(
|
||||
T::Hash[Symbol, T.untyped],
|
||||
)
|
||||
params(
|
||||
ok_files: T::Array[Domain::PostFile],
|
||||
initial_file_index: T.nilable(Integer),
|
||||
).returns(T::Hash[Symbol, T.untyped])
|
||||
end
|
||||
def props_for_post_files(ok_files)
|
||||
def props_for_post_files(ok_files, initial_file_index: nil)
|
||||
files_data =
|
||||
ok_files.map.with_index do |file, index|
|
||||
thumbnail_path = nil
|
||||
@@ -266,12 +267,14 @@ module Domain::PostsHelper
|
||||
}
|
||||
end
|
||||
|
||||
{
|
||||
files: files_data,
|
||||
totalFiles: ok_files.count,
|
||||
initialSelectedIndex: 0,
|
||||
hasMultipleFiles: ok_files.count > 1,
|
||||
}
|
||||
# Validate initial_file_index
|
||||
validated_initial_index = 0
|
||||
if initial_file_index && initial_file_index >= 0 &&
|
||||
initial_file_index < ok_files.count
|
||||
validated_initial_index = initial_file_index
|
||||
end
|
||||
|
||||
{ files: files_data, initialSelectedIndex: validated_initial_index }
|
||||
end
|
||||
|
||||
sig { params(url: String).returns(T.nilable(String)) }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { FileCarousel } from './FileCarousel';
|
||||
import { DisplayedFile } from './DisplayedFile';
|
||||
|
||||
@@ -14,31 +14,78 @@ export interface FileData {
|
||||
|
||||
interface PostFilesProps {
|
||||
files: FileData[];
|
||||
totalFiles: number;
|
||||
initialSelectedIndex?: number;
|
||||
hasMultipleFiles: boolean;
|
||||
}
|
||||
|
||||
export const PostFiles: React.FC<PostFilesProps> = ({
|
||||
files,
|
||||
totalFiles,
|
||||
initialSelectedIndex = 0,
|
||||
hasMultipleFiles,
|
||||
}) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);
|
||||
|
||||
// Update URL parameter when selected file changes
|
||||
const updateUrlWithFileIndex = (index: number) => {
|
||||
if (typeof window === 'undefined' || files.length <= 1) return;
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('idx', index.toString());
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
};
|
||||
|
||||
const handleFileSelect = (fileId: number, index: number) => {
|
||||
setSelectedIndex(index);
|
||||
updateUrlWithFileIndex(index);
|
||||
};
|
||||
|
||||
const navigateToNextFile = () => {
|
||||
if (files.length > 1) {
|
||||
const nextIndex = (selectedIndex + 1) % files.length;
|
||||
handleFileSelect(files[nextIndex].id, nextIndex);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToPreviousFile = () => {
|
||||
if (files.length > 1) {
|
||||
const prevIndex = (selectedIndex - 1 + files.length) % files.length;
|
||||
handleFileSelect(files[prevIndex].id, prevIndex);
|
||||
}
|
||||
};
|
||||
|
||||
// Add keyboard navigation
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
// Only handle arrow keys if we have multiple files
|
||||
if (files.length <= 1) return;
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
event.preventDefault();
|
||||
navigateToPreviousFile();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
event.preventDefault();
|
||||
navigateToNextFile();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Add event listener to document
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
// Cleanup event listener on unmount
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [selectedIndex, files.length]);
|
||||
|
||||
const selectedFile = files[selectedIndex];
|
||||
|
||||
return (
|
||||
<section id="file-display-section">
|
||||
{hasMultipleFiles && (
|
||||
{files.length > 1 && (
|
||||
<FileCarousel
|
||||
files={files}
|
||||
totalFiles={totalFiles}
|
||||
totalFiles={files.length}
|
||||
selectedIndex={selectedIndex}
|
||||
onFileSelect={handleFileSelect}
|
||||
/>
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
<% current_file = ok_files.first || post.primary_file_for_view %>
|
||||
<% if ok_files.any? && current_file&.log_entry&.status_code == 200 %>
|
||||
<!-- React PostFiles Component handles everything -->
|
||||
<%
|
||||
# Extract file index from URL parameter
|
||||
idx_param = params[:idx]
|
||||
initial_file_index = idx_param.present? ? idx_param.to_i : nil
|
||||
%>
|
||||
<%= react_component(
|
||||
"PostFiles",
|
||||
{
|
||||
prerender: true,
|
||||
props: props_for_post_files(ok_files),
|
||||
props: props_for_post_files(ok_files, initial_file_index: initial_file_index),
|
||||
html_options: {
|
||||
id: "post-files-component"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user