import { useAtomValue } from "@effect/atom-react";import { type EnvironmentConnectionPhase, presentConnectionState,} from "@t3tools/client-runtime/connection";import { assetUrlStateFromResult, createAssetEnvironmentAtoms, createProjectFaviconUrlAtomFamily, EMPTY_ASSET_URL_ATOM,} from "@t3tools/client-runtime/state/assets";import type { AssetResource, EnvironmentId } from "@t3tools/contracts";import * as Option from "effect/Option";import { AsyncResult, Atom } from "effect/unstable/reactivity";import { useCallback } from "react"; import { environmentCatalog } from "../connection/catalog";import { connectionAtomRuntime } from "../connection/runtime";import { projectFaviconCache } from "../lib/projectFaviconCache";import { type AssetUrlState, deriveAssetUrlState } from "./asset-url-state";import { environmentSession, usePreparedConnection } from "./session";import { useAtomQueryRunner } from "./use-atom-query-runner"; export type { AssetUrlFailureReason, AssetUrlState } from "./asset-url-state"; export const assetEnvironment = createAssetEnvironmentAtoms(connectionAtomRuntime); export const projectFaviconUrlAtom = createProjectFaviconUrlAtomFamily({ imageCache: projectFaviconCache, createUrl: assetEnvironment.createUrl, preparedConnection: environmentSession.preparedConnectionValueAtom,}); const EMPTY_CONNECTION_STATE_ATOM = Atom.make(AsyncResult.initial<never, never>(false)).pipe( Atom.withLabel("mobile-asset-connection-state:empty"),); function useConnectionPhase(environmentId: EnvironmentId | null): EnvironmentConnectionPhase { const state = useAtomValue( environmentId === null ? EMPTY_CONNECTION_STATE_ATOM : environmentCatalog.stateAtom(environmentId), ); const value = Option.getOrNull(AsyncResult.value(state)); return value === null ? "available" : presentConnectionState(value).phase;} export function useAssetUrlState( environmentId: EnvironmentId | null, resource: AssetResource | null,): AssetUrlState { const preparedConnection = usePreparedConnection(environmentId); const connectionPhase = useConnectionPhase(environmentId); const result = useAtomValue( environmentId === null || resource === null ? EMPTY_ASSET_URL_ATOM : assetEnvironment.createUrl({ environmentId, input: { resource } }), ); const shared = assetUrlStateFromResult( result, preparedConnection._tag === "Some" ? preparedConnection.value.httpBaseUrl : null, ); return deriveAssetUrlState({ connectionPhase, // A failure left over from an outage is re-queried as soon as the // connection returns. While that re-query is in flight it is not a verdict // on the file, so it reads as loading rather than a false "unavailable". shared: shared._tag === "Failure" && result.waiting ? { _tag: "Loading" } : shared, });} export function useAssetUrl( environmentId: EnvironmentId | null, resource: AssetResource | null,): string | null { const state = useAssetUrlState(environmentId, resource); return state._tag === "Success" ? state.url : null;} /** Explicit playback and sharing must reauthorize files that may have been replaced on disk. */export function useRefreshAssetUrl( environmentId: EnvironmentId | null, resource: AssetResource | null,): () => Promise<string | null> { const connection = usePreparedConnection(environmentId); const httpBaseUrl = connection._tag === "Some" ? connection.value.httpBaseUrl : null; const createUrl = useAtomQueryRunner(assetEnvironment.createUrl, { refresh: true, reportFailure: false, }); return useCallback(async () => { if (environmentId === null || resource === null || httpBaseUrl === null) return null; const state = assetUrlStateFromResult( await createUrl({ environmentId, input: { resource } }), httpBaseUrl, ); return state._tag === "Success" ? state.url : null; }, [createUrl, environmentId, httpBaseUrl, resource]);}
import { useAtomValue } from "@effect/atom-react";
import {
type EnvironmentConnectionPhase,
presentConnectionState,
} from "@t3tools/client-runtime/connection";
import {
assetUrlStateFromResult,
createAssetEnvironmentAtoms,
createProjectFaviconUrlAtomFamily,
EMPTY_ASSET_URL_ATOM,
} from "@t3tools/client-runtime/state/assets";
import type { AssetResource, EnvironmentId } from "@t3tools/contracts";
import * as Option from "effect/Option";
import { AsyncResult, Atom } from "effect/unstable/reactivity";
import { useCallback } from "react";
import { environmentCatalog } from "../connection/catalog";
import { connectionAtomRuntime } from "../connection/runtime";
import { projectFaviconCache } from "../lib/projectFaviconCache";
import { type AssetUrlState, deriveAssetUrlState } from "./asset-url-state";
import { environmentSession, usePreparedConnection } from "./session";
import { useAtomQueryRunner } from "./use-atom-query-runner";
export type { AssetUrlFailureReason, AssetUrlState } from "./asset-url-state";
export const assetEnvironment = createAssetEnvironmentAtoms(connectionAtomRuntime);
export const projectFaviconUrlAtom = createProjectFaviconUrlAtomFamily({
imageCache: projectFaviconCache,
createUrl: assetEnvironment.createUrl,
preparedConnection: environmentSession.preparedConnectionValueAtom,
});
const EMPTY_CONNECTION_STATE_ATOM = Atom.make(AsyncResult.initial<never, never>(false)).pipe(
Atom.withLabel("mobile-asset-connection-state:empty"),
);
function useConnectionPhase(environmentId: EnvironmentId | null): EnvironmentConnectionPhase {
const state = useAtomValue(
environmentId === null
? EMPTY_CONNECTION_STATE_ATOM
: environmentCatalog.stateAtom(environmentId),
);
const value = Option.getOrNull(AsyncResult.value(state));
return value === null ? "available" : presentConnectionState(value).phase;
}
export function useAssetUrlState(
environmentId: EnvironmentId | null,
resource: AssetResource | null,
): AssetUrlState {
const preparedConnection = usePreparedConnection(environmentId);
const connectionPhase = useConnectionPhase(environmentId);
const result = useAtomValue(
environmentId === null || resource === null
? EMPTY_ASSET_URL_ATOM
: assetEnvironment.createUrl({ environmentId, input: { resource } }),
);
const shared = assetUrlStateFromResult(
result,
preparedConnection._tag === "Some" ? preparedConnection.value.httpBaseUrl : null,
);
return deriveAssetUrlState({
connectionPhase,
// A failure left over from an outage is re-queried as soon as the
// connection returns. While that re-query is in flight it is not a verdict
// on the file, so it reads as loading rather than a false "unavailable".
shared: shared._tag === "Failure" && result.waiting ? { _tag: "Loading" } : shared,
});
}
export function useAssetUrl(
environmentId: EnvironmentId | null,
resource: AssetResource | null,
): string | null {
const state = useAssetUrlState(environmentId, resource);
return state._tag === "Success" ? state.url : null;
}
/** Explicit playback and sharing must reauthorize files that may have been replaced on disk. */
export function useRefreshAssetUrl(
environmentId: EnvironmentId | null,
resource: AssetResource | null,
): () => Promise<string | null> {
const connection = usePreparedConnection(environmentId);
const httpBaseUrl = connection._tag === "Some" ? connection.value.httpBaseUrl : null;
const createUrl = useAtomQueryRunner(assetEnvironment.createUrl, {
refresh: true,
reportFailure: false,
});
return useCallback(async () => {
if (environmentId === null || resource === null || httpBaseUrl === null) return null;
const state = assetUrlStateFromResult(
await createUrl({ environmentId, input: { resource } }),
httpBaseUrl,
);
return state._tag === "Success" ? state.url : null;
}, [createUrl, environmentId, httpBaseUrl, resource]);
}