@behindthemusictree/app-kit-ui 32.0.3 → 33.1.0

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.
@@ -117,18 +117,21 @@ function AuthErrorPopup({ message, onClose, ...rest }) {
117
117
 
118
118
  // src/popup/NetworkErrorPopup.tsx
119
119
  import { AlertTriangle } from "lucide-react";
120
+ import { ErrorCode } from "@behindthemusictree/app-kit/transport";
120
121
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
121
- function NetworkErrorPopup(props) {
122
+ var OFFLINE_MESSAGE = "It seems that you are not connected to the internet. Please check your connection and try again.";
123
+ var UNREACHABLE_MESSAGE = "We couldn't reach the server. Please try again in a moment.";
124
+ function NetworkErrorPopup({ errorCode, ...rest }) {
122
125
  return /* @__PURE__ */ jsx3(
123
126
  BasePopup,
124
127
  {
125
- ...props,
128
+ ...rest,
126
129
  title: "Network Error",
127
130
  isDismissable: false,
128
131
  icon: AlertTriangle,
129
132
  children: /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center space-y-6 py-4", children: [
130
133
  /* @__PURE__ */ jsx3(AlertTriangle, { className: "h-16 w-16 text-[var(--color-neutral-600)]", strokeWidth: 1.5 }),
131
- /* @__PURE__ */ jsx3("p", { className: "text-center text-[var(--color-neutral-700)]", children: "It seems that you are not connected to the internet. Please check your connection and try again." })
134
+ /* @__PURE__ */ jsx3("p", { className: "text-center text-[var(--color-neutral-700)]", children: errorCode === ErrorCode.NETWORK_OFFLINE ? OFFLINE_MESSAGE : UNREACHABLE_MESSAGE })
132
135
  ] })
133
136
  }
134
137
  );
@@ -271,4 +274,4 @@ export {
271
274
  AuthPopup,
272
275
  SpotifyAuthErrorPopup
273
276
  };
274
- //# sourceMappingURL=chunk-XUC4UJDQ.js.map
277
+ //# sourceMappingURL=chunk-K4WMHUNM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/popup/TrackUploadPopup.tsx","../src/popup/AuthErrorPopup.tsx","../src/popup/NetworkErrorPopup.tsx","../src/popup/AuthPopup.tsx","../src/popup/SpotifyAuthErrorPopup.tsx"],"sourcesContent":["\"use client\";\n\nimport { MdError, MdCheckCircle, MdUpload } from \"react-icons/md\";\nimport { useTrackUpload, type UploadStatus, type UseTrackUploadOptions } from \"@behindthemusictree/app-kit/popup\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { capitalize, useTrackNoun } from \"@behindthemusictree/app-kit/track-noun\";\n\ntype TrackUploadPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> &\n UseTrackUploadOptions & {\n onClose?: () => void;\n };\n\nfunction getStatusIcon(status: UploadStatus) {\n switch (status) {\n case \"success\":\n return <MdCheckCircle className=\"text-[var(--color-green-500)]\" size={20} />;\n case \"error\":\n return <MdError className=\"text-[var(--color-red-500)]\" size={20} />;\n case \"uploading\":\n return <MdUpload className=\"text-[var(--color-blue-500)] animate-pulse\" size={20} />;\n default:\n return <div className=\"w-5 h-5 rounded-full border-2 border-[var(--color-neutral-300)]\" />;\n }\n}\n\nfunction getStatusText(status: UploadStatus) {\n switch (status) {\n case \"success\":\n return \"Uploaded\";\n case \"error\":\n return \"Failed\";\n case \"uploading\":\n return \"Uploading...\";\n default:\n return \"Pending\";\n }\n}\n\nexport default function TrackUploadPopup({\n files,\n genre,\n onProcessFile,\n onComplete,\n onClose,\n uploadTimeoutMs,\n ...rest\n}: TrackUploadPopupProps) {\n const { items, isUploading, allComplete, successfulCount, errorCount } = useTrackUpload({\n files,\n genre,\n onProcessFile,\n onComplete,\n uploadTimeoutMs,\n });\n\n const displayTotal = items.length > 0 ? items.length : files.length;\n const trackNoun = useTrackNoun();\n\n return (\n <BasePopup\n {...rest}\n onClose={onClose}\n title={`Upload ${capitalize(trackNoun.plural)} (${successfulCount}/${displayTotal})`}\n isDismissable\n showOkButton\n okButtonText=\"OK\"\n okButtonDisabled={isUploading || !allComplete}\n onOk={onClose}\n >\n <div className=\"space-y-4\">\n {items.map((item) => (\n <div key={item.id} className=\"border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"flex items-center space-x-3\">\n {getStatusIcon(item.status)}\n <div>\n <div className=\"font-medium text-sm truncate max-w-xs\" title={item.file.name}>\n {item.file.name}\n </div>\n <div className=\"text-xs text-[var(--color-neutral-500)]\">{(item.file.size / 1024 / 1024).toFixed(1)} MB</div>\n </div>\n </div>\n <div className=\"text-sm text-[var(--color-neutral-600)]\">{getStatusText(item.status)}</div>\n </div>\n\n {item.status === \"uploading\" && (\n <div className=\"w-full bg-[var(--color-neutral-200)] rounded-full h-2\">\n <div\n className=\"bg-[var(--color-blue-500)] h-2 rounded-full transition-all duration-300\"\n style={{ width: `${item.progress}%` }}\n />\n </div>\n )}\n\n {item.status === \"error\" && item.error && (\n <div className=\"text-[var(--color-red-500)] text-sm mt-2\">\n {item.error.name === \"InvalidInputError\"\n ? \"Upload failed due to invalid file data. Please check your file and try again.\"\n : item.error.name === \"ZodError\"\n ? \"Upload failed due to a server error. Please try again later.\"\n : item.error.message}\n </div>\n )}\n </div>\n ))}\n\n {allComplete && (\n <div className=\"flex justify-between items-center\">\n <div className=\"text-sm text-[var(--color-neutral-600)]\">\n {successfulCount} successful, {errorCount} failed\n </div>\n </div>\n )}\n </div>\n </BasePopup>\n );\n}\n","\"use client\";\n\nimport { AlertCircle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { Button } from \"@behindthemusictree/ui\";\n\ntype AuthErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n message: string;\n onClose: () => void;\n};\n\nexport default function AuthErrorPopup({ message, onClose, ...rest }: AuthErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Sign-in error\"\n isDismissable\n icon={AlertCircle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <p className=\"text-center text-lg font-medium text-[var(--color-text)]\">{message}</p>\n <Button onClick={onClose} variant=\"secondary\" className=\"w-full max-w-xs\">\n Close\n </Button>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { AlertTriangle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\ntype NetworkErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n errorCode: ErrorCode;\n};\n\nconst OFFLINE_MESSAGE = \"It seems that you are not connected to the internet. Please check your connection and try again.\";\nconst UNREACHABLE_MESSAGE = \"We couldn't reach the server. Please try again in a moment.\";\n\nexport default function NetworkErrorPopup({ errorCode, ...rest }: NetworkErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Network Error\"\n isDismissable={false}\n icon={AlertTriangle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <AlertTriangle className=\"h-16 w-16 text-[var(--color-neutral-600)]\" strokeWidth={1.5} />\n <p className=\"text-center text-[var(--color-neutral-700)]\">\n {errorCode === ErrorCode.NETWORK_OFFLINE ? OFFLINE_MESSAGE : UNREACHABLE_MESSAGE}\n </p>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\nimport { User } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { FaSpotify } from \"react-icons/fa\";\nimport { FcGoogle } from \"react-icons/fc\";\n\ntype AuthPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n handleSpotifyOAuth: (redirectAfterAuthPath?: string) => void;\n handleGoogleOAuth?: (redirectAfterAuthPath?: string) => void;\n redirectAfterAuthPath?: string;\n spotifyOnly?: boolean;\n message?: string;\n /**\n * Body copy shown when `spotifyOnly` is true. Wording (app name, feature description) is\n * app-specific, so each consuming app supplies its own instead of this package assuming one.\n */\n spotifyOnlyDescription: ReactNode;\n /** Body copy shown when `spotifyOnly` is false, for the same reason as `spotifyOnlyDescription`. */\n defaultDescription: ReactNode;\n};\n\nexport default function AuthPopup({\n handleSpotifyOAuth,\n handleGoogleOAuth,\n redirectAfterAuthPath,\n spotifyOnly,\n message,\n spotifyOnlyDescription,\n defaultDescription,\n ...rest\n}: AuthPopupProps) {\n const showGoogle = !spotifyOnly && handleGoogleOAuth;\n\n return (\n <BasePopup\n {...rest}\n title={spotifyOnly ? \"Connect with Spotify\" : \"Sign in\"}\n isDismissable={false}\n icon={User}\n type=\"auth\"\n children={\n <div className=\"flex flex-col items-center space-y-7\">\n <div className=\"px-2 text-center\">\n {message && <p className=\"mb-3 text-base font-medium leading-relaxed text-amber-200\">{message}</p>}\n <p className=\"text-lg font-medium leading-relaxed text-white\">\n {spotifyOnly ? spotifyOnlyDescription : defaultDescription}\n </p>\n </div>\n <div className=\"flex w-full flex-col items-center gap-3\">\n <Button\n onClick={() => handleSpotifyOAuth(redirectAfterAuthPath)}\n className=\"relative mb-2 w-fit transform bg-green-600 px-8 py-3 text-white shadow-lg transition-all duration-200 hover:scale-[1.02] hover:bg-green-700 hover:shadow-xl\"\n >\n <span className=\"flex items-center justify-center\">\n <FaSpotify className=\"absolute left-6 text-3xl\" />\n <span className=\"w-full pl-14 pr-8 text-center text-lg font-medium\">Sign in with Spotify</span>\n </span>\n </Button>\n {showGoogle && (\n <Button\n variant=\"outline\"\n onClick={() => handleGoogleOAuth!(redirectAfterAuthPath)}\n className=\"relative w-fit transform border-white bg-white px-8 py-3 text-black shadow-lg transition-all duration-200 hover:scale-[1.02] hover:bg-[var(--color-neutral-100)] hover:shadow-xl\"\n >\n <span className=\"flex items-center justify-center\">\n <FcGoogle className=\"absolute left-6 text-2xl\" />\n <span className=\"w-full pl-14 pr-8 text-center text-lg font-medium text-black\">\n Sign in with Google\n </span>\n </span>\n </Button>\n )}\n </div>\n </div>\n }\n />\n );\n}\n","\"use client\";\n\nimport { FaSpotify } from \"react-icons/fa\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { User } from \"lucide-react\";\nimport { getSpotifyAllowlistMailtoHref } from \"@behindthemusictree/app-kit/transport\";\n\ntype SpotifyAuthErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n message: string;\n details?: string;\n onClose: () => void;\n /**\n * Support contact email used to build the allowlist-request mailto link. Passed by the\n * consuming app (from whatever env var / config it uses) so this package doesn't assume a\n * specific env var name.\n */\n contactEmail?: string | null;\n};\n\nexport default function SpotifyAuthErrorPopup({\n message,\n details,\n onClose,\n contactEmail = null,\n ...rest\n}: SpotifyAuthErrorPopupProps) {\n const requestAccessHref = getSpotifyAllowlistMailtoHref(contactEmail);\n\n return (\n <BasePopup\n {...rest}\n title=\"Authentication Failed\"\n isDismissable\n icon={User}\n children={\n <div className=\"flex flex-col items-center space-y-8 py-4\">\n <FaSpotify className=\"text-[#1DB954] text-7xl\" />\n <div className=\"space-y-3 text-center\">\n <p className=\"text-xl font-medium text-[var(--color-text)]\">{message}</p>\n {details && (\n <p className=\"text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]\">{details}</p>\n )}\n <div className=\"space-y-2 text-center\">\n <p className=\"text-base text-[var(--color-neutral-700)]\">\n The Spotify app is in development mode, so only allowlisted accounts can connect.\n </p>\n {requestAccessHref && contactEmail ? (\n <p className=\"text-base text-[var(--color-neutral-700)]\">\n Request access by emailing your{\" \"}\n <a\n href={requestAccessHref}\n className=\"text-[#1DB954] font-medium underline hover:no-underline focus:outline-none focus:ring-2 focus:ring-[#1DB954] focus:ring-offset-1 rounded\"\n >\n Spotify full name and Spotify email address\n </a>{\" \"}\n to{\" \"}\n <a\n href={requestAccessHref}\n className=\"text-[#1DB954] font-medium underline hover:no-underline focus:outline-none focus:ring-2 focus:ring-[#1DB954] focus:ring-offset-1 rounded\"\n >\n {contactEmail}\n </a>\n .\n </p>\n ) : (\n <p className=\"text-base text-[var(--color-neutral-700)]\">To request access, contact the app owner.</p>\n )}\n </div>\n </div>\n <Button\n onClick={onClose}\n className=\"bg-[#1DB954] hover:bg-[#1DB954]/90 text-white w-full max-w-xs transform transition-all duration-200 hover:scale-[1.02] shadow-lg hover:shadow-xl\"\n >\n <FaSpotify className=\"mr-2 text-lg\" />\n Try Again\n </Button>\n </div>\n }\n />\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","Button","jsx","jsxs","FaSpotify","Button","User","jsx","jsxs","FaSpotify"]}
@@ -784,7 +784,7 @@ function GenreSearch({
784
784
  onPick: handlePick,
785
785
  status,
786
786
  loading: loading ? "Searching\u2026" : void 0,
787
- inputClassName: `h-[var(--layout-search-h,3rem)] ${onPrevious ? "pl-11" : "pl-6"} pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md`,
787
+ inputClassName: `h-[var(--layout-search-h,3rem)] ${onPrevious ? "pl-11" : "pl-6"} pr-20 text-base transition-[padding] duration-200 ease-out motion-reduce:transition-none bg-[var(--color-surface)] border-[var(--color-border)] rounded-full shadow-md focus-visible:ring-0`,
788
788
  className: "genre-search-results z-40 rounded-xl shadow-md",
789
789
  listClassName: "max-h-[min(20rem,50dvh)]",
790
790
  optionClassName: "[@media(pointer:coarse)]:min-h-[var(--layout-touch-target)]"
@@ -1285,8 +1285,9 @@ function GenrePlaylistTracks({
1285
1285
  const trackNoun = (0, import_track_noun.useTrackNoun)();
1286
1286
  const { uuid } = genrePlaylist;
1287
1287
  const { fetch } = (0, import_transport3.useFetchWrapper)(getBackendBaseUrl);
1288
+ const playback = (0, import_transport3.usePlaybackContext)();
1288
1289
  const { data, isPending, isError, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)({
1289
- queryKey: scope === "me" ? import_genre_tree4.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid),
1290
+ queryKey: [...scope === "me" ? import_genre_tree4.genrePlaylistQueryKeys.me.tracks(uuid) : artist ? [...import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), "artist", artist.uuid] : import_genre_tree5.libraryQueryKeys.reference.songs.byGenre(getBackendBaseUrl(), genreUuid), ...playback.key ? [playback.key] : []],
1290
1291
  queryFn: async ({ pageParam }) => {
1291
1292
  if (scope === "me") return fetchGenrePlaylistTracksPage(uuid, scope, Number(pageParam));
1292
1293
  const response = await fetch(
@@ -1301,7 +1302,7 @@ function GenrePlaylistTracks({
1301
1302
  },
1302
1303
  initialPageParam: scope === "me" ? 1 : "",
1303
1304
  getNextPageParam: (lastPage) => lastPage.nextPage ?? void 0,
1304
- enabled: scope !== "me" || sessionRestored && !!session?.accessToken
1305
+ enabled: playback.ready && (scope !== "me" || sessionRestored && !!session?.accessToken)
1305
1306
  });
1306
1307
  const [sentinel, setSentinel] = (0, import_react11.useState)(null);
1307
1308
  (0, import_react11.useEffect)(() => {
@@ -2573,7 +2574,10 @@ function GenreTreeView({
2573
2574
  const ancestors = /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree_view4.GenreNodeAncestors, { ancestorNodes: relation.ancestorNodes, onSelectNode: selectGenreFromPanel });
2574
2575
  const genrePlaylist = genrePlaylistByNodeId.get(node.id);
2575
2576
  const criteria = genrePlaylist?.criteria;
2576
- if (!genrePlaylist || !criteria) return { primary: family, aside: ancestors };
2577
+ if (!genrePlaylist || !criteria) return { primary: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2578
+ family,
2579
+ ancestors
2580
+ ] }) };
2577
2581
  const genreUuid = criteria.uuid;
2578
2582
  const parentPlaylist = visibleGenrePlaylists.find(({ uuid }) => uuid === genrePlaylist.parent?.uuid);
2579
2583
  const canToggleSide = !readOnly && parentPlaylist !== void 0 && parentPlaylist.parent === null;
@@ -2594,7 +2598,10 @@ function GenreTreeView({
2594
2598
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2595
2599
  GenreInfoPanelBody,
2596
2600
  {
2597
- family,
2601
+ family: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(import_jsx_runtime34.Fragment, { children: [
2602
+ family,
2603
+ ancestors
2604
+ ] }),
2598
2605
  genreUuid,
2599
2606
  genrePlaylist,
2600
2607
  wikidataId: criteria.wikidataId,
@@ -2641,8 +2648,7 @@ function GenreTreeView({
2641
2648
  }
2642
2649
  ),
2643
2650
  renderDangerZone?.(node)
2644
- ] }),
2645
- aside: ancestors
2651
+ ] })
2646
2652
  };
2647
2653
  };
2648
2654
  const isRotationControlled = controlledAllowWheelRotation !== void 0;
@@ -2659,11 +2665,10 @@ function GenreTreeView({
2659
2665
  compact: false
2660
2666
  };
2661
2667
  } else if (relations) {
2662
- const { primary, aside } = renderGenreBody(relations);
2668
+ const { primary } = renderGenreBody(relations);
2663
2669
  activePanel = {
2664
2670
  title: relations.node.name,
2665
2671
  content: primary,
2666
- aside,
2667
2672
  expanded: isExpanded,
2668
2673
  onExpandedChange: changeExpanded,
2669
2674
  onClose: clearSelection,
@@ -3040,12 +3045,14 @@ function GenreDetailPanel({
3040
3045
  expanded,
3041
3046
  onExpandedChange,
3042
3047
  edgeTab,
3043
- aside: ancestors,
3044
3048
  children: [
3045
3049
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
3046
3050
  GenreInfoPanelBody,
3047
3051
  {
3048
- family,
3052
+ family: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
3053
+ family,
3054
+ ancestors
3055
+ ] }),
3049
3056
  genreUuid: detail.uuid,
3050
3057
  genrePlaylist: { ...detail.criteriaPlaylist, tracksCount: detail.tracksCount },
3051
3058
  wikidataId: detail.wikidataId,
@@ -3364,7 +3371,7 @@ function PlayerPanel({
3364
3371
  renderArtistLink,
3365
3372
  renderSongLink
3366
3373
  }) {
3367
- const { playerTrackObject } = (0, import_player13.usePlayer)();
3374
+ const { playerTrackObject, retryPlayback } = (0, import_player13.usePlayer)();
3368
3375
  const { toggleTrackListSidebar, isTrackListSidebarVisible } = (0, import_genre_tree24.useTrackListSidebarVisibility)();
3369
3376
  const [lastLoadedTrackTitle, setLastLoadedTrackTitle] = (0, import_react19.useState)("");
3370
3377
  const loadedTrackTitle = playerTrackObject?.track.title;
@@ -3427,7 +3434,12 @@ function PlayerPanel({
3427
3434
  /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-zinc-800 p-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "min-w-0 truncate text-sm font-semibold", children: loadedTrackTitle || lastLoadedTrackTitle }) }),
3428
3435
  /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "relative w-full shrink-0", children: [
3429
3436
  /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(import_player13.PlayerVideoSurface, { className: "aspect-video w-full bg-black" }),
3430
- playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: playerTrackObject.loadError })
3437
+ playerTrackObject?.loadError && /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("span", { className: "absolute bottom-2 left-2 right-2 text-xs text-red-400", children: [
3438
+ playerTrackObject.loadError,
3439
+ /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("button", { type: "button", className: "ml-2 underline", onClick: () => {
3440
+ void retryPlayback();
3441
+ }, children: "Retry" })
3442
+ ] })
3431
3443
  ] }),
3432
3444
  /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(PlayerControls, {})
3433
3445
  ] }),