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