| 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 {Atom} from 'jotai'; |
| b69ab31 | | | 9 | import type {Json} from 'shared/typeUtils'; |
| b69ab31 | | | 10 | import type {AtomFamilyWeak} from '../jotaiUtils'; |
| b69ab31 | | | 11 | |
| b69ab31 | | | 12 | import {SelfUpdate} from 'shared/immutableExt'; |
| b69ab31 | | | 13 | import {editedCommitMessages} from '../CommitInfoView/CommitInfoState'; |
| b69ab31 | | | 14 | import {latestSuccessorsMapAtom} from '../SuccessionTracker'; |
| b69ab31 | | | 15 | import {allDiffSummaries, codeReviewProvider} from '../codeReview/CodeReviewInfo'; |
| b69ab31 | | | 16 | import {readAtom} from '../jotaiUtils'; |
| b69ab31 | | | 17 | import {operationBeingPreviewed, operationList, queuedOperations} from '../operationsState'; |
| b69ab31 | | | 18 | import {uncommittedSelection} from '../partialSelection'; |
| b69ab31 | | | 19 | import {dagWithPreviews} from '../previews'; |
| b69ab31 | | | 20 | import {selectedCommits} from '../selection'; |
| b69ab31 | | | 21 | import { |
| b69ab31 | | | 22 | latestCommitsData, |
| b69ab31 | | | 23 | latestUncommittedChangesData, |
| b69ab31 | | | 24 | mergeConflicts, |
| b69ab31 | | | 25 | repositoryData, |
| b69ab31 | | | 26 | submodulesByRoot, |
| b69ab31 | | | 27 | } from '../serverAPIState'; |
| b69ab31 | | | 28 | |
| b69ab31 | | | 29 | export type UIStateSnapshot = {[key: string]: Json}; |
| b69ab31 | | | 30 | export type AtomsState = {[key: string]: unknown}; |
| b69ab31 | | | 31 | |
| b69ab31 | | | 32 | type AtomOrFamily = Atom<unknown> | AtomFamilyWeak<string, Atom<unknown>>; |
| b69ab31 | | | 33 | |
| b69ab31 | | | 34 | function listInterestingAtoms(): Array<AtomOrFamily> { |
| b69ab31 | | | 35 | return [ |
| b69ab31 | | | 36 | allDiffSummaries, |
| b69ab31 | | | 37 | codeReviewProvider, |
| b69ab31 | | | 38 | repositoryData, |
| b69ab31 | | | 39 | latestCommitsData, |
| b69ab31 | | | 40 | latestSuccessorsMapAtom, |
| b69ab31 | | | 41 | latestUncommittedChangesData, |
| b69ab31 | | | 42 | dagWithPreviews, |
| b69ab31 | | | 43 | mergeConflicts, |
| b69ab31 | | | 44 | operationBeingPreviewed, |
| b69ab31 | | | 45 | operationList, |
| b69ab31 | | | 46 | queuedOperations, |
| b69ab31 | | | 47 | selectedCommits, |
| b69ab31 | | | 48 | uncommittedSelection, |
| b69ab31 | | | 49 | // These are atomFamilies. |
| b69ab31 | | | 50 | editedCommitMessages, |
| b69ab31 | | | 51 | submodulesByRoot, |
| b69ab31 | | | 52 | ]; |
| b69ab31 | | | 53 | } |
| b69ab31 | | | 54 | |
| b69ab31 | | | 55 | /** Read all "interesting" atoms and returns a single object that contains them all. */ |
| b69ab31 | | | 56 | export function readInterestingAtoms(): AtomsState { |
| b69ab31 | | | 57 | return Object.fromEntries( |
| b69ab31 | | | 58 | listInterestingAtoms().map(a => [a.debugLabel ?? a.toString(), readAtomOrFamily(a)]), |
| b69ab31 | | | 59 | ); |
| b69ab31 | | | 60 | } |
| b69ab31 | | | 61 | |
| b69ab31 | | | 62 | /** Try to serialize the `state` so they can be represented in plain JSON. */ |
| b69ab31 | | | 63 | export function serializeAtomsState(state: AtomsState): UIStateSnapshot { |
| b69ab31 | | | 64 | const newEntries = Object.entries(state).map(([key, value]) => { |
| b69ab31 | | | 65 | return [key, serialize(value as Serializable)]; |
| b69ab31 | | | 66 | }); |
| b69ab31 | | | 67 | return Object.fromEntries(newEntries); |
| b69ab31 | | | 68 | } |
| b69ab31 | | | 69 | |
| b69ab31 | | | 70 | function readAtomOrFamily(atomOrFamily: AtomOrFamily): unknown { |
| b69ab31 | | | 71 | if (typeof atomOrFamily === 'function') { |
| b69ab31 | | | 72 | // atomFamily. Read its values from weakCache. |
| b69ab31 | | | 73 | const result = new Map<string, unknown>(); |
| b69ab31 | | | 74 | for (const [key, weak] of atomOrFamily.weakCache.entries()) { |
| b69ab31 | | | 75 | const value = weak.deref(); |
| b69ab31 | | | 76 | result.set(key, value === undefined ? undefined : readAtom(value)); |
| b69ab31 | | | 77 | } |
| b69ab31 | | | 78 | return result; |
| b69ab31 | | | 79 | } else { |
| b69ab31 | | | 80 | return readAtom(atomOrFamily); |
| b69ab31 | | | 81 | } |
| b69ab31 | | | 82 | } |
| b69ab31 | | | 83 | |
| b69ab31 | | | 84 | type Serializable = Json | {toJSON: () => Serializable}; |
| b69ab31 | | | 85 | |
| b69ab31 | | | 86 | function serialize(initialArg: Serializable): Json { |
| b69ab31 | | | 87 | let arg = initialArg; |
| b69ab31 | | | 88 | |
| b69ab31 | | | 89 | const isObject = arg != null && typeof arg === 'object'; |
| b69ab31 | | | 90 | |
| b69ab31 | | | 91 | // Extract debug state provided by the object. This applies to both immutable and regular objects. |
| b69ab31 | | | 92 | // This needs to happen before unwrapping SelfUpdate. |
| b69ab31 | | | 93 | let debugState = null; |
| b69ab31 | | | 94 | if (isObject) { |
| b69ab31 | | | 95 | // If the object defines `getDebugState`. Call it to get more (easier to visualize) states. |
| b69ab31 | | | 96 | const maybeGetDebugState = (arg as {getDebugState?: () => {[key: string]: Json}}).getDebugState; |
| b69ab31 | | | 97 | if (maybeGetDebugState != null) { |
| b69ab31 | | | 98 | debugState = maybeGetDebugState.call(arg); |
| b69ab31 | | | 99 | } |
| b69ab31 | | | 100 | } |
| b69ab31 | | | 101 | |
| b69ab31 | | | 102 | // Unwrap SelfUpdate types. |
| b69ab31 | | | 103 | if (arg instanceof SelfUpdate) { |
| b69ab31 | | | 104 | arg = arg.inner; |
| b69ab31 | | | 105 | } |
| b69ab31 | | | 106 | |
| b69ab31 | | | 107 | // Convert known immutable types. |
| b69ab31 | | | 108 | if (arg != null && typeof arg === 'object') { |
| b69ab31 | | | 109 | const maybeToJSON = (arg as {toJSON?: () => Json}).toJSON; |
| b69ab31 | | | 110 | if (maybeToJSON !== undefined) { |
| b69ab31 | | | 111 | arg = maybeToJSON.call(arg); |
| b69ab31 | | | 112 | if (typeof arg === 'object' && debugState != null) { |
| b69ab31 | | | 113 | arg = {...debugState, ...arg}; |
| b69ab31 | | | 114 | } |
| b69ab31 | | | 115 | } |
| b69ab31 | | | 116 | } |
| b69ab31 | | | 117 | |
| b69ab31 | | | 118 | if (arg === undefined) { |
| b69ab31 | | | 119 | return null; |
| b69ab31 | | | 120 | } |
| b69ab31 | | | 121 | |
| b69ab31 | | | 122 | if ( |
| b69ab31 | | | 123 | typeof arg === 'number' || |
| b69ab31 | | | 124 | typeof arg === 'boolean' || |
| b69ab31 | | | 125 | typeof arg === 'string' || |
| b69ab31 | | | 126 | arg === null |
| b69ab31 | | | 127 | ) { |
| b69ab31 | | | 128 | return arg; |
| b69ab31 | | | 129 | } |
| b69ab31 | | | 130 | |
| b69ab31 | | | 131 | if (arg instanceof Map) { |
| b69ab31 | | | 132 | return Array.from(arg.entries()).map(([key, val]) => [serialize(key), serialize(val)]); |
| b69ab31 | | | 133 | } else if (arg instanceof Set) { |
| b69ab31 | | | 134 | return Array.from(arg.values()).map(serialize); |
| b69ab31 | | | 135 | } else if (arg instanceof Error) { |
| b69ab31 | | | 136 | return {message: arg.message ?? null, stack: arg.stack ?? null}; |
| b69ab31 | | | 137 | } else if (arg instanceof Date) { |
| b69ab31 | | | 138 | return `Date: ${arg.valueOf()}`; |
| b69ab31 | | | 139 | } else if (Array.isArray(arg)) { |
| b69ab31 | | | 140 | return arg.map(a => serialize(a)); |
| b69ab31 | | | 141 | } else if (typeof arg === 'object') { |
| b69ab31 | | | 142 | const newObj: Json = debugState ?? {}; |
| b69ab31 | | | 143 | for (const [propertyName, propertyValue] of Object.entries(arg)) { |
| b69ab31 | | | 144 | // Skip functions. |
| b69ab31 | | | 145 | if (typeof propertyValue === 'function') { |
| b69ab31 | | | 146 | continue; |
| b69ab31 | | | 147 | } |
| b69ab31 | | | 148 | newObj[propertyName] = serialize(propertyValue); |
| b69ab31 | | | 149 | } |
| b69ab31 | | | 150 | |
| b69ab31 | | | 151 | return newObj; |
| b69ab31 | | | 152 | } |
| b69ab31 | | | 153 | |
| b69ab31 | | | 154 | // Return a dummy value instead of throw so if an item in a container is "bad", |
| b69ab31 | | | 155 | // it does not turn the whole container into an error. |
| b69ab31 | | | 156 | return `<unserializable: ${arg}>`; |
| b69ab31 | | | 157 | } |