5.0 KB152 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {StableLocationData} from './types';
9
10import {atom} from 'jotai';
11import {tracker} from './analytics';
12import serverAPI from './ClientToServerAPI';
13import {localStorageBackedAtom, readAtom, writeAtom} from './jotaiUtils';
14import {latestCommits} from './serverAPIState';
15import {registerDisposable} from './utils';
16
17export const REMOTE_MASTER_BOOKMARK = 'remote/master';
18
19export type MasterBookmarkVisibility = 'auto' | 'show' | 'hide';
20
21export type BookmarksData = {
22 /** These bookmarks should be hidden from the automatic set of remote bookmarks */
23 hiddenRemoteBookmarks: Array<string>;
24 /** These stables should be requested by the server to fetch additional stables */
25 additionalStables?: Array<string>;
26 /** Whether to use the recommended bookmark instead of user-selected bookmarks */
27 useRecommendedBookmark?: boolean;
28 /**
29 * Master bookmark visibility setting.
30 * - 'auto': Use sitevar config to decide (default when GK enabled)
31 * - 'show': Always show master bookmark (user override)
32 * - 'hide': Always hide master bookmark (user override)
33 */
34 masterBookmarkVisibility?: MasterBookmarkVisibility;
35};
36export const bookmarksDataStorage = localStorageBackedAtom<BookmarksData>('isl.bookmarks', {
37 hiddenRemoteBookmarks: [],
38 additionalStables: [],
39 useRecommendedBookmark: true,
40});
41export const hiddenRemoteBookmarksAtom = atom(get => {
42 return new Set(get(bookmarksDataStorage).hiddenRemoteBookmarks);
43});
44
45/** Result of fetch from the server. Stables are automatically included in list of commits */
46export const fetchedStablesAtom = atom<StableLocationData | undefined>(undefined);
47
48export function addManualStable(stable: string) {
49 // save this as a persisted stable we'd like to always fetch going forward
50 writeAtom(bookmarksDataStorage, data => ({
51 ...data,
52 additionalStables: [...(data.additionalStables ?? []), stable],
53 }));
54 // write the stable to the fetched state, so it shows a loading spinner
55 writeAtom(fetchedStablesAtom, last =>
56 last
57 ? {
58 ...last,
59 manual: {...(last?.manual ?? {}), [stable]: null},
60 }
61 : undefined,
62 );
63 // refetch using the new manual stable
64 fetchStableLocations();
65}
66
67export function removeManualStable(stable: string) {
68 writeAtom(bookmarksDataStorage, data => ({
69 ...data,
70 additionalStables: (data.additionalStables ?? []).filter(s => s !== stable),
71 }));
72 writeAtom(fetchedStablesAtom, last => {
73 if (last) {
74 const manual = {...(last.manual ?? {})};
75 delete manual[stable];
76 return {...last, manual};
77 }
78 });
79 // refetch without this stable, so it's excluded from `sl log`
80 fetchStableLocations();
81}
82
83registerDisposable(
84 serverAPI,
85 serverAPI.onMessageOfType('fetchedStables', data => {
86 writeAtom(fetchedStablesAtom, data.stables);
87 }),
88 import.meta.hot,
89);
90fetchStableLocations(); // fetch on startup
91
92registerDisposable(
93 serverAPI,
94 serverAPI.onMessageOfType('fetchedRecommendedBookmarks', data => {
95 writeAtom(recommendedBookmarksAtom, new Set(data.bookmarks));
96
97 const bookmarksData = readAtom(bookmarksDataStorage);
98 tracker.track('RecommendedBookmarksStatus', {
99 extras: {
100 enabled: bookmarksData.useRecommendedBookmark ?? false,
101 recommendedBookmarks: data.bookmarks,
102 },
103 });
104 }),
105 import.meta.hot,
106);
107
108export function fetchStableLocations() {
109 const data = readAtom(bookmarksDataStorage);
110 const additionalStables = data.additionalStables ?? [];
111 serverAPI.postMessage({type: 'fetchAndSetStables', additionalStables});
112}
113
114export const remoteBookmarks = atom(get => {
115 // Note: `latestDag` will have already filtered out hidden bookmarks,
116 // so we need to use latestCommits, which is not filtered.
117 const commits = get(latestCommits).filter(commit => commit.phase === 'public');
118 commits.sort((a, b) => b.date.valueOf() - a.date.valueOf());
119 return commits.flatMap(commit => commit.remoteBookmarks);
120});
121
122/**
123 * For determining if reminders to use recommended bookmarks should be shown
124 */
125export const recommendedBookmarksReminder = localStorageBackedAtom<{
126 shouldShow: boolean;
127 lastShown: number;
128}>('isl.recommended-bookmarks-reminder', {
129 shouldShow: true,
130 lastShown: 0,
131});
132
133/**
134 * For determining if recommended bookmarks onboarding tip should be shown
135 */
136export const recommendedBookmarksOnboarding = localStorageBackedAtom<boolean>(
137 'isl.recommended-bookmarks-onboarding',
138 true,
139);
140
141export const recommendedBookmarksAtom = atom<Set<string>>(new Set<string>());
142
143/** Checks if recommended bookmarks are available in remoteBookmarks */
144export const recommendedBookmarksAvailableAtom = atom(get => {
145 const recommendedBookmarks = get(recommendedBookmarksAtom);
146 const allRemoteBookmarks = get(remoteBookmarks);
147 return (
148 recommendedBookmarks.size > 0 &&
149 [...recommendedBookmarks].some(b => allRemoteBookmarks.includes(b))
150 );
151});
152