Initial overhaul of Movie Search
This commit is contained in:
@@ -1,19 +0,0 @@
|
|||||||
@page "/counter"
|
|
||||||
@rendermode InteractiveServer
|
|
||||||
|
|
||||||
<PageTitle>Counter</PageTitle>
|
|
||||||
|
|
||||||
<h1>Counter</h1>
|
|
||||||
|
|
||||||
<p role="status">Current count: @currentCount</p>
|
|
||||||
|
|
||||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private int currentCount = 0;
|
|
||||||
|
|
||||||
private void IncrementCount()
|
|
||||||
{
|
|
||||||
currentCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,93 +3,554 @@
|
|||||||
@inject ShowService ShowService
|
@inject ShowService ShowService
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
<h3>Search for Actor by TV Show and Character Name</h3>
|
<div class="search-container">
|
||||||
|
<div class="search-header">
|
||||||
|
<h2 class="search-title">Discover Actors & Their Shows</h2>
|
||||||
|
<p class="search-subtitle">Search by TV show and character to explore an actor's complete filmography</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="search-form">
|
||||||
<input type="text" class="form-control" @bind="_tvTitle" placeholder="Enter show title..." />
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" @bind="_characterName" placeholder="Enter character name..." />
|
<div class="input-wrapper">
|
||||||
<button class="btn btn-primary mt-2" @onclick="SearchForActorAndCharacters">Search</button>
|
<input type="text"
|
||||||
|
class="search-input"
|
||||||
|
@bind="_tvTitle"
|
||||||
|
placeholder="Enter TV show title..."
|
||||||
|
@onkeypress="@(async (e) => { if (e.Key == "Enter") await SearchForActorAndCharacters(); })" />
|
||||||
|
<div class="input-icon">📺</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="text"
|
||||||
|
class="search-input"
|
||||||
|
@bind="_characterName"
|
||||||
|
placeholder="Enter character name..."
|
||||||
|
@onkeypress="@(async (e) => { if (e.Key == "Enter") await SearchForActorAndCharacters(); })" />
|
||||||
|
<div class="input-icon">🎭</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="search-btn @(_isSearching ? "searching" : "")"
|
||||||
|
@onclick="SearchForActorAndCharacters"
|
||||||
|
disabled="@_isSearching">
|
||||||
|
@if (_isSearching)
|
||||||
|
{
|
||||||
|
<span class="spinner"></span>
|
||||||
|
<span>Searching...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>🔍 Search</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (_isSearching)
|
@if (_isSearching)
|
||||||
{
|
{
|
||||||
<p>Searching...</p>
|
<div class="loading-state">
|
||||||
|
<div class="loading-animation">
|
||||||
|
<div class="loading-dot"></div>
|
||||||
|
<div class="loading-dot"></div>
|
||||||
|
<div class="loading-dot"></div>
|
||||||
|
</div>
|
||||||
|
<p>Finding the perfect match...</p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
else if (_actor != null)
|
else if (_actor != null)
|
||||||
{
|
{
|
||||||
<h4>Actor: @_actor.Name</h4>
|
<div class="results-section">
|
||||||
|
<div class="actor-header">
|
||||||
|
<h3 class="actor-name">@_actor.Name</h3>
|
||||||
|
<div class="results-count">@_actor.TvCharacters.Count show@(_actor.TvCharacters.Count != 1 ? "s" : "") found</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (_matchedShow != null)
|
@if (_matchedShow != null)
|
||||||
{
|
{
|
||||||
<div class="card m-2" style="width: 18rem;">
|
<div class="matched-show-section">
|
||||||
<img class="card-img-top" src="@($"https://image.tmdb.org/t/p/w500{_matchedShow.ShowPosterPath}")" alt="Show Poster">
|
<h4 class="section-title">Your Search Match</h4>
|
||||||
<div class="card-body">
|
<div class="show-card featured">
|
||||||
<h5 class="card-title">@_matchedShow.ShowTitle</h5>
|
<div class="poster-container">
|
||||||
<h10 class="card-text">@_matchedShow.CharacterName</h10>
|
<img class="poster-image"
|
||||||
<br />
|
src="@($"https://image.tmdb.org/t/p/w500{_matchedShow.ShowPosterPath}")"
|
||||||
<small class="text-muted">First Air Date: @_matchedShow.FirstAirDate</small>
|
alt="@_matchedShow.ShowTitle Poster"
|
||||||
|
loading="lazy" />
|
||||||
|
<div class="match-badge">✨ Match</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<h5 class="show-title">@_matchedShow.ShowTitle</h5>
|
||||||
|
<div class="character-name">as @_matchedShow.CharacterName</div>
|
||||||
|
<div class="air-date">First aired: @_matchedShow.FirstAirDate</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div class="filmography-section">
|
||||||
|
<h4 class="section-title">Complete TV Filmography</h4>
|
||||||
|
|
||||||
@foreach (var group in _actor.TvCharacters
|
@foreach (var group in _actor.TvCharacters
|
||||||
.GroupBy(tc => tc.Franchise)
|
.GroupBy(tc => tc.Franchise)
|
||||||
.OrderBy(g => g.Key == "Standalone")
|
.OrderBy(g => g.Key == "Standalone")
|
||||||
.ThenBy(g => g.Key))
|
.ThenBy(g => g.Key))
|
||||||
{
|
{
|
||||||
<div class="d-flex flex-wrap">
|
@if (group.Key != "Standalone")
|
||||||
|
{
|
||||||
|
<div class="franchise-group">
|
||||||
|
<h5 class="franchise-title">@group.Key Series</h5>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="shows-grid">
|
||||||
@foreach (var show in (group.Key == "Standalone"
|
@foreach (var show in (group.Key == "Standalone"
|
||||||
? group.OrderByDescending(tc => DateTime.TryParse(tc.FirstAirDate, out var d) ? d : DateTime.MinValue)
|
? group.OrderByDescending(tc => DateTime.TryParse(tc.FirstAirDate, out var d) ? d : DateTime.MinValue)
|
||||||
: group.OrderBy(tc => DateTime.TryParse(tc.FirstAirDate, out var d) ? d : DateTime.MaxValue)))
|
: group.OrderBy(tc => DateTime.TryParse(tc.FirstAirDate, out var d) ? d : DateTime.MaxValue)))
|
||||||
{
|
{
|
||||||
<div class="card m-2" style="width: 18rem;">
|
<div class="show-card @(show == _matchedShow ? "matched" : "")">
|
||||||
<img class="card-img-top" src="@($"https://image.tmdb.org/t/p/w500{show.ShowPosterPath}")" alt="Poster">
|
<div class="poster-container">
|
||||||
<div class="card-body">
|
<img class="poster-image"
|
||||||
<h5 class="card-title">@show.ShowTitle</h5>
|
src="@($"https://image.tmdb.org/t/p/w500{show.ShowPosterPath}")"
|
||||||
<h10 class="card-text">@show.CharacterName</h10>
|
alt="@show.ShowTitle Poster"
|
||||||
<p class="text-muted">@show.Overview</p>
|
loading="lazy" />
|
||||||
<small class="text-muted">First Air Date: @show.FirstAirDate</small>
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<h5 class="show-title">@show.ShowTitle</h5>
|
||||||
|
<div class="character-name">as @show.CharacterName</div>
|
||||||
|
@if (!string.IsNullOrEmpty(show.Overview))
|
||||||
|
{
|
||||||
|
<p class="show-overview">@(show.Overview.Length > 120 ? show.Overview.Substring(0, 120) + "..." : show.Overview)</p>
|
||||||
|
}
|
||||||
|
<div class="air-date">@show.FirstAirDate</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(_tvTitle) || !string.IsNullOrEmpty(_characterName))
|
||||||
|
{
|
||||||
|
<div class="no-results">
|
||||||
|
<div class="no-results-icon">🔍</div>
|
||||||
|
<h4>No matches found</h4>
|
||||||
|
<p>Try adjusting your search terms or check the spelling</p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<p>Enter a TV show title and character name to begin your search.</p>
|
<div class="welcome-state">
|
||||||
|
<div class="welcome-icon">🎬</div>
|
||||||
|
<h4>Ready to explore?</h4>
|
||||||
|
<p>Enter a TV show title and character name to discover an actor's complete television journey</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.search-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-subtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #666;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
background: white;
|
||||||
|
border-radius: 1rem;
|
||||||
|
padding: 2rem;
|
||||||
|
box-shadow: 0 8px 25px rgba(0,0,0,0.08);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 250px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.875rem 1rem 0.875rem 3rem;
|
||||||
|
border: 2px solid #e1e5e9;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
background: #fafbfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 1rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 0.875rem 2rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-width: 140px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn:hover:not(:disabled) {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid rgba(255,255,255,0.3);
|
||||||
|
border-top: 2px solid white;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keyframes spin {
|
||||||
|
0%
|
||||||
|
|
||||||
|
{
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
} */
|
||||||
|
|
||||||
|
.loading-state, .welcome-state, .no-results {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-animation {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: bounce 1.4s ease-in-out infinite both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dot:nth-child(1) {
|
||||||
|
animation-delay: -0.32s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dot:nth-child(2) {
|
||||||
|
animation-delay: -0.16s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keyframes bounce {
|
||||||
|
0%, 80%, 100%
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} */
|
||||||
|
|
||||||
|
.welcome-icon, .no-results-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-section {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-name {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-count {
|
||||||
|
background: #f0f4ff;
|
||||||
|
color: #4c63d2;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matched-show-section {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.franchise-group {
|
||||||
|
margin: 2rem 0 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.franchise-title {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #4c63d2;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 4px solid #4c63d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shows-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.08);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-card:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 30px rgba(0,0,0,0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-card.featured {
|
||||||
|
border: 2px solid #667eea;
|
||||||
|
box-shadow: 0 8px 30px rgba(102, 126, 234, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-card.matched {
|
||||||
|
border: 2px solid #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poster-container {
|
||||||
|
position: relative;
|
||||||
|
height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poster-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-card:hover .poster-image {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.match-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
background: linear-gradient(135deg, #10b981, #059669);
|
||||||
|
color: white;
|
||||||
|
padding: 0.3rem 0.8rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
padding: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-name {
|
||||||
|
color: #667eea;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-overview {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.air-date {
|
||||||
|
color: #999;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@media (max-width: 768px) {
|
||||||
|
.search-container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shows-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string _tvTitle;
|
private string _tvTitle = "";
|
||||||
private string _characterName;
|
private string _characterName = "";
|
||||||
private Actor _actor;
|
private Actor _actor;
|
||||||
private TvCharacter _matchedShow;
|
private TvCharacter _matchedShow;
|
||||||
private bool _isSearching = false;
|
private bool _isSearching = false;
|
||||||
|
|
||||||
private async Task SearchForActorAndCharacters()
|
private async Task SearchForActorAndCharacters()
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_tvTitle) || string.IsNullOrWhiteSpace(_characterName))
|
||||||
|
return;
|
||||||
|
|
||||||
_isSearching = true;
|
_isSearching = true;
|
||||||
_actor = null;
|
_actor = null;
|
||||||
_matchedShow = null;
|
_matchedShow = null;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_tvTitle) && !string.IsNullOrWhiteSpace(_characterName))
|
try
|
||||||
{
|
{
|
||||||
var shows = await ShowService.GetShowsByTitle(_tvTitle);
|
var shows = await ShowService.GetShowsByTitle(_tvTitle);
|
||||||
|
|
||||||
foreach (var show in shows)
|
foreach (var show in shows)
|
||||||
{
|
{
|
||||||
var credits = await ShowService.GetShowCreditsById(show.Id);
|
var credits = await ShowService.GetShowCreditsById(show.Id);
|
||||||
|
|
||||||
foreach (var cast in credits.Cast)
|
foreach (var cast in credits.Cast)
|
||||||
{
|
{
|
||||||
if (cast.Character.ToLower().Contains(_characterName.ToLower()))
|
if (cast.Character.Contains(_characterName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (_actor == null)
|
if (_actor == null)
|
||||||
{
|
{
|
||||||
_actor = new Actor(cast.Name);
|
_actor = new Actor(cast.Name);
|
||||||
int personId = cast.Id;
|
await LoadActorFilmography(cast.Id, (object)show, (object)cast);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_actor != null) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Search error: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isSearching = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadActorFilmography(int personId, object originalShow, object originalCast)
|
||||||
|
{
|
||||||
var filmography = await ShowService.GetActorTvCreditsById(personId);
|
var filmography = await ShowService.GetActorTvCreditsById(personId);
|
||||||
|
|
||||||
if (filmography?.Cast != null)
|
if (filmography?.Cast != null)
|
||||||
{
|
{
|
||||||
foreach (var role in filmography.Cast)
|
foreach (var role in filmography.Cast)
|
||||||
@@ -98,12 +559,12 @@ else
|
|||||||
|
|
||||||
var tvCharacter = new TvCharacter
|
var tvCharacter = new TvCharacter
|
||||||
{
|
{
|
||||||
ShowTitle = role.ShowTitle,
|
ShowTitle = role.ShowTitle ?? "Unknown Title",
|
||||||
CharacterName = role.Character,
|
CharacterName = role.Character ?? "Unknown Character",
|
||||||
ShowPosterPath = role.PosterPath,
|
ShowPosterPath = role.PosterPath ?? "",
|
||||||
FirstAirDate = DateTime.TryParse(fullShow?.FirstAirDate, out var parsedDate)
|
FirstAirDate = DateTime.TryParse(fullShow?.FirstAirDate, out var parsedDate)
|
||||||
? parsedDate.ToString("MM/dd/yyyy")
|
? parsedDate.ToString("MMM dd, yyyy")
|
||||||
: "N/A",
|
: "Unknown Date",
|
||||||
Overview = fullShow?.Overview ?? "",
|
Overview = fullShow?.Overview ?? "",
|
||||||
Franchise = fullShow?.BelongsToCollection?.Name ?? "Standalone"
|
Franchise = fullShow?.BelongsToCollection?.Name ?? "Standalone"
|
||||||
};
|
};
|
||||||
@@ -111,20 +572,19 @@ else
|
|||||||
_actor.TvCharacters.Add(tvCharacter);
|
_actor.TvCharacters.Add(tvCharacter);
|
||||||
|
|
||||||
if (_matchedShow == null &&
|
if (_matchedShow == null &&
|
||||||
role.ShowTitle == show.Name &&
|
string.Equals(role.ShowTitle, GetPropertyValue(originalShow, "Name")?.ToString(), StringComparison.OrdinalIgnoreCase) &&
|
||||||
role.Character == cast.Character)
|
string.Equals(role.Character, GetPropertyValue(originalCast, "Character")?.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_matchedShow = tvCharacter;
|
_matchedShow = tvCharacter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_isSearching = false;
|
private object GetPropertyValue(object obj, string propertyName)
|
||||||
StateHasChanged();
|
{
|
||||||
|
if (obj == null) return null;
|
||||||
|
var property = obj.GetType().GetProperty(propertyName);
|
||||||
|
return property?.GetValue(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
@page "/weather"
|
|
||||||
@attribute [StreamRendering]
|
|
||||||
|
|
||||||
<PageTitle>Weather</PageTitle>
|
|
||||||
|
|
||||||
<h1>Weather</h1>
|
|
||||||
|
|
||||||
<p>This component demonstrates showing data.</p>
|
|
||||||
|
|
||||||
@if (forecasts == null)
|
|
||||||
{
|
|
||||||
<p><em>Loading...</em></p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<table class="table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Temp. (C)</th>
|
|
||||||
<th>Temp. (F)</th>
|
|
||||||
<th>Summary</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach (var forecast in forecasts)
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>@forecast.Date.ToShortDateString()</td>
|
|
||||||
<td>@forecast.TemperatureC</td>
|
|
||||||
<td>@forecast.TemperatureF</td>
|
|
||||||
<td>@forecast.Summary</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private WeatherForecast[]? forecasts;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
// Simulate asynchronous loading to demonstrate streaming rendering
|
|
||||||
await Task.Delay(500);
|
|
||||||
|
|
||||||
var startDate = DateOnly.FromDateTime(DateTime.Now);
|
|
||||||
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
|
|
||||||
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
|
||||||
{
|
|
||||||
Date = startDate.AddDays(index),
|
|
||||||
TemperatureC = Random.Shared.Next(-20, 55),
|
|
||||||
Summary = summaries[Random.Shared.Next(summaries.Length)]
|
|
||||||
}).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class WeatherForecast
|
|
||||||
{
|
|
||||||
public DateOnly Date { get; set; }
|
|
||||||
public int TemperatureC { get; set; }
|
|
||||||
public string? Summary { get; set; }
|
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user