@anymux/ui-kit 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/{calendar-DSlrbHoj.js → calendar-DQKfYSQS.js} +48 -45
  2. package/dist/calendar-DQKfYSQS.js.map +1 -0
  3. package/dist/calendar.d.ts +1 -1
  4. package/dist/calendar.js +1 -1
  5. package/dist/{contacts-DQXTZzHc.js → contacts-By9Wg3kn.js} +35 -33
  6. package/dist/contacts-By9Wg3kn.js.map +1 -0
  7. package/dist/contacts.d.ts +1 -1
  8. package/dist/contacts.js +1 -1
  9. package/dist/{file-browser-m5atC3kF.js → file-browser-CkhNwADU.js} +61 -133
  10. package/dist/file-browser-CkhNwADU.js.map +1 -0
  11. package/dist/file-browser.d.ts +6 -6
  12. package/dist/file-browser.js +4 -4
  13. package/dist/{git-B55e6LL-.js → git-m4lboTfx.js} +29 -29
  14. package/dist/git-m4lboTfx.js.map +1 -0
  15. package/dist/git.js +1 -1
  16. package/dist/{iconMap-V4B8P-Uh.js → iconMap-DDpe35ek.js} +5 -5
  17. package/dist/iconMap-DDpe35ek.js.map +1 -0
  18. package/dist/icons.js +1 -1
  19. package/dist/{index-Bryv_GCG.d.ts → index-BP4IYXiF.d.ts} +46 -53
  20. package/dist/index-BP4IYXiF.d.ts.map +1 -0
  21. package/dist/{index-kHr9udZD.d.ts → index-BkIh8oov.d.ts} +17 -17
  22. package/dist/{index-kHr9udZD.d.ts.map → index-BkIh8oov.d.ts.map} +1 -1
  23. package/dist/{index-DSu19mq0.d.ts → index-D3Ob3aXg.d.ts} +9 -9
  24. package/dist/{index-DSu19mq0.d.ts.map → index-D3Ob3aXg.d.ts.map} +1 -1
  25. package/dist/{index-Ml_SgiKa.d.ts → index-DGoLQBX6.d.ts} +18 -42
  26. package/dist/index-DGoLQBX6.d.ts.map +1 -0
  27. package/dist/index-DnJaZr08.d.ts +67 -0
  28. package/dist/index-DnJaZr08.d.ts.map +1 -0
  29. package/dist/{index-DmsyeHFr.d.ts → index-Pty-N7-g.d.ts} +5 -5
  30. package/dist/{index-DmsyeHFr.d.ts.map → index-Pty-N7-g.d.ts.map} +1 -1
  31. package/dist/index.d.ts +7 -7
  32. package/dist/index.js +10 -10
  33. package/dist/layout-BYsc16hD.js +183 -0
  34. package/dist/layout-BYsc16hD.js.map +1 -0
  35. package/dist/layout.d.ts +2 -2
  36. package/dist/layout.js +2 -2
  37. package/dist/{list-CxfT6hix.js → list-DAq-b6RR.js} +49 -63
  38. package/dist/list-DAq-b6RR.js.map +1 -0
  39. package/dist/list.d.ts +2 -2
  40. package/dist/list.js +4 -3
  41. package/dist/{media-DZ292aKK.js → media-DuczOGsk.js} +32 -31
  42. package/dist/media-DuczOGsk.js.map +1 -0
  43. package/dist/media.js +1 -1
  44. package/dist/{tree-Dd9Z0Aso.js → tree-B9VQcKBp.js} +2 -2
  45. package/dist/{tree-Dd9Z0Aso.js.map → tree-B9VQcKBp.js.map} +1 -1
  46. package/dist/tree.d.ts +1 -1
  47. package/dist/tree.js +2 -2
  48. package/package.json +2 -2
  49. package/src/calendar/AgendaView.tsx +2 -2
  50. package/src/calendar/CalendarBrowser.tsx +11 -11
  51. package/src/calendar/CalendarSidebar.tsx +10 -10
  52. package/src/calendar/DayView.tsx +5 -5
  53. package/src/calendar/EventCard.tsx +3 -3
  54. package/src/calendar/MonthView.tsx +6 -6
  55. package/src/calendar/WeekView.tsx +10 -10
  56. package/src/contacts/ContactBrowser.tsx +8 -8
  57. package/src/contacts/ContactCard.tsx +4 -4
  58. package/src/contacts/ContactDetail.tsx +10 -10
  59. package/src/contacts/ContactGroupSidebar.tsx +6 -6
  60. package/src/contacts/ContactList.tsx +3 -3
  61. package/src/file-browser/components/FileBrowser.tsx +3 -2
  62. package/src/file-browser/components/FileBrowserContent.tsx +1 -1
  63. package/src/file-browser/examples/BasicUsage.tsx +2 -2
  64. package/src/file-browser/index.ts +1 -1
  65. package/src/file-browser/providers/FileSystemProvider.ts +1 -1
  66. package/src/git/BranchList.tsx +12 -12
  67. package/src/git/CommitList.tsx +11 -11
  68. package/src/git/DiffViewer.tsx +11 -11
  69. package/src/icons/iconMap.ts +4 -4
  70. package/src/layout/index.ts +6 -2
  71. package/src/layout/models/ResponsiveLayoutModel.ts +116 -0
  72. package/src/list/components/ListItem.tsx +1 -1
  73. package/src/list/index.ts +1 -1
  74. package/src/media/AlbumSidebar.tsx +4 -4
  75. package/src/media/MediaBrowser.tsx +11 -11
  76. package/src/media/MediaGrid.tsx +3 -3
  77. package/src/media/MediaList.tsx +6 -6
  78. package/src/media/MediaPreview.tsx +2 -2
  79. package/src/media/MediaTimeline.tsx +3 -3
  80. package/src/{file-browser/components/shared → shared}/ErrorBoundary.tsx +3 -3
  81. package/dist/calendar-DSlrbHoj.js.map +0 -1
  82. package/dist/contacts-DQXTZzHc.js.map +0 -1
  83. package/dist/file-browser-m5atC3kF.js.map +0 -1
  84. package/dist/git-B55e6LL-.js.map +0 -1
  85. package/dist/iconMap-V4B8P-Uh.js.map +0 -1
  86. package/dist/index-Bryv_GCG.d.ts.map +0 -1
  87. package/dist/index-DzfY1Tok.d.ts +0 -32
  88. package/dist/index-DzfY1Tok.d.ts.map +0 -1
  89. package/dist/index-Ml_SgiKa.d.ts.map +0 -1
  90. package/dist/layout-Ca_4r8ka.js +0 -89
  91. package/dist/layout-Ca_4r8ka.js.map +0 -1
  92. package/dist/list-CxfT6hix.js.map +0 -1
  93. package/dist/media-DZ292aKK.js.map +0 -1
  94. package/src/list/components/shared/ErrorBoundary.tsx +0 -123
@@ -1 +0,0 @@
1
- {"version":3,"file":"media-DZ292aKK.js","names":["provider: IMediaProvider","err: any","id: string","mode: MediaViewMode","album: string | null","query: string","sortBy: MediaSortBy","type: MediaType | null","item: MediaItem","items: MediaItem[]","item: MediaItem","MEDIA_TYPES: MediaType[]","items: MediaItem[]","id: string","item: Omit<MediaItem, 'id' | 'createdAt' | 'updatedAt'>","created: MediaItem","updates: Partial<MediaItem>","query: string","album: string","start: Date","end: Date"],"sources":["../src/media/MediaBrowserModel.ts","../src/media/MediaGrid.tsx","../src/media/MediaList.tsx","../src/media/MediaTimeline.tsx","../src/media/MediaPreview.tsx","../src/media/AlbumSidebar.tsx","../src/media/MediaBrowser.tsx","../src/media/MockMediaProvider.ts"],"sourcesContent":["import { makeAutoObservable, runInAction } from 'mobx';\nimport type { IMediaProvider, MediaItem, MediaType } from './types';\n\nexport type MediaViewMode = 'grid' | 'list' | 'timeline';\nexport type MediaSortBy = 'date' | 'name' | 'size';\n\nexport class MediaBrowserModel {\n items: MediaItem[] = [];\n selectedItems = new Set<string>();\n viewMode: MediaViewMode = 'grid';\n currentAlbum: string | null = null;\n loading = false;\n error: string | null = null;\n searchQuery = '';\n sortBy: MediaSortBy = 'date';\n filterByType: MediaType | null = null;\n previewItem: MediaItem | null = null;\n\n constructor(private provider: IMediaProvider) {\n makeAutoObservable(this);\n }\n\n get filteredItems(): MediaItem[] {\n let result = this.items;\n if (this.searchQuery) {\n const q = this.searchQuery.toLowerCase();\n result = result.filter(i => i.title.toLowerCase().includes(q));\n }\n if (this.filterByType) {\n result = result.filter(i => i.mediaType === this.filterByType);\n }\n return this.sortItems(result);\n }\n\n get groupedByDate(): Map<string, MediaItem[]> {\n const groups = new Map<string, MediaItem[]>();\n for (const item of this.filteredItems) {\n const key = item.createdAt.toLocaleDateString();\n const group = groups.get(key) ?? [];\n group.push(item);\n groups.set(key, group);\n }\n return groups;\n }\n\n get groupedByAlbum(): Map<string, MediaItem[]> {\n const groups = new Map<string, MediaItem[]>();\n for (const item of this.filteredItems) {\n const key = item.album ?? 'Uncategorized';\n const group = groups.get(key) ?? [];\n group.push(item);\n groups.set(key, group);\n }\n return groups;\n }\n\n async loadItems() {\n this.loading = true;\n this.error = null;\n try {\n const items = this.currentAlbum\n ? await this.provider.getByAlbum(this.currentAlbum)\n : await this.provider.listItems();\n runInAction(() => { this.items = items; });\n } catch (err: any) {\n runInAction(() => { this.error = err?.message || 'Failed to load media'; });\n } finally {\n runInAction(() => { this.loading = false; });\n }\n }\n\n selectItem(id: string) {\n this.selectedItems.clear();\n this.selectedItems.add(id);\n }\n\n toggleSelect(id: string) {\n if (this.selectedItems.has(id)) {\n this.selectedItems.delete(id);\n } else {\n this.selectedItems.add(id);\n }\n }\n\n setViewMode(mode: MediaViewMode) {\n this.viewMode = mode;\n }\n\n setAlbum(album: string | null) {\n this.currentAlbum = album;\n this.loadItems();\n }\n\n async search(query: string) {\n this.searchQuery = query;\n if (query) {\n this.loading = true;\n this.error = null;\n try {\n const items = await this.provider.search(query);\n runInAction(() => { this.items = items; });\n } catch (err: any) {\n runInAction(() => { this.error = err?.message || 'Search failed'; });\n } finally {\n runInAction(() => { this.loading = false; });\n }\n } else {\n this.loadItems();\n }\n }\n\n setSort(sortBy: MediaSortBy) {\n this.sortBy = sortBy;\n }\n\n setFilter(type: MediaType | null) {\n this.filterByType = type;\n }\n\n openPreview(item: MediaItem) {\n this.previewItem = item;\n }\n\n closePreview() {\n this.previewItem = null;\n }\n\n private sortItems(items: MediaItem[]): MediaItem[] {\n return [...items].sort((a, b) => {\n switch (this.sortBy) {\n case 'date': return b.createdAt.getTime() - a.createdAt.getTime();\n case 'name': return a.title.localeCompare(b.title);\n case 'size': return (b.width ?? 0) * (b.height ?? 0) - (a.width ?? 0) * (a.height ?? 0);\n default: return 0;\n }\n });\n }\n}\n","import React from 'react';\nimport { observer } from 'mobx-react-lite';\nimport { Play, Music } from 'lucide-react';\nimport type { MediaBrowserModel } from './MediaBrowserModel';\nimport type { MediaItem } from './types';\n\nexport interface MediaGridProps {\n model: MediaBrowserModel;\n className?: string;\n}\n\nconst MediaThumbnail = ({ item, onClick, selected }: { item: MediaItem; onClick: () => void; selected: boolean }) => (\n <button\n onClick={onClick}\n className={`relative aspect-square rounded-lg overflow-hidden cursor-pointer group border-2 transition-all ${\n selected ? 'border-blue-500 ring-2 ring-blue-200' : 'border-transparent hover:border-gray-300'\n }`}\n >\n {item.thumbnail || item.mediaType === 'photo' ? (\n <img\n src={item.thumbnail ?? item.url}\n alt={item.title}\n className=\"w-full h-full object-cover\"\n />\n ) : (\n <div className=\"w-full h-full bg-gray-100 flex items-center justify-center\">\n {item.mediaType === 'video' ? <Play size={32} className=\"text-gray-400\" /> : <Music size={32} className=\"text-gray-400\" />}\n </div>\n )}\n {item.mediaType === 'video' && (\n <div className=\"absolute bottom-1 right-1 bg-black/70 text-white text-xs px-1.5 py-0.5 rounded\">\n {item.duration ? `${Math.floor(item.duration / 60)}:${String(item.duration % 60).padStart(2, '0')}` : 'Video'}\n </div>\n )}\n <div className=\"absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors\" />\n </button>\n);\n\nexport const MediaGrid = observer<MediaGridProps>(({ model, className = '' }) => (\n <div className={`grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-1 p-2 ${className}`}>\n {model.filteredItems.map(item => (\n <MediaThumbnail\n key={item.id}\n item={item}\n selected={model.selectedItems.has(item.id)}\n onClick={() => model.openPreview(item)}\n />\n ))}\n </div>\n));\n","import React from 'react';\nimport { observer } from 'mobx-react-lite';\nimport { Image, Play, Music } from 'lucide-react';\nimport type { MediaBrowserModel } from './MediaBrowserModel';\nimport type { MediaItem } from './types';\n\nexport interface MediaListProps {\n model: MediaBrowserModel;\n className?: string;\n}\n\nconst mediaIcon = (item: MediaItem) => {\n switch (item.mediaType) {\n case 'photo': return <Image size={16} className=\"text-green-500\" />;\n case 'video': return <Play size={16} className=\"text-blue-500\" />;\n case 'audio': return <Music size={16} className=\"text-purple-500\" />;\n }\n};\n\nexport const MediaList = observer<MediaListProps>(({ model, className = '' }) => (\n <div className={`divide-y divide-gray-100 ${className}`}>\n {model.filteredItems.map(item => (\n <button\n key={item.id}\n onClick={() => model.openPreview(item)}\n className={`flex items-center gap-3 px-4 py-3 w-full text-left hover:bg-gray-50 transition-colors ${\n model.selectedItems.has(item.id) ? 'bg-blue-50' : ''\n }`}\n >\n <div className=\"w-12 h-12 rounded-lg overflow-hidden flex-shrink-0 bg-gray-100 flex items-center justify-center\">\n {item.thumbnail ? (\n <img src={item.thumbnail} alt={item.title} className=\"w-full h-full object-cover\" />\n ) : (\n mediaIcon(item)\n )}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-gray-900 truncate\" title={item.title}>{item.title}</p>\n <p className=\"text-xs text-gray-500\">\n {item.mediaType} {item.album && `· ${item.album}`} · {item.createdAt.toLocaleDateString()}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {mediaIcon(item)}\n </div>\n </button>\n ))}\n </div>\n));\n","import React from 'react';\nimport { observer } from 'mobx-react-lite';\nimport { Play, Music } from 'lucide-react';\nimport type { MediaBrowserModel } from './MediaBrowserModel';\nimport type { MediaItem } from './types';\n\nexport interface MediaTimelineProps {\n model: MediaBrowserModel;\n className?: string;\n}\n\nconst TimelineThumbnail = ({ item, onClick }: { item: MediaItem; onClick: () => void }) => (\n <button onClick={onClick} className=\"relative aspect-square rounded-lg overflow-hidden cursor-pointer group\">\n {item.thumbnail || item.mediaType === 'photo' ? (\n <img src={item.thumbnail ?? item.url} alt={item.title} className=\"w-full h-full object-cover\" />\n ) : (\n <div className=\"w-full h-full bg-gray-100 flex items-center justify-center\">\n {item.mediaType === 'video' ? <Play size={24} className=\"text-gray-400\" /> : <Music size={24} className=\"text-gray-400\" />}\n </div>\n )}\n <div className=\"absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-colors\" />\n </button>\n);\n\nexport const MediaTimeline = observer<MediaTimelineProps>(({ model, className = '' }) => (\n <div className={`space-y-6 p-4 ${className}`}>\n {Array.from(model.groupedByDate.entries()).map(([date, items]) => (\n <div key={date}>\n <h3 className=\"text-sm font-semibold text-gray-700 mb-2 sticky top-0 bg-white/90 backdrop-blur-sm py-1\">{date}</h3>\n <div className=\"grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-1\">\n {items.map(item => (\n <TimelineThumbnail key={item.id} item={item} onClick={() => model.openPreview(item)} />\n ))}\n </div>\n </div>\n ))}\n </div>\n));\n","import React from 'react';\nimport { observer } from 'mobx-react-lite';\nimport { X, ChevronLeft, ChevronRight, Download } from 'lucide-react';\nimport type { MediaBrowserModel } from './MediaBrowserModel';\n\nexport interface MediaPreviewProps {\n model: MediaBrowserModel;\n className?: string;\n}\n\nexport const MediaPreview = observer<MediaPreviewProps>(({ model, className = '' }) => {\n const item = model.previewItem;\n if (!item) return null;\n\n const items = model.filteredItems;\n const idx = items.findIndex(i => i.id === item.id);\n\n const goPrev = () => {\n if (idx > 0) model.openPreview(items[idx - 1]);\n };\n const goNext = () => {\n if (idx < items.length - 1) model.openPreview(items[idx + 1]);\n };\n\n return (\n <div className={`fixed inset-0 z-50 bg-black/90 flex items-center justify-center ${className}`}>\n <button onClick={() => model.closePreview()} className=\"absolute top-4 right-4 text-white/80 hover:text-white p-2 rounded-full hover:bg-white/10\">\n <X size={24} />\n </button>\n\n <button onClick={goPrev} disabled={idx <= 0} className=\"absolute left-4 text-white/80 hover:text-white p-2 rounded-full hover:bg-white/10 disabled:opacity-30\">\n <ChevronLeft size={32} />\n </button>\n\n <div className=\"max-w-[90vw] max-h-[90vh] flex flex-col items-center\">\n {item.mediaType === 'photo' && (\n <img src={item.url} alt={item.title} className=\"max-w-full max-h-[80vh] object-contain rounded-lg\" />\n )}\n {item.mediaType === 'video' && (\n <video src={item.url} controls className=\"max-w-full max-h-[80vh] rounded-lg\" />\n )}\n {item.mediaType === 'audio' && (\n <div className=\"bg-gray-900 rounded-xl p-8 flex flex-col items-center gap-4\">\n <div className=\"w-48 h-48 bg-gray-800 rounded-xl flex items-center justify-center text-6xl\">🎵</div>\n <audio src={item.url} controls className=\"w-80\" />\n </div>\n )}\n <div className=\"mt-4 text-center\">\n <p className=\"text-white font-medium\">{item.title}</p>\n <p className=\"text-white/60 text-sm\">{item.createdAt.toLocaleDateString()}</p>\n </div>\n </div>\n\n <button onClick={goNext} disabled={idx >= items.length - 1} className=\"absolute right-4 text-white/80 hover:text-white p-2 rounded-full hover:bg-white/10 disabled:opacity-30\">\n <ChevronRight size={32} />\n </button>\n\n <a href={item.url} download className=\"absolute bottom-4 right-4 text-white/80 hover:text-white p-2 rounded-full hover:bg-white/10\">\n <Download size={20} />\n </a>\n </div>\n );\n});\n","import React, { useEffect, useState } from 'react';\nimport { observer } from 'mobx-react-lite';\nimport { FolderOpen, Image } from 'lucide-react';\nimport type { MediaBrowserModel } from './MediaBrowserModel';\nimport type { IMediaProvider } from './types';\n\nexport interface AlbumSidebarProps {\n model: MediaBrowserModel;\n provider: IMediaProvider;\n className?: string;\n}\n\nexport const AlbumSidebar = observer<AlbumSidebarProps>(({ model, provider, className = '' }) => {\n const [albums, setAlbums] = useState<string[]>([]);\n\n useEffect(() => {\n provider.getAlbums().then(setAlbums);\n }, [provider]);\n\n return (\n <div className={`w-56 border-r border-gray-200 bg-gray-50 overflow-y-auto ${className}`}>\n <div className=\"p-3\">\n <h3 className=\"text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2\">Albums</h3>\n <button\n onClick={() => model.setAlbum(null)}\n className={`flex items-center gap-2 w-full px-3 py-2 rounded-lg text-sm transition-colors ${\n model.currentAlbum === null ? 'bg-blue-100 text-blue-700' : 'text-gray-700 hover:bg-gray-100'\n }`}\n >\n <Image size={16} />\n <span>All Media</span>\n </button>\n {albums.map(album => (\n <button\n key={album}\n onClick={() => model.setAlbum(album)}\n className={`flex items-center gap-2 w-full px-3 py-2 rounded-lg text-sm transition-colors ${\n model.currentAlbum === album ? 'bg-blue-100 text-blue-700' : 'text-gray-700 hover:bg-gray-100'\n }`}\n >\n <FolderOpen size={16} />\n <span className=\"truncate\" title={album}>{album}</span>\n </button>\n ))}\n </div>\n </div>\n );\n});\n","import React, { useEffect } from 'react';\nimport { observer } from 'mobx-react-lite';\nimport { Grid3X3, List, Clock, Search, Loader2 } from 'lucide-react';\nimport { BrowserError } from '@anymux/ui/components/browser-error';\nimport type { MediaBrowserModel } from './MediaBrowserModel';\nimport type { IMediaProvider } from './types';\nimport { MediaGrid } from './MediaGrid';\nimport { MediaList } from './MediaList';\nimport { MediaTimeline } from './MediaTimeline';\nimport { MediaPreview } from './MediaPreview';\nimport { AlbumSidebar } from './AlbumSidebar';\n\nexport interface MediaBrowserProps {\n model: MediaBrowserModel;\n provider: IMediaProvider;\n className?: string;\n showSidebar?: boolean;\n}\n\nexport const MediaBrowser = observer<MediaBrowserProps>(({ model, provider, className = '', showSidebar = true }) => {\n useEffect(() => { model.loadItems(); }, [model]);\n\n return (\n <div className={`flex h-full bg-white rounded-xl border border-gray-200 overflow-hidden ${className}`}>\n {showSidebar && <AlbumSidebar model={model} provider={provider} />}\n\n <div className=\"flex-1 flex flex-col min-w-0\">\n {/* Toolbar */}\n <div className=\"flex items-center gap-2 px-4 py-2 border-b border-gray-200\">\n <div className=\"relative flex-1 max-w-xs\">\n <Search size={16} className=\"absolute left-3 top-1/2 -translate-y-1/2 text-gray-400\" />\n <input\n type=\"text\"\n placeholder=\"Search media...\"\n value={model.searchQuery}\n onChange={e => model.search(e.target.value)}\n className=\"w-full pl-9 pr-3 py-1.5 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500\"\n />\n </div>\n\n <div className=\"flex items-center border border-gray-200 rounded-lg overflow-hidden\">\n {([['grid', Grid3X3], ['list', List], ['timeline', Clock]] as const).map(([mode, Icon]) => (\n <button\n key={mode}\n onClick={() => model.setViewMode(mode)}\n className={`p-1.5 ${model.viewMode === mode ? 'bg-blue-50 text-blue-600' : 'text-gray-500 hover:bg-gray-50'}`}\n >\n <Icon size={16} />\n </button>\n ))}\n </div>\n\n <select\n value={model.filterByType ?? ''}\n onChange={e => model.setFilter(e.target.value as 'photo' | 'video' | 'audio' || null)}\n className=\"text-sm border border-gray-200 rounded-lg px-2 py-1.5\"\n >\n <option value=\"\">All types</option>\n <option value=\"photo\">Photos</option>\n <option value=\"video\">Videos</option>\n <option value=\"audio\">Audio</option>\n </select>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-y-auto\">\n {model.loading ? (\n <div className=\"flex items-center justify-center h-64\">\n <Loader2 size={24} className=\"animate-spin text-gray-400\" />\n </div>\n ) : model.error ? (\n <BrowserError\n error={model.error}\n context=\"Media\"\n onRetry={() => model.loadItems()}\n />\n ) : model.filteredItems.length === 0 ? (\n <div className=\"flex items-center justify-center h-64 text-gray-400 text-sm\">No media found</div>\n ) : (\n <>\n {model.viewMode === 'grid' && <MediaGrid model={model} />}\n {model.viewMode === 'list' && <MediaList model={model} />}\n {model.viewMode === 'timeline' && <MediaTimeline model={model} />}\n </>\n )}\n </div>\n </div>\n\n <MediaPreview model={model} />\n </div>\n );\n});\n","import type { IMediaProvider, MediaItem, MediaType } from './types';\n\nconst ALBUMS = ['Vacation 2024', 'Family', 'Nature', 'City Lights', 'Concerts'];\nconst MEDIA_TYPES: MediaType[] = ['photo', 'video', 'audio'];\n\nfunction generateMediaItems(count = 60): MediaItem[] {\n const items: MediaItem[] = [];\n const now = Date.now();\n for (let i = 0; i < count; i++) {\n const mediaType = MEDIA_TYPES[i % 3];\n const date = new Date(now - i * 86400000 * Math.random() * 30);\n items.push({\n id: `media-${i}`,\n type: 'media',\n title: `${mediaType === 'photo' ? 'IMG' : mediaType === 'video' ? 'VID' : 'AUD'}_${String(i).padStart(4, '0')}`,\n mediaType,\n url: `https://picsum.photos/seed/${i}/800/600`,\n thumbnail: `https://picsum.photos/seed/${i}/200/200`,\n mimeType: mediaType === 'photo' ? 'image/jpeg' : mediaType === 'video' ? 'video/mp4' : 'audio/mpeg',\n width: mediaType !== 'audio' ? 800 + (i % 5) * 100 : undefined,\n height: mediaType !== 'audio' ? 600 + (i % 3) * 100 : undefined,\n duration: mediaType !== 'photo' ? 30 + i * 5 : undefined,\n album: ALBUMS[i % ALBUMS.length],\n artist: mediaType === 'audio' ? `Artist ${i % 10}` : undefined,\n createdAt: date,\n updatedAt: date,\n tags: [`tag${i % 5}`],\n });\n }\n return items;\n}\n\nexport class MockMediaProvider implements IMediaProvider {\n private items = generateMediaItems();\n\n async listItems() { return this.items; }\n\n async getItem(id: string) { return this.items.find(i => i.id === id) ?? null; }\n\n async createItem(item: Omit<MediaItem, 'id' | 'createdAt' | 'updatedAt'>) {\n const now = new Date();\n const created: MediaItem = { ...item, id: `media-${Date.now()}`, createdAt: now, updatedAt: now } as MediaItem;\n this.items.push(created);\n return created;\n }\n\n async updateItem(id: string, updates: Partial<MediaItem>) {\n const idx = this.items.findIndex(i => i.id === id);\n if (idx === -1) throw new Error('Not found');\n this.items[idx] = { ...this.items[idx], ...updates, updatedAt: new Date() };\n return this.items[idx];\n }\n\n async deleteItem(id: string) {\n this.items = this.items.filter(i => i.id !== id);\n }\n\n async search(query: string) {\n const q = query.toLowerCase();\n return this.items.filter(i => i.title.toLowerCase().includes(q));\n }\n\n async getAlbums() { return ALBUMS; }\n\n async getByAlbum(album: string) { return this.items.filter(i => i.album === album); }\n\n async getByDateRange(start: Date, end: Date) {\n return this.items.filter(i => i.createdAt >= start && i.createdAt <= end);\n }\n}\n"],"mappings":"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"}
@@ -1,123 +0,0 @@
1
- import React, { Component, ErrorInfo, ReactNode } from 'react';
2
-
3
- // AICODE-NOTE: Error boundary for catching and displaying React errors
4
- export interface ErrorBoundaryProps {
5
- children: ReactNode;
6
- fallback?: (error: Error, errorInfo: ErrorInfo) => ReactNode;
7
- onError?: (error: Error, errorInfo: ErrorInfo) => void;
8
- className?: string;
9
- }
10
-
11
- export interface ErrorBoundaryState {
12
- hasError: boolean;
13
- error: Error | null;
14
- errorInfo: ErrorInfo | null;
15
- }
16
-
17
- export class ListErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
18
- constructor(props: ErrorBoundaryProps) {
19
- super(props);
20
- this.state = {
21
- hasError: false,
22
- error: null,
23
- errorInfo: null
24
- };
25
- }
26
-
27
- static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
28
- // AICODE-NOTE: Update state to show error UI
29
- return {
30
- hasError: true,
31
- error
32
- };
33
- }
34
-
35
- componentDidCatch(error: Error, errorInfo: ErrorInfo) {
36
- // AICODE-NOTE: Log error and update state
37
- this.setState({
38
- error,
39
- errorInfo
40
- });
41
-
42
- // AICODE-NOTE: Call optional error handler
43
- if (this.props.onError) {
44
- this.props.onError(error, errorInfo);
45
- }
46
-
47
- // AICODE-NOTE: Log to console for debugging
48
- console.error('ListItemsComponent Error:', error, errorInfo);
49
- }
50
-
51
- handleRetry = () => {
52
- // AICODE-NOTE: Reset error state to retry rendering
53
- this.setState({
54
- hasError: false,
55
- error: null,
56
- errorInfo: null
57
- });
58
- };
59
-
60
- render() {
61
- if (this.state.hasError) {
62
- // AICODE-NOTE: Use custom fallback if provided
63
- if (this.props.fallback && this.state.error && this.state.errorInfo) {
64
- return this.props.fallback(this.state.error, this.state.errorInfo);
65
- }
66
-
67
- // AICODE-NOTE: Default error UI
68
- return (
69
- <div className={`flex items-center justify-center p-8 ${this.props.className || ''}`}>
70
- <div className="text-center space-y-4 max-w-md">
71
- <div className="text-destructive">
72
- <svg
73
- className="w-12 h-12 mx-auto mb-4"
74
- fill="none"
75
- stroke="currentColor"
76
- viewBox="0 0 24 24"
77
- >
78
- <path
79
- strokeLinecap="round"
80
- strokeLinejoin="round"
81
- strokeWidth={2}
82
- d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"
83
- />
84
- </svg>
85
- </div>
86
-
87
- <div>
88
- <h3 className="text-lg font-semibold text-foreground mb-2">
89
- Something went wrong
90
- </h3>
91
- <p className="text-sm text-muted-foreground mb-4">
92
- An error occurred while displaying the list items.
93
- </p>
94
- </div>
95
-
96
- <div className="space-y-2">
97
- <button
98
- onClick={this.handleRetry}
99
- className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
100
- >
101
- Try Again
102
- </button>
103
-
104
- {typeof window !== 'undefined' && this.state.error && (
105
- <details className="text-left">
106
- <summary className="text-xs text-muted-foreground cursor-pointer hover:text-foreground">
107
- Show Error Details
108
- </summary>
109
- <pre className="text-xs text-destructive mt-2 p-2 bg-muted rounded overflow-auto max-h-32">
110
- {this.state.error.toString()}
111
- {this.state.errorInfo?.componentStack}
112
- </pre>
113
- </details>
114
- )}
115
- </div>
116
- </div>
117
- </div>
118
- );
119
- }
120
-
121
- return this.props.children;
122
- }
123
- }