@burdenoff/microfe-hookmovies 2026.706.1 → 2026.706.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hookmovies/HookMoviesRoutes.js +26 -6
- package/dist/hookmovies/HookMoviesRoutes.js.map +1 -1
- package/dist/hookmovies/components/index.js +7 -0
- package/dist/hookmovies/hooks/useFiles.js +54 -23
- package/dist/hookmovies/hooks/useFiles.js.map +1 -1
- package/dist/hookmovies/hooks/useHookMoviesApi.js +108 -42
- package/dist/hookmovies/hooks/useHookMoviesApi.js.map +1 -1
- package/dist/hookmovies/pages/catalog/TitleDetailPage.js +852 -454
- package/dist/hookmovies/pages/catalog/TitleDetailPage.js.map +1 -1
- package/dist/hookmovies/pages/consumer/DiscoveryPage.js +422 -0
- package/dist/hookmovies/pages/consumer/DiscoveryPage.js.map +1 -0
- package/dist/hookmovies/pages/consumer/HooksPage.js +252 -0
- package/dist/hookmovies/pages/consumer/HooksPage.js.map +1 -0
- package/dist/hookmovies/pages/consumer/PlayerPage.js +542 -0
- package/dist/hookmovies/pages/consumer/PlayerPage.js.map +1 -0
- package/dist/hookmovies/pages/consumer/TitleViewPage.js +424 -0
- package/dist/hookmovies/pages/consumer/TitleViewPage.js.map +1 -0
- package/package.json +1 -1
|
@@ -4,15 +4,15 @@ import { Suspense as n, lazy as r } from "react";
|
|
|
4
4
|
import { Navigate as i, Route as a, Routes as o, useLocation as s } from "react-router-dom";
|
|
5
5
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
6
6
|
//#region src/hookmovies/HookMoviesRoutes.tsx
|
|
7
|
-
var u = r(() => import("./pages/catalog/TitlesPage.js").then((e) => ({ default: e.TitlesPage }))), d = r(() => import("./pages/catalog/TitleCreatePage.js").then((e) => ({ default: e.TitleCreatePage }))), f = r(() => import("./pages/catalog/TitleDetailPage.js").then((e) => ({ default: e.TitleDetailPage }))), p = r(() => import("./pages/catalog/StudiosPage.js").then((e) => ({ default: e.StudiosPage }))), m = r(() => import("./pages/catalog/PeoplePage.js").then((e) => ({ default: e.PeoplePage }))), h = r(() => import("./pages/productions/ProductionsPage.js").then((e) => ({ default: e.ProductionsPage }))), g = r(() => import("./pages/productions/ProductionCreatePage.js").then((e) => ({ default: e.ProductionCreatePage }))), _ = r(() => import("./pages/productions/ProductionDetailPage.js").then((e) => ({ default: e.ProductionDetailPage }))), v = r(() => import("./pages/auditions/AuditionsPage.js").then((e) => ({ default: e.AuditionsPage }))), y = r(() => import("./pages/auditions/AuditionCreatePage.js").then((e) => ({ default: e.AuditionCreatePage }))), b = r(() => import("./pages/auditions/AuditionDetailPage.js").then((e) => ({ default: e.AuditionDetailPage }))), x = r(() => import("./pages/engagement/ReviewsPage.js").then((e) => ({ default: e.ReviewsPage }))), S = r(() => import("./pages/distribution/RightsWindowsPage.js").then((e) => ({ default: e.RightsWindowsPage }))), C = r(() => import("./pages/library/ListsPage.js").then((e) => ({ default: e.ListsPage }))), w = r(() => import("./pages/library/EntitlementsPage.js").then((e) => ({ default: e.EntitlementsPage }))), T = () => /* @__PURE__ */ c("div", {
|
|
7
|
+
var u = r(() => import("./pages/catalog/TitlesPage.js").then((e) => ({ default: e.TitlesPage }))), d = r(() => import("./pages/catalog/TitleCreatePage.js").then((e) => ({ default: e.TitleCreatePage }))), f = r(() => import("./pages/catalog/TitleDetailPage.js").then((e) => ({ default: e.TitleDetailPage }))), p = r(() => import("./pages/catalog/StudiosPage.js").then((e) => ({ default: e.StudiosPage }))), m = r(() => import("./pages/catalog/PeoplePage.js").then((e) => ({ default: e.PeoplePage }))), h = r(() => import("./pages/productions/ProductionsPage.js").then((e) => ({ default: e.ProductionsPage }))), g = r(() => import("./pages/productions/ProductionCreatePage.js").then((e) => ({ default: e.ProductionCreatePage }))), _ = r(() => import("./pages/productions/ProductionDetailPage.js").then((e) => ({ default: e.ProductionDetailPage }))), v = r(() => import("./pages/auditions/AuditionsPage.js").then((e) => ({ default: e.AuditionsPage }))), y = r(() => import("./pages/auditions/AuditionCreatePage.js").then((e) => ({ default: e.AuditionCreatePage }))), b = r(() => import("./pages/auditions/AuditionDetailPage.js").then((e) => ({ default: e.AuditionDetailPage }))), x = r(() => import("./pages/engagement/ReviewsPage.js").then((e) => ({ default: e.ReviewsPage }))), S = r(() => import("./pages/distribution/RightsWindowsPage.js").then((e) => ({ default: e.RightsWindowsPage }))), C = r(() => import("./pages/library/ListsPage.js").then((e) => ({ default: e.ListsPage }))), w = r(() => import("./pages/library/EntitlementsPage.js").then((e) => ({ default: e.EntitlementsPage }))), T = r(() => import("./pages/consumer/DiscoveryPage.js").then((e) => ({ default: e.DiscoveryPage }))), E = r(() => import("./pages/consumer/TitleViewPage.js").then((e) => ({ default: e.TitleViewPage }))), D = r(() => import("./pages/consumer/PlayerPage.js").then((e) => ({ default: e.PlayerPage }))), O = r(() => import("./pages/consumer/HooksPage.js").then((e) => ({ default: e.HooksPage }))), k = () => /* @__PURE__ */ c("div", {
|
|
8
8
|
className: "flex min-h-[400px] items-center justify-center",
|
|
9
9
|
children: /* @__PURE__ */ c("div", { className: "h-8 w-8 animate-spin rounded-full border-b-2 border-action-primary-bg" })
|
|
10
|
-
}),
|
|
11
|
-
let { basePath: r, isStandalone:
|
|
12
|
-
return
|
|
10
|
+
}), A = () => {
|
|
11
|
+
let { basePath: r, isStandalone: A } = e(), j = s(), M = r === "/" ? "" : (r ?? "").replace(/\/$/, "");
|
|
12
|
+
return M && !A && !(j.pathname === M || j.pathname.startsWith(M + "/")) ? null : /* @__PURE__ */ c("div", {
|
|
13
13
|
className: "flex min-h-full flex-col",
|
|
14
14
|
children: /* @__PURE__ */ c(n, {
|
|
15
|
-
fallback: /* @__PURE__ */ c(
|
|
15
|
+
fallback: /* @__PURE__ */ c(k, {}),
|
|
16
16
|
children: /* @__PURE__ */ l(o, { children: [
|
|
17
17
|
/* @__PURE__ */ c(a, {
|
|
18
18
|
index: !0,
|
|
@@ -78,6 +78,26 @@ var u = r(() => import("./pages/catalog/TitlesPage.js").then((e) => ({ default:
|
|
|
78
78
|
path: "library/entitlements",
|
|
79
79
|
element: /* @__PURE__ */ c(w, {})
|
|
80
80
|
}),
|
|
81
|
+
/* @__PURE__ */ c(a, {
|
|
82
|
+
path: "browse",
|
|
83
|
+
element: /* @__PURE__ */ c(T, {})
|
|
84
|
+
}),
|
|
85
|
+
/* @__PURE__ */ c(a, {
|
|
86
|
+
path: "title/:id",
|
|
87
|
+
element: /* @__PURE__ */ c(E, {})
|
|
88
|
+
}),
|
|
89
|
+
/* @__PURE__ */ c(a, {
|
|
90
|
+
path: "watch/:titleId",
|
|
91
|
+
element: /* @__PURE__ */ c(D, {})
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ c(a, {
|
|
94
|
+
path: "watch/:titleId/episode/:episodeId",
|
|
95
|
+
element: /* @__PURE__ */ c(D, {})
|
|
96
|
+
}),
|
|
97
|
+
/* @__PURE__ */ c(a, {
|
|
98
|
+
path: "hooks",
|
|
99
|
+
element: /* @__PURE__ */ c(O, {})
|
|
100
|
+
}),
|
|
81
101
|
/* @__PURE__ */ c(a, {
|
|
82
102
|
path: "*",
|
|
83
103
|
element: /* @__PURE__ */ c(i, {
|
|
@@ -90,6 +110,6 @@ var u = r(() => import("./pages/catalog/TitlesPage.js").then((e) => ({ default:
|
|
|
90
110
|
});
|
|
91
111
|
};
|
|
92
112
|
//#endregion
|
|
93
|
-
export {
|
|
113
|
+
export { A as HookMoviesRoutes };
|
|
94
114
|
|
|
95
115
|
//# sourceMappingURL=HookMoviesRoutes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HookMoviesRoutes.js","names":[],"sources":["../../src/hookmovies/HookMoviesRoutes.tsx"],"sourcesContent":["/**\n * HookMovies Routes\n * @module hookmovies\n *\n * Real domain routes for the HookMovies workspace product: catalog (titles,\n * studios, people), production lifecycle, casting auditions, audience\n * engagement (reviews), rights & distribution, and the user library (lists +\n * entitlements). All data comes from the live `hookmovies` subgraph.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router-dom';\nimport { useHookMovies } from './context/HookMoviesContext';\n\n// Eager — primary landing\nimport { HomePage } from './pages/HomePage';\n\n// Catalog\nconst TitlesPage = lazy(() =>\n import('./pages/catalog/TitlesPage').then((m) => ({ default: m.TitlesPage }))\n);\nconst TitleCreatePage = lazy(() =>\n import('./pages/catalog/TitleCreatePage').then((m) => ({ default: m.TitleCreatePage }))\n);\nconst TitleDetailPage = lazy(() =>\n import('./pages/catalog/TitleDetailPage').then((m) => ({ default: m.TitleDetailPage }))\n);\nconst StudiosPage = lazy(() =>\n import('./pages/catalog/StudiosPage').then((m) => ({ default: m.StudiosPage }))\n);\nconst PeoplePage = lazy(() =>\n import('./pages/catalog/PeoplePage').then((m) => ({ default: m.PeoplePage }))\n);\n\n// Productions\nconst ProductionsPage = lazy(() =>\n import('./pages/productions/ProductionsPage').then((m) => ({ default: m.ProductionsPage }))\n);\nconst ProductionCreatePage = lazy(() =>\n import('./pages/productions/ProductionCreatePage').then((m) => ({\n default: m.ProductionCreatePage,\n }))\n);\nconst ProductionDetailPage = lazy(() =>\n import('./pages/productions/ProductionDetailPage').then((m) => ({\n default: m.ProductionDetailPage,\n }))\n);\n\n// Auditions\nconst AuditionsPage = lazy(() =>\n import('./pages/auditions/AuditionsPage').then((m) => ({ default: m.AuditionsPage }))\n);\nconst AuditionCreatePage = lazy(() =>\n import('./pages/auditions/AuditionCreatePage').then((m) => ({ default: m.AuditionCreatePage }))\n);\nconst AuditionDetailPage = lazy(() =>\n import('./pages/auditions/AuditionDetailPage').then((m) => ({ default: m.AuditionDetailPage }))\n);\n\n// Engagement\nconst ReviewsPage = lazy(() =>\n import('./pages/engagement/ReviewsPage').then((m) => ({ default: m.ReviewsPage }))\n);\n\n// Distribution\nconst RightsWindowsPage = lazy(() =>\n import('./pages/distribution/RightsWindowsPage').then((m) => ({ default: m.RightsWindowsPage }))\n);\n\n// Library\nconst ListsPage = lazy(() =>\n import('./pages/library/ListsPage').then((m) => ({ default: m.ListsPage }))\n);\nconst EntitlementsPage = lazy(() =>\n import('./pages/library/EntitlementsPage').then((m) => ({ default: m.EntitlementsPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-b-2 border-action-primary-bg\" />\n </div>\n);\n\nexport const HookMoviesRoutes: FC = () => {\n const { basePath, isStandalone } = useHookMovies();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Overview / dashboard */}\n <Route index element={<HomePage />} />\n\n {/* Catalog */}\n <Route path=\"catalog/titles\" element={<TitlesPage />} />\n <Route path=\"catalog/titles/new\" element={<TitleCreatePage />} />\n <Route path=\"catalog/titles/:id\" element={<TitleDetailPage />} />\n <Route path=\"catalog/studios\" element={<StudiosPage />} />\n <Route path=\"catalog/people\" element={<PeoplePage />} />\n\n {/* Productions */}\n <Route path=\"productions\" element={<ProductionsPage />} />\n <Route path=\"productions/new\" element={<ProductionCreatePage />} />\n <Route path=\"productions/:id\" element={<ProductionDetailPage />} />\n\n {/* Auditions */}\n <Route path=\"auditions\" element={<AuditionsPage />} />\n <Route path=\"auditions/new\" element={<AuditionCreatePage />} />\n <Route path=\"auditions/:id\" element={<AuditionDetailPage />} />\n\n {/* Engagement */}\n <Route path=\"engagement/reviews\" element={<ReviewsPage />} />\n\n {/* Distribution */}\n <Route path=\"distribution/rights\" element={<RightsWindowsPage />} />\n\n {/* Library */}\n <Route path=\"library/lists\" element={<ListsPage />} />\n <Route path=\"library/entitlements\" element={<EntitlementsPage />} />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HookMoviesRoutes.js","names":[],"sources":["../../src/hookmovies/HookMoviesRoutes.tsx"],"sourcesContent":["/**\n * HookMovies Routes\n * @module hookmovies\n *\n * Real domain routes for the HookMovies workspace product: catalog (titles,\n * studios, people), production lifecycle, casting auditions, audience\n * engagement (reviews), rights & distribution, and the user library (lists +\n * entitlements). All data comes from the live `hookmovies` subgraph.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router-dom';\nimport { useHookMovies } from './context/HookMoviesContext';\n\n// Eager — primary landing\nimport { HomePage } from './pages/HomePage';\n\n// Catalog\nconst TitlesPage = lazy(() =>\n import('./pages/catalog/TitlesPage').then((m) => ({ default: m.TitlesPage }))\n);\nconst TitleCreatePage = lazy(() =>\n import('./pages/catalog/TitleCreatePage').then((m) => ({ default: m.TitleCreatePage }))\n);\nconst TitleDetailPage = lazy(() =>\n import('./pages/catalog/TitleDetailPage').then((m) => ({ default: m.TitleDetailPage }))\n);\nconst StudiosPage = lazy(() =>\n import('./pages/catalog/StudiosPage').then((m) => ({ default: m.StudiosPage }))\n);\nconst PeoplePage = lazy(() =>\n import('./pages/catalog/PeoplePage').then((m) => ({ default: m.PeoplePage }))\n);\n\n// Productions\nconst ProductionsPage = lazy(() =>\n import('./pages/productions/ProductionsPage').then((m) => ({ default: m.ProductionsPage }))\n);\nconst ProductionCreatePage = lazy(() =>\n import('./pages/productions/ProductionCreatePage').then((m) => ({\n default: m.ProductionCreatePage,\n }))\n);\nconst ProductionDetailPage = lazy(() =>\n import('./pages/productions/ProductionDetailPage').then((m) => ({\n default: m.ProductionDetailPage,\n }))\n);\n\n// Auditions\nconst AuditionsPage = lazy(() =>\n import('./pages/auditions/AuditionsPage').then((m) => ({ default: m.AuditionsPage }))\n);\nconst AuditionCreatePage = lazy(() =>\n import('./pages/auditions/AuditionCreatePage').then((m) => ({ default: m.AuditionCreatePage }))\n);\nconst AuditionDetailPage = lazy(() =>\n import('./pages/auditions/AuditionDetailPage').then((m) => ({ default: m.AuditionDetailPage }))\n);\n\n// Engagement\nconst ReviewsPage = lazy(() =>\n import('./pages/engagement/ReviewsPage').then((m) => ({ default: m.ReviewsPage }))\n);\n\n// Distribution\nconst RightsWindowsPage = lazy(() =>\n import('./pages/distribution/RightsWindowsPage').then((m) => ({ default: m.RightsWindowsPage }))\n);\n\n// Library\nconst ListsPage = lazy(() =>\n import('./pages/library/ListsPage').then((m) => ({ default: m.ListsPage }))\n);\nconst EntitlementsPage = lazy(() =>\n import('./pages/library/EntitlementsPage').then((m) => ({ default: m.EntitlementsPage }))\n);\n\n// Consumer (audience-facing) — discovery, title view, playback, hooks feed\nconst DiscoveryPage = lazy(() =>\n import('./pages/consumer/DiscoveryPage').then((m) => ({ default: m.DiscoveryPage }))\n);\nconst TitleViewPage = lazy(() =>\n import('./pages/consumer/TitleViewPage').then((m) => ({ default: m.TitleViewPage }))\n);\nconst PlayerPage = lazy(() =>\n import('./pages/consumer/PlayerPage').then((m) => ({ default: m.PlayerPage }))\n);\nconst HooksPage = lazy(() =>\n import('./pages/consumer/HooksPage').then((m) => ({ default: m.HooksPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-b-2 border-action-primary-bg\" />\n </div>\n);\n\nexport const HookMoviesRoutes: FC = () => {\n const { basePath, isStandalone } = useHookMovies();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Overview / dashboard */}\n <Route index element={<HomePage />} />\n\n {/* Catalog */}\n <Route path=\"catalog/titles\" element={<TitlesPage />} />\n <Route path=\"catalog/titles/new\" element={<TitleCreatePage />} />\n <Route path=\"catalog/titles/:id\" element={<TitleDetailPage />} />\n <Route path=\"catalog/studios\" element={<StudiosPage />} />\n <Route path=\"catalog/people\" element={<PeoplePage />} />\n\n {/* Productions */}\n <Route path=\"productions\" element={<ProductionsPage />} />\n <Route path=\"productions/new\" element={<ProductionCreatePage />} />\n <Route path=\"productions/:id\" element={<ProductionDetailPage />} />\n\n {/* Auditions */}\n <Route path=\"auditions\" element={<AuditionsPage />} />\n <Route path=\"auditions/new\" element={<AuditionCreatePage />} />\n <Route path=\"auditions/:id\" element={<AuditionDetailPage />} />\n\n {/* Engagement */}\n <Route path=\"engagement/reviews\" element={<ReviewsPage />} />\n\n {/* Distribution */}\n <Route path=\"distribution/rights\" element={<RightsWindowsPage />} />\n\n {/* Library */}\n <Route path=\"library/lists\" element={<ListsPage />} />\n <Route path=\"library/entitlements\" element={<EntitlementsPage />} />\n\n {/* Consumer (audience-facing) */}\n <Route path=\"browse\" element={<DiscoveryPage />} />\n <Route path=\"title/:id\" element={<TitleViewPage />} />\n <Route path=\"watch/:titleId\" element={<PlayerPage />} />\n <Route path=\"watch/:titleId/episode/:episodeId\" element={<PlayerPage />} />\n <Route path=\"hooks\" element={<HooksPage />} />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,55 +1,59 @@
|
|
|
1
|
+
import { useHookMovies as e } from "../context/HookMoviesContext.js";
|
|
1
2
|
import "../context/index.js";
|
|
2
|
-
import "@tanstack/react-query";
|
|
3
|
-
import { graphqlFetch as
|
|
3
|
+
import { useQuery as t } from "@tanstack/react-query";
|
|
4
|
+
import { graphqlFetch as n } from "@burdenoff/fe-libs/shared/graphql";
|
|
4
5
|
//#region src/hookmovies/hooks/useFiles.ts
|
|
5
|
-
var
|
|
6
|
+
var r = 21600, i = {
|
|
6
7
|
selfTapes: "/HookMovies/SelfTapes",
|
|
7
|
-
episodes: "/HookMovies/Episodes"
|
|
8
|
+
episodes: "/HookMovies/Episodes",
|
|
9
|
+
artwork: "/HookMovies/Artwork",
|
|
10
|
+
masters: "/HookMovies/Masters",
|
|
11
|
+
clips: "/HookMovies/Clips"
|
|
8
12
|
};
|
|
9
|
-
async function
|
|
10
|
-
let r = await
|
|
13
|
+
async function a(e, t) {
|
|
14
|
+
let r = await n({
|
|
11
15
|
gateway: "workspace",
|
|
12
|
-
query:
|
|
13
|
-
variables:
|
|
16
|
+
query: e,
|
|
17
|
+
variables: t,
|
|
14
18
|
workspaceToken: !0
|
|
15
19
|
});
|
|
16
20
|
if (r.errors?.length) throw Error(r.errors[0]?.message ?? "Files request failed");
|
|
17
21
|
if (!r.data) throw Error("Files response did not include data");
|
|
18
22
|
return r.data;
|
|
19
23
|
}
|
|
20
|
-
var
|
|
21
|
-
function
|
|
24
|
+
var o = "\n mutation HokEnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }\n", s = "\n mutation HokInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }\n", c = "\n mutation HokCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }\n", l = "\n query HokGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n";
|
|
25
|
+
function u(e, t) {
|
|
22
26
|
let n = e.lastIndexOf(".");
|
|
23
27
|
return n > -1 && n < e.length - 1 ? e.slice(n + 1).toLowerCase() : (t.split("/")[1] ?? "bin").toLowerCase();
|
|
24
28
|
}
|
|
25
|
-
async function
|
|
26
|
-
let
|
|
27
|
-
if (!
|
|
28
|
-
let
|
|
29
|
+
async function d(e, t) {
|
|
30
|
+
let n = (await a(o, { path: t })).ensureFolderByPath?.id;
|
|
31
|
+
if (!n) throw Error("Could not resolve the upload folder.");
|
|
32
|
+
let i = e.type || "application/octet-stream", d = e.size, f = await a(s, { input: {
|
|
29
33
|
fileName: e.name,
|
|
30
|
-
extension:
|
|
34
|
+
extension: u(e.name, i),
|
|
31
35
|
fileSize: d,
|
|
32
|
-
mimeType:
|
|
33
|
-
folderId:
|
|
36
|
+
mimeType: i,
|
|
37
|
+
folderId: n
|
|
34
38
|
} }), p = f.initiateUpload?.uploadUrl, m = f.initiateUpload?.session.id;
|
|
35
39
|
if (!p || !m) throw Error("The files service did not return an upload URL.");
|
|
36
40
|
let h = await fetch(p, {
|
|
37
41
|
method: "PUT",
|
|
38
42
|
body: e,
|
|
39
|
-
headers: { "Content-Type":
|
|
43
|
+
headers: { "Content-Type": i }
|
|
40
44
|
});
|
|
41
45
|
if (!h.ok) throw Error(`Upload to storage failed (${h.status}).`);
|
|
42
|
-
let g = (await
|
|
46
|
+
let g = (await a(c, { input: {
|
|
43
47
|
sessionId: m,
|
|
44
48
|
fileSize: d,
|
|
45
|
-
folderId:
|
|
49
|
+
folderId: n
|
|
46
50
|
} })).completeUpload?.file;
|
|
47
51
|
if (!g?.id) throw Error("The upload could not be finalized.");
|
|
48
52
|
let _ = "";
|
|
49
53
|
try {
|
|
50
|
-
_ = (await
|
|
54
|
+
_ = (await a(l, {
|
|
51
55
|
id: g.id,
|
|
52
|
-
expiresIn:
|
|
56
|
+
expiresIn: r
|
|
53
57
|
})).getFileDownloadUrl ?? "";
|
|
54
58
|
} catch {
|
|
55
59
|
_ = "";
|
|
@@ -60,7 +64,34 @@ async function l(e, n) {
|
|
|
60
64
|
name: g.name
|
|
61
65
|
};
|
|
62
66
|
}
|
|
67
|
+
function f(i) {
|
|
68
|
+
let { apiGatewayUrl: a } = e();
|
|
69
|
+
return t({
|
|
70
|
+
queryKey: [
|
|
71
|
+
"hookmovies",
|
|
72
|
+
"file-url",
|
|
73
|
+
i ?? ""
|
|
74
|
+
],
|
|
75
|
+
enabled: !!i,
|
|
76
|
+
staleTime: 1800 * 1e3,
|
|
77
|
+
retry: 1,
|
|
78
|
+
queryFn: async () => {
|
|
79
|
+
let e = await n({
|
|
80
|
+
gateway: "workspace",
|
|
81
|
+
baseUrl: a,
|
|
82
|
+
workspaceToken: !0,
|
|
83
|
+
query: l,
|
|
84
|
+
variables: {
|
|
85
|
+
id: i,
|
|
86
|
+
expiresIn: r
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (e.errors?.length) throw Error(e.errors[0]?.message ?? "Could not resolve file URL");
|
|
90
|
+
return e.data?.getFileDownloadUrl ?? null;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
63
94
|
//#endregion
|
|
64
|
-
export {
|
|
95
|
+
export { i as FILE_FOLDERS, d as uploadFileToFiles, f as useFileUrl };
|
|
65
96
|
|
|
66
97
|
//# sourceMappingURL=useFiles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFiles.js","names":[],"sources":["../../../src/hookmovies/hooks/useFiles.ts"],"sourcesContent":["/**\n * File hooks — self-tapes & episode masters via wspace-files-svc (NO duplication).\n * @module hookmovies/hooks\n *\n * HookMovies never stores blobs. Episodes (master/mezzanine) and audition\n * self-tapes reference files owned by wspace-files-svc by id. Upload uses the\n * SAME proven `graphqlFetch` transport as every other read/write in this MFE\n * (the shell's workspace gateway + token), driving the wspace-files 3-step\n * presigned flow directly:\n * ensureFolderByPath -> initiateUpload -> HTTP PUT -> completeUpload\n * Display resolves a file id to a presigned URL via `getFileDownloadUrl`.\n *\n * NOTE: we deliberately do NOT use the Apollo-based `uploadBlobToFiles` /\n * `getWorkspaceClient()` helper here — this shell wires its data plane through\n * `graphqlFetch`, not a global Apollo client, so `getWorkspaceClient()` returns\n * an un-networked client and the upload silently no-ops. `graphqlFetch` is the\n * transport the whole MFE already uses successfully.\n */\n\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useHookMovies } from '../context';\n\n/**\n * Six hours. wspace-files-svc rejects any `expiresIn` above the S3 7-day cap\n * (604800s) with an INTERNAL_SERVER_ERROR, so we deliberately stay well under\n * it — these URLs back immediate preview/download, not durable references.\n */\nconst DOWNLOAD_URL_TTL_SECONDS = 21_600;\n\nexport const FILE_FOLDERS = {\n selfTapes: '/HookMovies/SelfTapes',\n episodes: '/HookMovies/Episodes',\n} as const;\n\nexport interface UploadedFile {\n id: string;\n url: string;\n name: string;\n}\n\n/** Thin wrapper over the workspace gateway using the MFE's proven transport. */\nasync function filesFetch<TData>(\n query: string,\n variables: Record<string, unknown>\n): Promise<TData> {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n query,\n variables,\n workspaceToken: true,\n });\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'Files request failed');\n }\n if (!response.data) {\n throw new Error('Files response did not include data');\n }\n return response.data;\n}\n\nconst ENSURE_FOLDER = `\n mutation HokEnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }\n`;\n\nconst INITIATE_UPLOAD = `\n mutation HokInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }\n`;\n\nconst COMPLETE_UPLOAD = `\n mutation HokCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }\n`;\n\nconst GET_FILE_DOWNLOAD_URL = `\n query HokGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\nfunction fileExtension(name: string, mimeType: string): string {\n const dot = name.lastIndexOf('.');\n if (dot > -1 && dot < name.length - 1) return name.slice(dot + 1).toLowerCase();\n const fromMime = mimeType.split('/')[1];\n return (fromMime ?? 'bin').toLowerCase();\n}\n\n/**\n * Upload a file to wspace-files-svc and return its id + a presigned URL. The\n * caller persists the id (e.g. via `hokCreateEpisode({ fileId })`); the URL is\n * used for an immediate preview / download link.\n */\nexport async function uploadFileToFiles(file: File, folderPath: string): Promise<UploadedFile> {\n // 1. Resolve (or create) the destination folder — File.folderId is a real FK.\n const folder = await filesFetch<{ ensureFolderByPath?: { id: string } | null }>(ENSURE_FOLDER, {\n path: folderPath,\n });\n const folderId = folder.ensureFolderByPath?.id;\n if (!folderId) {\n throw new Error('Could not resolve the upload folder.');\n }\n\n const mimeType = file.type || 'application/octet-stream';\n const fileSize = file.size;\n\n // 2. Open a presigned upload session.\n const initiated = await filesFetch<{\n initiateUpload?: { uploadUrl?: string | null; session: { id: string } } | null;\n }>(INITIATE_UPLOAD, {\n input: {\n fileName: file.name,\n extension: fileExtension(file.name, mimeType),\n fileSize,\n mimeType,\n folderId,\n },\n });\n const uploadUrl = initiated.initiateUpload?.uploadUrl;\n const sessionId = initiated.initiateUpload?.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('The files service did not return an upload URL.');\n }\n\n // 3. PUT the bytes straight to storage using the presigned URL. Content-Type\n // must match what was signed at initiateUpload.\n const putResponse = await fetch(uploadUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': mimeType },\n });\n if (!putResponse.ok) {\n throw new Error(`Upload to storage failed (${putResponse.status}).`);\n }\n\n // 4. Finalize — creates the File record. fileSize must match initiateUpload.\n const completed = await filesFetch<{\n completeUpload?: { fileId: string; file: { id: string; name: string } } | null;\n }>(COMPLETE_UPLOAD, {\n input: { sessionId, fileSize, folderId },\n });\n const fileRecord = completed.completeUpload?.file;\n if (!fileRecord?.id) {\n throw new Error('The upload could not be finalized.');\n }\n\n // 5. Resolve a presigned URL for immediate preview/download (best-effort).\n let url = '';\n try {\n const urlData = await filesFetch<{ getFileDownloadUrl?: string | null }>(\n GET_FILE_DOWNLOAD_URL,\n {\n id: fileRecord.id,\n expiresIn: DOWNLOAD_URL_TTL_SECONDS,\n }\n );\n url = urlData.getFileDownloadUrl ?? '';\n } catch {\n url = '';\n }\n\n return { id: fileRecord.id, url, name: fileRecord.name };\n}\n\n/** Resolve a wspace-files-svc file id to a presigned download URL for display. */\nexport function useFileUrl(fileId?: string | null) {\n const { apiGatewayUrl } = useHookMovies();\n return useQuery({\n queryKey: ['hookmovies', 'file-url', fileId ?? ''],\n enabled: Boolean(fileId),\n staleTime: 30 * 60 * 1000,\n retry: 1,\n queryFn: async () => {\n const response = await graphqlFetch<{ getFileDownloadUrl: string | null }>({\n gateway: 'workspace',\n baseUrl: apiGatewayUrl,\n workspaceToken: true,\n query: GET_FILE_DOWNLOAD_URL,\n variables: { id: fileId, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n });\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'Could not resolve file URL');\n }\n return response.data?.getFileDownloadUrl ?? null;\n },\n });\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"useFiles.js","names":[],"sources":["../../../src/hookmovies/hooks/useFiles.ts"],"sourcesContent":["/**\n * File hooks — self-tapes & episode masters via wspace-files-svc (NO duplication).\n * @module hookmovies/hooks\n *\n * HookMovies never stores blobs. Episodes (master/mezzanine) and audition\n * self-tapes reference files owned by wspace-files-svc by id. Upload uses the\n * SAME proven `graphqlFetch` transport as every other read/write in this MFE\n * (the shell's workspace gateway + token), driving the wspace-files 3-step\n * presigned flow directly:\n * ensureFolderByPath -> initiateUpload -> HTTP PUT -> completeUpload\n * Display resolves a file id to a presigned URL via `getFileDownloadUrl`.\n *\n * NOTE: we deliberately do NOT use the Apollo-based `uploadBlobToFiles` /\n * `getWorkspaceClient()` helper here — this shell wires its data plane through\n * `graphqlFetch`, not a global Apollo client, so `getWorkspaceClient()` returns\n * an un-networked client and the upload silently no-ops. `graphqlFetch` is the\n * transport the whole MFE already uses successfully.\n */\n\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useHookMovies } from '../context';\n\n/**\n * Six hours. wspace-files-svc rejects any `expiresIn` above the S3 7-day cap\n * (604800s) with an INTERNAL_SERVER_ERROR, so we deliberately stay well under\n * it — these URLs back immediate preview/download, not durable references.\n */\nconst DOWNLOAD_URL_TTL_SECONDS = 21_600;\n\nexport const FILE_FOLDERS = {\n selfTapes: '/HookMovies/SelfTapes',\n episodes: '/HookMovies/Episodes',\n artwork: '/HookMovies/Artwork',\n masters: '/HookMovies/Masters',\n clips: '/HookMovies/Clips',\n} as const;\n\nexport interface UploadedFile {\n id: string;\n url: string;\n name: string;\n}\n\n/** Thin wrapper over the workspace gateway using the MFE's proven transport. */\nasync function filesFetch<TData>(\n query: string,\n variables: Record<string, unknown>\n): Promise<TData> {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n query,\n variables,\n workspaceToken: true,\n });\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'Files request failed');\n }\n if (!response.data) {\n throw new Error('Files response did not include data');\n }\n return response.data;\n}\n\nconst ENSURE_FOLDER = `\n mutation HokEnsureFolder($path: String!) {\n ensureFolderByPath(path: $path) { id }\n }\n`;\n\nconst INITIATE_UPLOAD = `\n mutation HokInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }\n`;\n\nconst COMPLETE_UPLOAD = `\n mutation HokCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }\n`;\n\nconst GET_FILE_DOWNLOAD_URL = `\n query HokGetFileDownloadUrl($id: ID!, $expiresIn: Int) {\n getFileDownloadUrl(id: $id, expiresIn: $expiresIn)\n }\n`;\n\nfunction fileExtension(name: string, mimeType: string): string {\n const dot = name.lastIndexOf('.');\n if (dot > -1 && dot < name.length - 1) return name.slice(dot + 1).toLowerCase();\n const fromMime = mimeType.split('/')[1];\n return (fromMime ?? 'bin').toLowerCase();\n}\n\n/**\n * Upload a file to wspace-files-svc and return its id + a presigned URL. The\n * caller persists the id (e.g. via `hokCreateEpisode({ fileId })`); the URL is\n * used for an immediate preview / download link.\n */\nexport async function uploadFileToFiles(file: File, folderPath: string): Promise<UploadedFile> {\n // 1. Resolve (or create) the destination folder — File.folderId is a real FK.\n const folder = await filesFetch<{ ensureFolderByPath?: { id: string } | null }>(ENSURE_FOLDER, {\n path: folderPath,\n });\n const folderId = folder.ensureFolderByPath?.id;\n if (!folderId) {\n throw new Error('Could not resolve the upload folder.');\n }\n\n const mimeType = file.type || 'application/octet-stream';\n const fileSize = file.size;\n\n // 2. Open a presigned upload session.\n const initiated = await filesFetch<{\n initiateUpload?: { uploadUrl?: string | null; session: { id: string } } | null;\n }>(INITIATE_UPLOAD, {\n input: {\n fileName: file.name,\n extension: fileExtension(file.name, mimeType),\n fileSize,\n mimeType,\n folderId,\n },\n });\n const uploadUrl = initiated.initiateUpload?.uploadUrl;\n const sessionId = initiated.initiateUpload?.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('The files service did not return an upload URL.');\n }\n\n // 3. PUT the bytes straight to storage using the presigned URL. Content-Type\n // must match what was signed at initiateUpload.\n const putResponse = await fetch(uploadUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': mimeType },\n });\n if (!putResponse.ok) {\n throw new Error(`Upload to storage failed (${putResponse.status}).`);\n }\n\n // 4. Finalize — creates the File record. fileSize must match initiateUpload.\n const completed = await filesFetch<{\n completeUpload?: { fileId: string; file: { id: string; name: string } } | null;\n }>(COMPLETE_UPLOAD, {\n input: { sessionId, fileSize, folderId },\n });\n const fileRecord = completed.completeUpload?.file;\n if (!fileRecord?.id) {\n throw new Error('The upload could not be finalized.');\n }\n\n // 5. Resolve a presigned URL for immediate preview/download (best-effort).\n let url = '';\n try {\n const urlData = await filesFetch<{ getFileDownloadUrl?: string | null }>(\n GET_FILE_DOWNLOAD_URL,\n {\n id: fileRecord.id,\n expiresIn: DOWNLOAD_URL_TTL_SECONDS,\n }\n );\n url = urlData.getFileDownloadUrl ?? '';\n } catch {\n url = '';\n }\n\n return { id: fileRecord.id, url, name: fileRecord.name };\n}\n\n/** Resolve a wspace-files-svc file id to a presigned download URL for display. */\nexport function useFileUrl(fileId?: string | null) {\n const { apiGatewayUrl } = useHookMovies();\n return useQuery({\n queryKey: ['hookmovies', 'file-url', fileId ?? ''],\n enabled: Boolean(fileId),\n staleTime: 30 * 60 * 1000,\n retry: 1,\n queryFn: async () => {\n const response = await graphqlFetch<{ getFileDownloadUrl: string | null }>({\n gateway: 'workspace',\n baseUrl: apiGatewayUrl,\n workspaceToken: true,\n query: GET_FILE_DOWNLOAD_URL,\n variables: { id: fileId, expiresIn: DOWNLOAD_URL_TTL_SECONDS },\n });\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'Could not resolve file URL');\n }\n return response.data?.getFileDownloadUrl ?? null;\n },\n });\n}\n"],"mappings":";;;;;AA6BA,IAAM,IAA2B,OAEpB,IAAe;CAC1B,WAAW;CACX,UAAU;CACV,SAAS;CACT,SAAS;CACT,OAAO;CACR;AASD,eAAe,EACb,GACA,GACgB;CAChB,IAAM,IAAW,MAAM,EAAoB;EACzC,SAAS;EACT;EACA;EACA,gBAAgB;EACjB,CAAC;AACF,KAAI,EAAS,QAAQ,OACnB,OAAU,MAAM,EAAS,OAAO,IAAI,WAAW,uBAAuB;AAExE,KAAI,CAAC,EAAS,KACZ,OAAU,MAAM,sCAAsC;AAExD,QAAO,EAAS;;AAGlB,IAAM,IAAgB,qGAMhB,IAAkB,4JASlB,IAAkB,2JASlB,IAAwB;AAM9B,SAAS,EAAc,GAAc,GAA0B;CAC7D,IAAM,IAAM,EAAK,YAAY,IAAI;AAGjC,QAFI,IAAM,MAAM,IAAM,EAAK,SAAS,IAAU,EAAK,MAAM,IAAM,EAAE,CAAC,aAAa,IAC9D,EAAS,MAAM,IAAI,CAAC,MACjB,OAAO,aAAa;;AAQ1C,eAAsB,EAAkB,GAAY,GAA2C;CAK7F,IAAM,KAHS,MAAM,EAA2D,GAAe,EAC7F,MAAM,GACP,CAAC,EACsB,oBAAoB;AAC5C,KAAI,CAAC,EACH,OAAU,MAAM,uCAAuC;CAGzD,IAAM,IAAW,EAAK,QAAQ,4BACxB,IAAW,EAAK,MAGhB,IAAY,MAAM,EAErB,GAAiB,EAClB,OAAO;EACL,UAAU,EAAK;EACf,WAAW,EAAc,EAAK,MAAM,EAAS;EAC7C;EACA;EACA;EACD,EACF,CAAC,EACI,IAAY,EAAU,gBAAgB,WACtC,IAAY,EAAU,gBAAgB,QAAQ;AACpD,KAAI,CAAC,KAAa,CAAC,EACjB,OAAU,MAAM,kDAAkD;CAKpE,IAAM,IAAc,MAAM,MAAM,GAAW;EACzC,QAAQ;EACR,MAAM;EACN,SAAS,EAAE,gBAAgB,GAAU;EACtC,CAAC;AACF,KAAI,CAAC,EAAY,GACf,OAAU,MAAM,6BAA6B,EAAY,OAAO,IAAI;CAStE,IAAM,KALY,MAAM,EAErB,GAAiB,EAClB,OAAO;EAAE;EAAW;EAAU;EAAU,EACzC,CAAC,EAC2B,gBAAgB;AAC7C,KAAI,CAAC,GAAY,GACf,OAAU,MAAM,qCAAqC;CAIvD,IAAI,IAAM;AACV,KAAI;AAQF,OAPgB,MAAM,EACpB,GACA;GACE,IAAI,EAAW;GACf,WAAW;GACZ,CACF,EACa,sBAAsB;SAC9B;AACN,MAAM;;AAGR,QAAO;EAAE,IAAI,EAAW;EAAI;EAAK,MAAM,EAAW;EAAM;;AAI1D,SAAgB,EAAW,GAAwB;CACjD,IAAM,EAAE,qBAAkB,GAAe;AACzC,QAAO,EAAS;EACd,UAAU;GAAC;GAAc;GAAY,KAAU;GAAG;EAClD,SAAS,EAAQ;EACjB,WAAW,OAAU;EACrB,OAAO;EACP,SAAS,YAAY;GACnB,IAAM,IAAW,MAAM,EAAoD;IACzE,SAAS;IACT,SAAS;IACT,gBAAgB;IAChB,OAAO;IACP,WAAW;KAAE,IAAI;KAAQ,WAAW;KAA0B;IAC/D,CAAC;AACF,OAAI,EAAS,QAAQ,OACnB,OAAU,MAAM,EAAS,OAAO,IAAI,WAAW,6BAA6B;AAE9E,UAAO,EAAS,MAAM,sBAAsB;;EAE/C,CAAC"}
|