Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions packages/studio/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "./utils/projectRouting";
import { useState, useCallback, useRef, useMemo, useLayoutEffect } from "react";
import type { LeftSidebarHandle, SidebarTab } from "./components/sidebar/LeftSidebar";
import { useRenderQueue } from "./components/renders/useRenderQueue";
Expand Down Expand Up @@ -326,9 +327,10 @@ export function StudioApp() {
const renderClipContent = useRenderClipContent({
projectIdRef: fileManager.projectIdRef,
compIdToSrc,
activePreviewUrl: activeCompPath
? `/api/projects/${projectId}/preview/comp/${activeCompPath}`
: null,
activePreviewUrl:
activeCompPath && projectId
? buildProjectApiPath(projectId, `/preview/comp/${activeCompPath}`)
: null,
effectiveTimelineDuration,
});
const compositionDimensions = useCompositionDimensions();
Expand Down
5 changes: 3 additions & 2 deletions packages/studio/src/captions/hooks/useCaptionSync.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
import { useCallback, useRef } from "react";
import { useCaptionStore } from "../store";
import { useMountEffect } from "../../hooks/useMountEffect";
Expand Down Expand Up @@ -92,7 +93,7 @@ export function useCaptionSync(projectId: string | null) {
const seqAtSave = editSeqRef.current;
const overrides = buildOverrides(state.model);

fetch(`/api/projects/${pid}/files/${encodeURIComponent("caption-overrides.json")}`, {
fetch(buildProjectApiPath(pid, `/files/${encodeURIComponent("caption-overrides.json")}`), {
method: "PUT",
headers: { "Content-Type": "text/plain", ...studioWriteHeaders() },
body: JSON.stringify(overrides, null, 2),
Expand Down Expand Up @@ -171,7 +172,7 @@ export function useCaptionSync(projectId: string | null) {
let data: { content?: string };
try {
const res = await fetch(
`/api/projects/${pid}/files/${encodeURIComponent("caption-overrides.json")}`,
buildProjectApiPath(pid, `/files/${encodeURIComponent("caption-overrides.json")}`),
);
if (!res.ok) return; // no overrides file yet — normal
data = await res.json();
Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/components/MediaPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../utils/projectRouting";
import { useState } from "react";
import { IMAGE_EXT, VIDEO_EXT, AUDIO_EXT } from "../utils/mediaTypes";

Expand Down Expand Up @@ -28,7 +29,7 @@ function MediaErrorPanel({ name, filePath }: { name: string; filePath: string })
}

export function MediaPreview({ projectId, filePath }: { projectId: string; filePath: string }) {
const serveUrl = `/api/projects/${projectId}/preview/${filePath}`;
const serveUrl = buildProjectApiPath(projectId, `/preview/${filePath}`);
const name = filePath.split("/").pop() ?? filePath;
// Keyed by path so switching to another file clears a previous failure.
const [failedPath, setFailedPath] = useState<string | null>(null);
Expand Down
26 changes: 1 addition & 25 deletions packages/studio/src/components/editor/domEditingLayers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { probeSourceElement } from "./probeSourceElement";
import type { PatchOperation } from "../../utils/sourcePatcher";
import {
resolveEditingAffordances,
Expand Down Expand Up @@ -281,31 +282,6 @@ export function resolveDomEditCapabilities(args: {
).capabilities;
}

async function probeSourceElement(
projectId: string,
sourceFile: string,
target: { id?: string; hfId?: string; selector?: string; selectorIndex?: number },
): Promise<boolean> {
try {
const response = await fetch(
`/api/projects/${projectId}/file-mutations/probe-element/${encodeURIComponent(sourceFile)}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ target }),
},
);
if (!response.ok) return true;
const data = await response.json();
if (data && typeof data === "object" && "exists" in data && data.exists === false) {
return false;
}
return true;
} catch {
return true;
}
}

// fallow-ignore-next-line complexity
export async function resolveDomEditSelection(
startEl: HTMLElement | null,
Expand Down
29 changes: 29 additions & 0 deletions packages/studio/src/components/editor/probeSourceElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { buildProjectApiPath } from "../../utils/projectRouting";

export async function probeSourceElement(
projectId: string,
sourceFile: string,
target: { id?: string; hfId?: string; selector?: string; selectorIndex?: number },
): Promise<boolean> {
try {
const response = await fetch(
buildProjectApiPath(
projectId,
`/file-mutations/probe-element/${encodeURIComponent(sourceFile)}`,
),
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ target }),
},
);
if (!response.ok) return true;
const data = await response.json();
if (data && typeof data === "object" && "exists" in data && data.exists === false) {
return false;
}
return true;
} catch {
return true;
}
}
3 changes: 2 additions & 1 deletion packages/studio/src/components/editor/propertyPanelFill.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
import { useMemo, useRef, useState } from "react";
import { Plus, RotateCcw, X } from "../../icons/SystemIcons";
import {
Expand Down Expand Up @@ -158,7 +159,7 @@ export function ImageFillField({
{selectedAsset && (
<div className="overflow-hidden rounded-xl border border-neutral-800 bg-neutral-900/80">
<img
src={`/api/projects/${projectId}/preview/${selectedAsset}`}
src={buildProjectApiPath(projectId, `/preview/${selectedAsset}`)}
alt={selectedAsset.split("/").pop() ?? selectedAsset}
className="h-28 w-full object-contain bg-neutral-950/80"
/>
Expand Down
5 changes: 4 additions & 1 deletion packages/studio/src/components/feedback/projectProvenance.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
// ---------------------------------------------------------------------------
// How this project came to exist, and roughly what shape it is.
//
Expand Down Expand Up @@ -53,7 +54,9 @@ export async function captureProjectProvenance(
if (!scaffolded) return;

try {
const res = await fetch(`/api/projects/${projectId}/files/${encodeURIComponent(CONFIG_FILE)}`);
const res = await fetch(
buildProjectApiPath(projectId, `/files/${encodeURIComponent(CONFIG_FILE)}`),
);
if (!res.ok) return;
// The route answers with an envelope, not the file: {filename, content,
// version}. The config is the `content` string inside it.
Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/components/nle/NLEContext.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
import { useContext, useState, useCallback, useRef, useEffect, type ReactNode } from "react";
import { useTimelinePlayer, usePlayerStore } from "../../player";
import type { TimelineElement } from "../../player";
Expand Down Expand Up @@ -185,7 +186,7 @@ export function NLEProvider({
setCompositionSourceMap(emptyMap);
onCompIdToSrcChangeRef.current?.(emptyMap);

fetch(`/api/projects/${projectId}/files/index.html`, {
fetch(buildProjectApiPath(projectId, `/files/index.html`), {
signal: controller.signal,
})
.then((r) => {
Expand Down
15 changes: 11 additions & 4 deletions packages/studio/src/components/nle/useCompositionStack.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
// Composition drill-down stack management for NLEContext/EditorShell
import { useState, useCallback, useRef, useEffect } from "react";
import { usePlayerStore } from "../../player";
Expand Down Expand Up @@ -29,7 +30,7 @@ export function useCompositionStack({
{
id: "master",
label: "Master",
previewUrl: `/api/projects/${projectId}/preview`,
previewUrl: buildProjectApiPath(projectId, `/preview`),
},
]);

Expand Down Expand Up @@ -89,7 +90,10 @@ export function useCompositionStack({
.split("/")
.pop()
?.replace(/\.html$/, "") || resolvedPath;
const previewUrl = `/api/projects/${projectId}/preview/comp/${encodePreviewPath(resolvedPath)}`;
const previewUrl = buildProjectApiPath(
projectId,
`/preview/comp/${encodePreviewPath(resolvedPath)}`,
);
return [...prev, { id: resolvedPath, label, previewUrl }];
});
},
Expand All @@ -103,7 +107,7 @@ export function useCompositionStack({
const master: CompositionLevel = {
id: "master",
label: "Master",
previewUrl: `/api/projects/${projectId}/preview`,
previewUrl: buildProjectApiPath(projectId, `/preview`),
};
if (activeCompositionPath === "index.html") {
usePlayerStore.getState().setElements([]);
Expand All @@ -116,7 +120,10 @@ export function useCompositionStack({
// panel highlighted the row, so the canvas and timeline stayed on
// index.html and edits landed in the root file.
const label = activeCompositionPath.replace(/^compositions\//, "").replace(/\.html$/, "");
const previewUrl = `/api/projects/${projectId}/preview/comp/${encodePreviewPath(activeCompositionPath)}`;
const previewUrl = buildProjectApiPath(
projectId,
`/preview/comp/${encodePreviewPath(activeCompositionPath)}`,
);
usePlayerStore.getState().setElements([]);
updateCompositionStack((prev) => {
if (prev[prev.length - 1]?.id === activeCompositionPath) return prev;
Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/components/renders/RenderQueueItem.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
import { memo, useCallback, useState } from "react";
import { VideoFrameThumbnail } from "../ui/VideoFrameThumbnail";
import { Button } from "../ui/Button";
Expand Down Expand Up @@ -36,7 +37,7 @@ export const RenderQueueItem = memo(function RenderQueueItem({
const [confirmingDelete, setConfirmingDelete] = useState(false);

// Direct file URL — serves from disk, survives server restarts
const fileSrc = `/api/projects/${projectId}/renders/file/${job.filename}`;
const fileSrc = buildProjectApiPath(projectId, `/renders/file/${job.filename}`);

const handleOpen = useCallback(() => {
window.open(fileSrc, "_blank");
Expand Down
5 changes: 3 additions & 2 deletions packages/studio/src/components/renders/useRenderQueue.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
import { useState, useEffect, useCallback, useRef, useMemo } from "react";
import type { CanvasResolution } from "@hyperframes/parsers";
import { trackStudioRenderStart } from "../../telemetry/events";
Expand Down Expand Up @@ -125,7 +126,7 @@ export function useRenderQueue(
const loadRenders = useCallback(async () => {
if (!projectId) return;
try {
const res = await fetch(`/api/projects/${projectId}/renders`);
const res = await fetch(buildProjectApiPath(projectId, `/renders`));
if (!res.ok) {
setLoadError(`Couldn't load render history (server error ${res.status}).`);
return;
Expand Down Expand Up @@ -261,7 +262,7 @@ export function useRenderQueue(
}
let res: Response;
try {
res = await fetch(`/api/projects/${projectId}/render`, {
res = await fetch(buildProjectApiPath(projectId, `/render`), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/components/sidebar/AssetsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
// fallow-ignore-file code-duplication
import { memo, useState, useCallback, useRef, useMemo, useEffect } from "react";
import { SearchInput } from "../ui/SearchInput";
Expand Down Expand Up @@ -216,7 +217,7 @@ export const AssetsTab = memo(function AssetsTab({
useEffect(() => {
if (manifest404Ref.current.has(projectId)) return;
let cancelled = false;
fetch(`/api/projects/${projectId}/preview/.media/manifest.jsonl`)
fetch(buildProjectApiPath(projectId, `/preview/.media/manifest.jsonl`))
.then((r) => {
if (!r.ok) {
manifest404Ref.current.add(projectId);
Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/components/sidebar/CompositionsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
import { memo, useCallback, useEffect, useRef, useState } from "react";
import { buildCompositionThumbnailUrl } from "../../player/components/CompositionThumbnail";
import { setPreviewMediaMuted } from "../../player/lib/timelineIframeHelpers";
Expand Down Expand Up @@ -173,7 +174,7 @@ function CompCard({
setLivePreviewLoaded(false);
};
const name = comp.replace(/^compositions\//, "").replace(/\.html$/, "");
const previewUrl = `/api/projects/${projectId}/preview/comp/${comp}`;
const previewUrl = buildProjectApiPath(projectId, `/preview/comp/${comp}`);
const thumbnailUrl = buildCompositionThumbnailUrl({
previewUrl,
seekTime: THUMBNAIL_SEEK_TIME_SECONDS,
Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/components/storyboard/FramePoster.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../../utils/projectRouting";
import { useEffect, useState } from "react";
import { buildCompositionThumbnailUrl } from "../../player/components/CompositionThumbnail";

Expand Down Expand Up @@ -51,7 +52,7 @@ export function FramePoster({
);
}
let url = buildCompositionThumbnailUrl({
previewUrl: `/api/projects/${projectId}/preview/comp/${src}`,
previewUrl: buildProjectApiPath(projectId, `/preview/comp/${src}`),
seekTime: seconds,
duration: 0,
origin: window.location.origin,
Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/hooks/useAskAgentModal.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../utils/projectRouting";
import { useState, useCallback, useRef, useEffect } from "react";
import { copyTextToClipboard } from "../utils/clipboard";
import { readTagSnippetByTarget } from "../utils/sourcePatcher";
Expand Down Expand Up @@ -53,7 +54,7 @@ export function useAskAgentModal({
const targetPath = selection.sourceFile || activeCompPath || "index.html";
try {
const response = await fetch(
`/api/projects/${pid}/files/${encodeURIComponent(targetPath)}`,
buildProjectApiPath(pid, `/files/${encodeURIComponent(targetPath)}`),
);
if (!response.ok) return;

Expand Down
3 changes: 2 additions & 1 deletion packages/studio/src/hooks/useCaptionDetection.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../utils/projectRouting";
import { useEffect, useRef } from "react";
import { useCaptionStore } from "../captions/store";
import { acceptStudioRuntimeMessage } from "../player/lib/runtimeProtocol";
Expand Down Expand Up @@ -107,7 +108,7 @@ export function useCaptionDetection({

activating = true;
const srcPath = captionSrcPath;
fetch(`/api/projects/${projectId}/files/${encodeURIComponent(srcPath)}`)
fetch(buildProjectApiPath(projectId, `/files/${encodeURIComponent(srcPath)}`))
.then((r) => r.json())
.then((data: { content?: string }) => {
if (!data.content || !doc || !win || useCaptionStore.getState().isEditMode) return;
Expand Down
4 changes: 3 additions & 1 deletion packages/studio/src/hooks/useCompositionContentLoader.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../utils/projectRouting";
import { useCallback } from "react";

/**
Expand All @@ -20,9 +21,10 @@ export function useCompositionContentLoader({
}) {
return useCallback(
(comp: string) => {
if (!projectId) return;
setActiveCompPath(comp.endsWith(".html") ? comp : null);
setEditingFile({ path: comp, content: null });
fetch(`/api/projects/${projectId}/files/${comp}`)
fetch(buildProjectApiPath(projectId, `/files/${encodeURIComponent(comp)}`))
.then(async (r) => {
if (!r.ok) throw new Error(`Failed to load ${comp} (${r.status})`);
return r.json();
Expand Down
9 changes: 5 additions & 4 deletions packages/studio/src/hooks/useDomEditCommits.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../utils/projectRouting";
import { useCallback, useRef } from "react";
import { findUnsafeDomPatchValues } from "@hyperframes/core/studio-api/finite-mutation";
import { FONT_EXT } from "../utils/mediaTypes";
Expand Down Expand Up @@ -127,11 +128,11 @@ export function useDomEditCommits({
FONT_EXT.test(path) &&
fontFamilyFromAssetPath(path).toLowerCase() === family.toLowerCase(),
);
if (!asset) return null;
if (!asset || !projectId) return null;
return {
family: fontFamilyFromAssetPath(asset),
path: asset,
url: `/api/projects/${projectId}/preview/${asset}`,
url: buildProjectApiPath(projectId, `/preview/${asset}`),
};
},
[fileTree, projectId, importedFontAssetsRef],
Expand Down Expand Up @@ -161,7 +162,7 @@ export function useDomEditCommits({
};

const readResponse = await fetch(
`/api/projects/${pid}/files/${encodeURIComponent(targetPath)}`,
buildProjectApiPath(pid, `/files/${encodeURIComponent(targetPath)}`),
);
if (!readResponse.ok) {
throw await createStudioSaveHttpError(readResponse, `Failed to read ${targetPath}`);
Expand Down Expand Up @@ -219,7 +220,7 @@ export function useDomEditCommits({
domEditSaveTimestampRef.current = Date.now();

const patchResponse = await fetch(
`/api/projects/${pid}/file-mutations/patch-element/${encodeURIComponent(targetPath)}`,
buildProjectApiPath(pid, `/file-mutations/patch-element/${encodeURIComponent(targetPath)}`),
{
method: "POST",
headers: { "Content-Type": "application/json", ...studioWriteHeaders() },
Expand Down
6 changes: 5 additions & 1 deletion packages/studio/src/hooks/useElementLifecycleOps.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildProjectApiPath } from "../utils/projectRouting";
import { useCallback } from "react";
import { usePlayerStore } from "../player";
import {
Expand Down Expand Up @@ -152,7 +153,10 @@ export function useElementLifecycleOps({
// selection runs to hundreds of members — the file ended up correct, but
// only after long enough that Delete looked like it had done nothing.
const removeResponse = await fetch(
`/api/projects/${pid}/file-mutations/remove-elements/${encodeURIComponent(targetPath)}`,
buildProjectApiPath(
pid,
`/file-mutations/remove-elements/${encodeURIComponent(targetPath)}`,
),
{
method: "POST",
headers: { "Content-Type": "application/json", ...studioWriteHeaders() },
Expand Down
Loading
Loading