BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
at main 166 lines 4.6 kB view raw
1import type { 2 ColumnWidth, 3 DiagnosticsColumnConfig, 4 ExplorerColumnConfig, 5 FeedColumnConfig, 6 ProfileColumnConfig, 7 SearchColumnConfig, 8} from "$/lib/api/types/columns"; 9import { getFeedName } from "$/lib/feeds"; 10import type { FeedGeneratorView, SavedFeedItem } from "$/lib/types"; 11 12export const COLUMN_WIDTH_PX: Record<ColumnWidth, number> = { narrow: 320, standard: 420, wide: 560 }; 13 14export type ResolvedFeedColumn = { 15 config: FeedColumnConfig; 16 feed: SavedFeedItem; 17 generator?: FeedGeneratorView; 18 title: string; 19}; 20 21export function cycleWidth(current: ColumnWidth): ColumnWidth { 22 switch (current) { 23 case "narrow": { 24 return "standard"; 25 } 26 case "standard": { 27 return "wide"; 28 } 29 case "wide": { 30 return "narrow"; 31 } 32 } 33} 34 35function isFeedType(value: unknown): value is FeedColumnConfig["feedType"] { 36 return value === "timeline" || value === "feed" || value === "list"; 37} 38 39export function parseFeedConfig(config: string): FeedColumnConfig | null { 40 try { 41 const parsed = JSON.parse(config) as Record<string, unknown>; 42 if (!parsed || typeof parsed !== "object") { 43 return null; 44 } 45 46 if (!isFeedType(parsed.feedType) || typeof parsed.feedUri !== "string") { 47 return null; 48 } 49 50 if (parsed.title !== undefined && parsed.title !== null && typeof parsed.title !== "string") { 51 return null; 52 } 53 54 return { feedType: parsed.feedType, feedUri: parsed.feedUri, title: parsed.title as string | null | undefined }; 55 } catch { 56 return null; 57 } 58} 59 60function parseExplorerConfig(config: string): ExplorerColumnConfig | null { 61 try { 62 const parsed = JSON.parse(config) as unknown; 63 if (parsed && typeof parsed === "object" && "targetUri" in parsed) { 64 return parsed as ExplorerColumnConfig; 65 } 66 return null; 67 } catch { 68 return null; 69 } 70} 71 72export function parseDiagnosticsConfig(config: string): DiagnosticsColumnConfig | null { 73 try { 74 const parsed = JSON.parse(config) as unknown; 75 if (parsed && typeof parsed === "object" && "did" in parsed) { 76 return parsed as DiagnosticsColumnConfig; 77 } 78 return null; 79 } catch { 80 return null; 81 } 82} 83 84export function parseSearchConfig(config: string): SearchColumnConfig | null { 85 try { 86 const parsed = JSON.parse(config) as unknown; 87 if (parsed && typeof parsed === "object" && "mode" in parsed && "query" in parsed) { 88 return parsed as SearchColumnConfig; 89 } 90 return null; 91 } catch { 92 return null; 93 } 94} 95 96export function parseProfileConfig(config: string): ProfileColumnConfig | null { 97 try { 98 const parsed = JSON.parse(config) as unknown; 99 if (parsed && typeof parsed === "object" && "actor" in parsed) { 100 return parsed as ProfileColumnConfig; 101 } 102 return null; 103 } catch { 104 return null; 105 } 106} 107 108function feedConfigToSavedFeedItem(config: FeedColumnConfig): SavedFeedItem { 109 return { 110 id: config.feedUri || "following", 111 pinned: false, 112 type: config.feedType, 113 value: config.feedUri || "following", 114 }; 115} 116 117export function resolveFeedColumn( 118 config: FeedColumnConfig, 119 options: { generator?: FeedGeneratorView; savedFeedTitle?: string | null } = {}, 120): ResolvedFeedColumn { 121 const feed = feedConfigToSavedFeedItem(config); 122 const hydratedTitle = options.generator?.displayName || config.title?.trim() || options.savedFeedTitle?.trim(); 123 124 return { config, feed, generator: options.generator, title: getFeedName(feed, hydratedTitle) }; 125} 126 127export function columnTitle(kind: string, config: string): string { 128 switch (kind) { 129 case "feed": { 130 return "Feed"; 131 } 132 case "explorer": { 133 const parsed = parseExplorerConfig(config); 134 if (!parsed?.targetUri) return "Explorer"; 135 return parsed.targetUri.length > 30 ? `${parsed.targetUri.slice(0, 30)}` : parsed.targetUri; 136 } 137 case "diagnostics": { 138 const parsed = parseDiagnosticsConfig(config); 139 return parsed?.did ?? "Diagnostics"; 140 } 141 case "messages": { 142 return "Messages"; 143 } 144 case "search": { 145 const parsed = parseSearchConfig(config); 146 const query = parsed?.query.trim(); 147 return query ? `Search: ${query}` : "Search"; 148 } 149 case "profile": { 150 const parsed = parseProfileConfig(config); 151 return parsed?.displayName?.trim() || parsed?.handle?.trim() || parsed?.actor || "Profile"; 152 } 153 default: { 154 return "Column"; 155 } 156 } 157} 158 159export type FeedPickerSelection = { feed: SavedFeedItem; title: string }; 160 161export type ProfileSelection = { 162 actor: string; 163 did?: string | null; 164 displayName?: string | null; 165 handle?: string | null; 166};