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