addons/isl/src/BookmarksData.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {StableLocationData} from './types';
b69ab319
b69ab3110import {atom} from 'jotai';
b69ab3111import {tracker} from './analytics';
b69ab3112import serverAPI from './ClientToServerAPI';
b69ab3113import {localStorageBackedAtom, readAtom, writeAtom} from './jotaiUtils';
b69ab3114import {latestCommits} from './serverAPIState';
b69ab3115import {registerDisposable} from './utils';
b69ab3116
b69ab3117export const REMOTE_MASTER_BOOKMARK = 'remote/master';
b69ab3118
b69ab3119export type MasterBookmarkVisibility = 'auto' | 'show' | 'hide';
b69ab3120
b69ab3121export type BookmarksData = {
b69ab3122 /** These bookmarks should be hidden from the automatic set of remote bookmarks */
b69ab3123 hiddenRemoteBookmarks: Array<string>;
b69ab3124 /** These stables should be requested by the server to fetch additional stables */
b69ab3125 additionalStables?: Array<string>;
b69ab3126 /** Whether to use the recommended bookmark instead of user-selected bookmarks */
b69ab3127 useRecommendedBookmark?: boolean;
b69ab3128 /**
b69ab3129 * Master bookmark visibility setting.
b69ab3130 * - 'auto': Use sitevar config to decide (default when GK enabled)
b69ab3131 * - 'show': Always show master bookmark (user override)
b69ab3132 * - 'hide': Always hide master bookmark (user override)
b69ab3133 */
b69ab3134 masterBookmarkVisibility?: MasterBookmarkVisibility;
b69ab3135};
b69ab3136export const bookmarksDataStorage = localStorageBackedAtom<BookmarksData>('isl.bookmarks', {
b69ab3137 hiddenRemoteBookmarks: [],
b69ab3138 additionalStables: [],
b69ab3139 useRecommendedBookmark: true,
b69ab3140});
b69ab3141export const hiddenRemoteBookmarksAtom = atom(get => {
b69ab3142 return new Set(get(bookmarksDataStorage).hiddenRemoteBookmarks);
b69ab3143});
b69ab3144
b69ab3145/** Result of fetch from the server. Stables are automatically included in list of commits */
b69ab3146export const fetchedStablesAtom = atom<StableLocationData | undefined>(undefined);
b69ab3147
b69ab3148export function addManualStable(stable: string) {
b69ab3149 // save this as a persisted stable we'd like to always fetch going forward
b69ab3150 writeAtom(bookmarksDataStorage, data => ({
b69ab3151 ...data,
b69ab3152 additionalStables: [...(data.additionalStables ?? []), stable],
b69ab3153 }));
b69ab3154 // write the stable to the fetched state, so it shows a loading spinner
b69ab3155 writeAtom(fetchedStablesAtom, last =>
b69ab3156 last
b69ab3157 ? {
b69ab3158 ...last,
b69ab3159 manual: {...(last?.manual ?? {}), [stable]: null},
b69ab3160 }
b69ab3161 : undefined,
b69ab3162 );
b69ab3163 // refetch using the new manual stable
b69ab3164 fetchStableLocations();
b69ab3165}
b69ab3166
b69ab3167export function removeManualStable(stable: string) {
b69ab3168 writeAtom(bookmarksDataStorage, data => ({
b69ab3169 ...data,
b69ab3170 additionalStables: (data.additionalStables ?? []).filter(s => s !== stable),
b69ab3171 }));
b69ab3172 writeAtom(fetchedStablesAtom, last => {
b69ab3173 if (last) {
b69ab3174 const manual = {...(last.manual ?? {})};
b69ab3175 delete manual[stable];
b69ab3176 return {...last, manual};
b69ab3177 }
b69ab3178 });
b69ab3179 // refetch without this stable, so it's excluded from `sl log`
b69ab3180 fetchStableLocations();
b69ab3181}
b69ab3182
b69ab3183registerDisposable(
b69ab3184 serverAPI,
b69ab3185 serverAPI.onMessageOfType('fetchedStables', data => {
b69ab3186 writeAtom(fetchedStablesAtom, data.stables);
b69ab3187 }),
b69ab3188 import.meta.hot,
b69ab3189);
b69ab3190fetchStableLocations(); // fetch on startup
b69ab3191
b69ab3192registerDisposable(
b69ab3193 serverAPI,
b69ab3194 serverAPI.onMessageOfType('fetchedRecommendedBookmarks', data => {
b69ab3195 writeAtom(recommendedBookmarksAtom, new Set(data.bookmarks));
b69ab3196
b69ab3197 const bookmarksData = readAtom(bookmarksDataStorage);
b69ab3198 tracker.track('RecommendedBookmarksStatus', {
b69ab3199 extras: {
b69ab31100 enabled: bookmarksData.useRecommendedBookmark ?? false,
b69ab31101 recommendedBookmarks: data.bookmarks,
b69ab31102 },
b69ab31103 });
b69ab31104 }),
b69ab31105 import.meta.hot,
b69ab31106);
b69ab31107
b69ab31108export function fetchStableLocations() {
b69ab31109 const data = readAtom(bookmarksDataStorage);
b69ab31110 const additionalStables = data.additionalStables ?? [];
b69ab31111 serverAPI.postMessage({type: 'fetchAndSetStables', additionalStables});
b69ab31112}
b69ab31113
b69ab31114export const remoteBookmarks = atom(get => {
b69ab31115 // Note: `latestDag` will have already filtered out hidden bookmarks,
b69ab31116 // so we need to use latestCommits, which is not filtered.
b69ab31117 const commits = get(latestCommits).filter(commit => commit.phase === 'public');
b69ab31118 commits.sort((a, b) => b.date.valueOf() - a.date.valueOf());
b69ab31119 return commits.flatMap(commit => commit.remoteBookmarks);
b69ab31120});
b69ab31121
b69ab31122/**
b69ab31123 * For determining if reminders to use recommended bookmarks should be shown
b69ab31124 */
b69ab31125export const recommendedBookmarksReminder = localStorageBackedAtom<{
b69ab31126 shouldShow: boolean;
b69ab31127 lastShown: number;
b69ab31128}>('isl.recommended-bookmarks-reminder', {
b69ab31129 shouldShow: true,
b69ab31130 lastShown: 0,
b69ab31131});
b69ab31132
b69ab31133/**
b69ab31134 * For determining if recommended bookmarks onboarding tip should be shown
b69ab31135 */
b69ab31136export const recommendedBookmarksOnboarding = localStorageBackedAtom<boolean>(
b69ab31137 'isl.recommended-bookmarks-onboarding',
b69ab31138 true,
b69ab31139);
b69ab31140
b69ab31141export const recommendedBookmarksAtom = atom<Set<string>>(new Set<string>());
b69ab31142
b69ab31143/** Checks if recommended bookmarks are available in remoteBookmarks */
b69ab31144export const recommendedBookmarksAvailableAtom = atom(get => {
b69ab31145 const recommendedBookmarks = get(recommendedBookmarksAtom);
b69ab31146 const allRemoteBookmarks = get(remoteBookmarks);
b69ab31147 return (
b69ab31148 recommendedBookmarks.size > 0 &&
b69ab31149 [...recommendedBookmarks].some(b => allRemoteBookmarks.includes(b))
b69ab31150 );
b69ab31151});