| 1 | import { useAtomValue } from "@effect/atom-react"; |
| 2 | import { resolveFilesystemReadAccess } from "@t3tools/client-runtime/state/filesystem"; |
| 3 | import { |
| 4 | assetUrlStateFromResult, |
| 5 | blockedAssetUrlState, |
| 6 | createAssetEnvironmentAtoms, |
| 7 | createProjectFaviconUrlAtomFamily, |
| 8 | EMPTY_ASSET_URL_ATOM, |
| 9 | } from "@t3tools/client-runtime/state/assets"; |
| 10 | import type { AssetResource, EnvironmentId } from "@t3tools/contracts"; |
| 11 | import { useCallback } from "react"; |
| 12 | |
| 13 | import { connectionAtomRuntime } from "../connection/runtime"; |
| 14 | import { projectFaviconDatabaseCache } from "../lib/projectFaviconDatabaseCache"; |
| 15 | import { type AssetUrlState, deriveAssetUrlState } from "./asset-url-state"; |
| 16 | import { environmentProjectCloneListAtom } from "./projectClones"; |
| 17 | import { environmentSession, usePreparedConnection } from "./session"; |
| 18 | import { useEnvironmentPresentation } from "./presentation"; |
| 19 | import { useEnvironmentQuery } from "./query"; |
| 20 | import { useAtomQueryRunner } from "./use-atom-query-runner"; |
| 21 | |
| 22 | export type { AssetUrlFailureReason, AssetUrlState } from "./asset-url-state"; |
| 23 | |
| 24 | export const assetEnvironment = createAssetEnvironmentAtoms(connectionAtomRuntime); |
| 25 | |
| 26 | export const projectFaviconUrlAtom = createProjectFaviconUrlAtomFamily({ |
| 27 | imageCache: projectFaviconDatabaseCache, |
| 28 | createUrl: assetEnvironment.createUrl, |
| 29 | preparedConnection: environmentSession.preparedConnectionValueAtom, |
| 30 | projectClones: environmentProjectCloneListAtom, |
| 31 | }); |
| 32 | |
| 33 | export function useAssetUrlState( |
| 34 | environmentId: EnvironmentId | null, |
| 35 | resource: AssetResource | null, |
| 36 | ): AssetUrlState { |
| 37 | const fileAccessSession = useEnvironmentQuery( |
| 38 | environmentId === null ? null : environmentSession.sessionStateAtom(environmentId), |
| 39 | ); |
| 40 | const fileEnvironment = useEnvironmentPresentation(environmentId); |
| 41 | const fileAccess = resolveFilesystemReadAccess({ |
| 42 | isCatalogReady: fileEnvironment.isReady, |
| 43 | connection: fileEnvironment.presentation?.connection ?? null, |
| 44 | session: fileAccessSession.data, |
| 45 | sessionError: fileAccessSession.error, |
| 46 | }); |
| 47 | const blocked = blockedAssetUrlState(resource, fileAccess); |
| 48 | const preparedConnection = usePreparedConnection(environmentId); |
| 49 | const connectionPhase = fileEnvironment.presentation?.connection.phase ?? "available"; |
| 50 | const result = useAtomValue( |
| 51 | blocked !== null || environmentId === null || resource === null |
| 52 | ? EMPTY_ASSET_URL_ATOM |
| 53 | : assetEnvironment.createUrl({ environmentId, input: { resource } }), |
| 54 | ); |
| 55 | const shared = |
| 56 | blocked ?? |
| 57 | assetUrlStateFromResult( |
| 58 | result, |
| 59 | preparedConnection._tag === "Some" ? preparedConnection.value.httpBaseUrl : null, |
| 60 | ); |
| 61 | return deriveAssetUrlState({ |
| 62 | connectionPhase, |
| 63 | // A failure left over from an outage is re-queried as soon as the |
| 64 | // connection returns. While that re-query is in flight it is not a verdict |
| 65 | // on the file, so it reads as loading rather than a false "unavailable". |
| 66 | shared: shared._tag === "Failure" && result.waiting ? { _tag: "Loading" } : shared, |
| 67 | }); |
| 68 | } |
| 69 | |
| 70 | export function useAssetUrl( |
| 71 | environmentId: EnvironmentId | null, |
| 72 | resource: AssetResource | null, |
| 73 | ): string | null { |
| 74 | const state = useAssetUrlState(environmentId, resource); |
| 75 | return state._tag === "Success" ? state.url : null; |
| 76 | } |
| 77 | |
| 78 | /** Explicit playback and sharing must reauthorize files that may have been replaced on disk. */ |
| 79 | export function useRefreshAssetUrl( |
| 80 | environmentId: EnvironmentId | null, |
| 81 | resource: AssetResource | null, |
| 82 | ): () => Promise<string | null> { |
| 83 | const connection = usePreparedConnection(environmentId); |
| 84 | const httpBaseUrl = connection._tag === "Some" ? connection.value.httpBaseUrl : null; |
| 85 | const createUrl = useAtomQueryRunner(assetEnvironment.createUrl, { |
| 86 | refresh: true, |
| 87 | reportFailure: false, |
| 88 | }); |
| 89 | return useCallback(async () => { |
| 90 | if (environmentId === null || resource === null || httpBaseUrl === null) return null; |
| 91 | const state = assetUrlStateFromResult( |
| 92 | await createUrl({ environmentId, input: { resource } }), |
| 93 | httpBaseUrl, |
| 94 | ); |
| 95 | return state._tag === "Success" ? state.url : null; |
| 96 | }, [createUrl, environmentId, httpBaseUrl, resource]); |
| 97 | } |