diff --git a/src/api.ts b/src/api.ts
index 59a164c..45df67a 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -43,6 +43,7 @@ export interface ApiMediaItem {
director: string | null;
writer: string | null;
releaseDate: string | null;
+ source?: string | null;
createdAt: string;
updatedAt: string;
genres?: string[];
@@ -53,7 +54,6 @@ export interface ApiMediaItem {
platforms?: string[];
developers?: string[];
completionStatus?: string;
- source?: string;
playCount?: number;
lastActivity?: string | null;
playtime?: number;
@@ -87,6 +87,7 @@ export interface CreateMediaInput {
director?: string | null;
writer?: string | null;
releaseDate?: string | null;
+ source?: string | null;
genres?: string[];
tags?: string[];
studios?: string[];
@@ -309,6 +310,7 @@ export function convertApiToMedia(apiItem: ApiMediaItem): Media {
tags: apiItem.tags || [],
studios: apiItem.studios,
type: mediaType,
+ source: apiItem.source || undefined,
status: mediaStatus,
staff: staff.length > 0 ? staff : undefined,
aspectRatio: aspectRatio,
@@ -316,7 +318,6 @@ export function convertApiToMedia(apiItem: ApiMediaItem): Media {
platforms: apiItem.platforms,
developers: apiItem.developers,
completionStatus: apiItem.completionStatus,
- source: apiItem.source,
playCount: apiItem.playCount,
lastActivity: apiItem.lastActivity,
playtime: apiItem.playtime
diff --git a/src/components/AddMediaView.tsx b/src/components/AddMediaView.tsx
index 0494160..46e8d01 100644
--- a/src/components/AddMediaView.tsx
+++ b/src/components/AddMediaView.tsx
@@ -31,6 +31,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
director: '',
writer: '',
releaseDate: '',
+ source: '' as string,
genres: '' as string,
tags: '' as string,
studios: '' as string
@@ -129,6 +130,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
director: newMedia.director || null,
writer: newMedia.writer || null,
releaseDate: newMedia.releaseDate || null,
+ source: newMedia.source || null,
genres: newMedia.genres ? newMedia.genres.split(',').map(g => g.trim()) : [],
tags: newMedia.tags ? newMedia.tags.split(',').map(t => t.trim()) : [],
studios: newMedia.studios ? newMedia.studios.split(',').map(s => s.trim()) : [],
@@ -163,6 +165,7 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
director: '',
writer: '',
releaseDate: '',
+ source: '',
genres: '',
tags: '',
studios: ''
@@ -439,6 +442,16 @@ export default function AddMediaView({ activeCategory, enabledCategories, onAddC
className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
/>
+
+
+ setNewMedia(prev => ({ ...prev, source: e.target.value }))}
+ placeholder="e.g. username, xbvr, stashapp"
+ className="bg-muted border-border rounded-xl h-11 focus:ring-[#6d28d9]"
+ />
+
{/* Cast/Staff Section */}
{(newMedia.category === 'Anime' || newMedia.category === 'Movies' || newMedia.category === 'TV Series' || newMedia.category === 'Adult') && (
diff --git a/src/components/BrowseView.tsx b/src/components/BrowseView.tsx
index dfaf4f2..14f128d 100644
--- a/src/components/BrowseView.tsx
+++ b/src/components/BrowseView.tsx
@@ -1,7 +1,7 @@
import { Media, MediaCategory } from '@/types';
import MediaCard from './MediaCard';
import MediaListItem from './MediaListItem';
-import { LayoutGrid, List, Star, ChevronLeft, ChevronRight, ArrowUpDown, Search, Monitor, Users, FolderTree } from 'lucide-react';
+import { LayoutGrid, List, Star, ChevronLeft, ChevronRight, ArrowUpDown, Search, Monitor, Users, FolderTree, Tag } from 'lucide-react';
import { Button } from '@/components/ui/button';
import React, { useState, useMemo, useEffect } from 'react';
import {
@@ -49,6 +49,7 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
const [selectedPlatform, setSelectedPlatform] = useState(null);
const [selectedDeveloper, setSelectedDeveloper] = useState(null);
const [selectedCategory, setSelectedCategory] = useState(null);
+ const [selectedSource, setSelectedSource] = useState(null);
// Extract unique values for filters
const allGenres = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.genres || []))), [mediaList]);
@@ -56,6 +57,7 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
const allPlatforms = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.platforms || []))), [mediaList]);
const allDevelopers = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.developers || []))), [mediaList]);
const allCategories = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.categories || []))), [mediaList]);
+ const allSources = useMemo(() => Array.from(new Set(mediaList.flatMap(m => m.source ? [m.source] : []))), [mediaList]);
const filteredMedia = useMemo(() => {
return mediaList.filter(media => {
@@ -64,9 +66,10 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
if (selectedPlatform && !media.platforms?.includes(selectedPlatform)) return false;
if (selectedDeveloper && !media.developers?.includes(selectedDeveloper)) return false;
if (selectedCategory && !media.categories?.includes(selectedCategory)) return false;
+ if (selectedSource && media.source !== selectedSource) return false;
return true;
});
- }, [mediaList, selectedGenre, selectedStudio, selectedPlatform, selectedDeveloper, selectedCategory]);
+ }, [mediaList, selectedGenre, selectedStudio, selectedPlatform, selectedDeveloper, selectedCategory, selectedSource]);
// Reset to first page when mediaList or filters change
useEffect(() => {
@@ -208,7 +211,25 @@ export default function BrowseView({ mediaList, onMediaClick, activeCategory, it
)}
- {(selectedGenre || selectedStudio || selectedPlatform || selectedDeveloper || selectedCategory) && (
+ {/* Source Filter */}
+ {allSources.length > 0 && (
+
+
+
+
+
+ setSelectedSource(null)}>All Sources
+ {allSources.sort().map(source => (
+ setSelectedSource(source)}>{source}
+ ))}
+
+
+ )}
+
+ {(selectedGenre || selectedStudio || selectedPlatform || selectedDeveloper || selectedCategory || selectedSource) && (