1import { useAtomValue } from "@effect/atom-react";
2import {
3 type AssetUrlState,
4 assetUrlStateFromResult,
5 blockedAssetUrlState,
6 EMPTY_ASSET_URL_ATOM,
7 resolveAssetUrl,
8} from "@t3tools/client-runtime/state/assets";
9import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
10import type { AssetResource, EnvironmentId } from "@t3tools/contracts";
11import { AsyncResult } from "effect/reactivity";
12import { useCallback, useMemo } from "react";
13
14import { assetEnvironment } from "~/state/assets";
15import { useFilesystemReadAccess } from "~/state/filesystem";
16import { usePreparedConnection } from "~/state/session";
17import { useAtomQueryRunner } from "~/state/use-atom-query-runner";
18
19export { resolveAssetUrl, type AssetUrlState } from "@t3tools/client-runtime/state/assets";
20
21export function useAssetUrlState(
22 environmentId: EnvironmentId | null,
23 resource: AssetResource | null,
24): AssetUrlState {
25 const blocked = blockedAssetUrlState(resource, useFilesystemReadAccess(environmentId));
26 const preparedConnection = usePreparedConnection(environmentId);
27 const result = useAtomValue(
28 blocked !== null || environmentId === null || resource === null
29 ? EMPTY_ASSET_URL_ATOM
30 : assetEnvironment.createUrl({ environmentId, input: { resource } }),
31 );
32 if (blocked !== null) return blocked;
33 return assetUrlStateFromResult(
34 result,
35 preparedConnection._tag === "Some" ? preparedConnection.value.httpBaseUrl : null,
36 );
37}
38
39export function useAssetUrlRefresh(
40 environmentId: EnvironmentId | null,
41 resource: AssetResource | null,
42): () => Promise<string | null> {
43 const connection = usePreparedConnection(environmentId);
44 const httpBaseUrl = connection._tag === "Some" ? connection.value.httpBaseUrl : null;
45 const refresh = useAtomQueryRunner(assetEnvironment.createUrl, {
46 reportFailure: false,
47 refresh: true,
48 });
49 return useCallback(async () => {
50 if (environmentId === null || resource === null || httpBaseUrl === null) return null;
51 const result = await refresh({ environmentId, input: { resource } });
52 if (result._tag === "Failure") throw squashAtomCommandFailure(result);
53 return resolveAssetUrl(httpBaseUrl, result.value.relativeUrl);
54 }, [environmentId, resource, refresh, httpBaseUrl]);
55}
56
57export function useAssetUrls(
58 environmentId: EnvironmentId,
59 resources: ReadonlyArray<AssetResource>,
60): ReadonlyArray<string | null> {
61 const preparedConnection = usePreparedConnection(environmentId);
62 const { canReadFiles } = useFilesystemReadAccess(environmentId);
63 const allowedResources = useMemo(
64 () =>
65 canReadFiles
66 ? resources
67 : resources.filter(
68 (resource) =>
69 resource._tag !== "workspace-file" &&
70 resource._tag !== "media-file" &&
71 resource._tag !== "draft-workspace-file",
72 ),
73 [canReadFiles, resources],
74 );
75 const results = useAtomValue(
76 assetEnvironment.createUrls({
77 environmentId,
78 resources: allowedResources,
79 }),
80 );
81 return useMemo(() => {
82 if (preparedConnection._tag === "None") return resources.map(() => null);
83 let resultIndex = 0;
84 return resources.map((resource) => {
85 if (
86 !canReadFiles &&
87 (resource._tag === "workspace-file" ||
88 resource._tag === "media-file" ||
89 resource._tag === "draft-workspace-file")
90 )
91 return null;
92 const result = results[resultIndex++];
93 return result && AsyncResult.isSuccess(result)
94 ? resolveAssetUrl(preparedConnection.value.httpBaseUrl, result.value.relativeUrl)
95 : null;
96 });
97 }, [canReadFiles, preparedConnection, resources, results]);
98}