Skip to content

Commit c8828fe

Browse files
committed
add error when not importing TS / TSX files
1 parent 2b324b2 commit c8828fe

6 files changed

Lines changed: 93 additions & 41 deletions

File tree

packages/@triplex/editor-next/src/features/app-root/dnd-helper.tsx

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,20 @@ import { useEffect, useState } from "react";
99
import { preloadSubscription } from "../../hooks/ws";
1010
import { handleVSCERequestResponse, onVSCE, requestVSCE, type ToVSCodeEvent } from "../../util/bridge";
1111
import { useSceneContext } from "./context";
12-
import { type UseDNDReturn } from "../../../../../lib/src/use-dnd";
12+
import { type UseDNDReturnError, type UseDNDReturn } from "../../../../../lib/src/use-dnd";
1313
import { Dialog } from "@triplex/ux";
1414
import { on } from "@triplex/bridge/host";
1515

16-
export function DNDHelper({ children }: { children: React.ReactNode }) {
16+
export function FileDNDHelper({ children }: { children: React.ReactNode }) {
1717
const context = useSceneContext();
1818

19-
const [errorData, setErrorData] = useState<{
20-
exportNames: string[];
21-
type: 'multiple-exports' | 'unknown';
22-
}>();
19+
const [errorData, setErrorData] = useState<UseDNDReturnError>();
2320
const [retryData, setRetryData] = useState<ToVSCodeEvent['component-insert']>();
2421

2522
const handleComponentInsert = async (_: string, data: ToVSCodeEvent['component-insert']) => {
2623
const result = await requestVSCE<UseDNDReturn, "component-insert">("component-insert", data);
2724
if (!result.success) {
28-
setErrorData({
29-
exportNames: result.error.multipleExports,
30-
type: result.error.type || 'unknown',
31-
});
25+
setErrorData(result.error);
3226
setRetryData(data);
3327
} else {
3428
setErrorData(undefined);
@@ -69,14 +63,34 @@ export function DNDHelper({ children }: { children: React.ReactNode }) {
6963
<div className="fixed inset-0 flex select-none"
7064
{...bindingsDND}
7165
>
72-
{errorData && (
66+
{errorData && errorData.type === 'unknown' && (
67+
<Dialog onDismiss={onDismissError}>
68+
<div className="flex flex-col gap-4 p-4">
69+
<span className="text-heading select-none font-medium">
70+
An error occurred while adding the component.
71+
</span>
72+
<span className="text-sm text-gray-600 break-all">
73+
{errorData.message}
74+
</span>
75+
<div className="flex justify-end">
76+
<button className="bg-blue-600 text-white px-4 py-2 rounded" onClick={onDismissError} type="button">
77+
Dismiss
78+
</button>
79+
</div>
80+
</div>
81+
</Dialog>
82+
)}
83+
{errorData && errorData.type === 'multiple-exports' && (
7384
<Dialog onDismiss={onDismissError}>
7485
<form className="flex flex-col gap-2.5 p-2.5" onSubmit={onSubmit}>
7586
<span className="text-heading select-none font-medium">
7687
Which component do you want to add?
7788
</span>
7889
<select>
79-
{errorData.exportNames.map((name) => (
90+
{errorData.multipleExports.map((name) => (
91+
<option key={name} value={name}>{name}</option>
92+
))}
93+
{errorData.multipleExports.map((name) => (
8094
<option key={name} value={name}>{name}</option>
8195
))}
8296
</select>

packages/@triplex/editor-next/src/features/app-root/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import { Panels } from "../panels";
1313
import { Dialogs } from "./dialogs";
1414
import { EmptyState } from "./empty-state";
1515
import { Events } from "./events";
16-
import { DNDHelper } from "./dnd-helper";
16+
import { FileDNDHelper } from "./dnd-helper";
1717

1818
export function AppRoot() {
1919
useScreenView("app", "Screen");
2020

2121
return (
22-
<DNDHelper>
22+
<FileDNDHelper>
2323
<Events />
2424
<Panels />
2525
<Dialogs />
@@ -35,7 +35,7 @@ export function AppRoot() {
3535
/>
3636
</div>
3737
{fg("ai_chat") && <AIChat />}
38-
</DNDHelper>
38+
</FileDNDHelper>
3939
);
4040
}
4141

packages/@triplex/server/src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import { resolveGitRepoVisibility } from "./util/git";
6868
import { getParam, getParamOptional } from "./util/params";
6969
import { getThumbnailPath } from "./util/thumbnail";
7070
import { resolveRemoteURL } from "./util/path";
71+
import { DNDError } from "./util/errors";
7172

7273
export * from "./types";
7374
export { type PropGroupDef } from "./ast/prop-groupings";
@@ -347,7 +348,16 @@ export async function createServer({
347348
router.post("/scene/:path/add-component", async (context) => {
348349
const { path: scenePath } = context.params;
349350

350-
const body = await context.request.body().value;
351+
const body = await context.request.body().value as Record<string, string>;
352+
353+
if (body.componentPath.endsWith('.ts') === false && body.componentPath.endsWith('.tsx') === false) {
354+
context.response.body = {
355+
error: new DNDError(`Component path ${body.componentPath} must end with .ts or .tsx`),
356+
status: "unmodified",
357+
success: false
358+
};
359+
return;
360+
}
351361

352362
const componentPath = fileURLToPath(resolveRemoteURL(body.componentPath));
353363

packages/@triplex/server/src/services/component.ts

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ export function addComponentToEnd(
644644
}
645645

646646
if (!componentFile) {
647-
throw new Error(`Component file not found: ${componentPath}`);
647+
throw new DNDError(`Component file not found: ${componentPath}`);
648648
}
649649

650650
// determin the imported export
@@ -655,19 +655,41 @@ export function addComponentToEnd(
655655

656656
// If no export name is provided and there are multiple exports, we need to ask the user to specify one.
657657
if (!defaultExport && exportNames.length > 1 && !exportName) {
658-
const error = new Error(`Multiple exports found in ${componentPath}, please specify an export to use.`) as Error & { multipleExports: string[]; type: string };
658+
const error = new DNDError(`Multiple exports found in ${componentPath}, please specify an export to use.`, 'multiple-exports');
659659
error.multipleExports = exportNames;
660-
error.type = 'multiple-exports';
661660
throw error;
662661
}
663662
// If no exports found, throw.
664663
if (!defaultExport && exportNames.length === 0) {
665-
throw new Error(`No exports found in ${componentPath}`);
664+
throw new DNDError(`No exports found in ${componentPath}`);
666665
}
667666

668667
// Determine which export to use
669668
const componentExportName = defaultExport ? "__default__" : exportName ?? exportNames[0];
670669

670+
// Determine the import name
671+
672+
if (!componentExportName) {
673+
throw new DNDError(`No exports found in ${componentPath}`);
674+
}
675+
676+
// Ensure the import exists
677+
const componentImportName = ensureImport(sceneFile, componentPath, componentExportName);
678+
679+
const components = [...sceneFile.getExportedDeclarations().keys()];
680+
const sceneExportName = components.includes(activeScene || "") ? activeScene : components[0];
681+
682+
if (!sceneExportName) {
683+
throw new DNDError(`No exports found in scene file`);
684+
}
685+
686+
const jsxElement = getExportJsxElement(sceneFile, sceneExportName);
687+
688+
const newComponentJsx = `<${componentImportName} />`;
689+
690+
insertAtEnd(sceneFile, jsxElement, newComponentJsx);
691+
}
692+
export function getUniqueImportName(componentExportName: string, componentPath: string, sceneFile: SourceFile) {
671693
// Determine the import name
672694
let componentImportName = componentExportName === "__default__" ? toPascalCase(basename(componentPath).replace(extname(componentPath), '')) : componentExportName;
673695
let componentImportNameAddition = 0;
@@ -696,35 +718,16 @@ export function addComponentToEnd(
696718
componentImportNameAddition++;
697719
}
698720
componentImportName = `${componentImportName}${componentImportNameAddition === 0 ? "" : '_' + componentImportNameAddition}`;
699-
700-
if (!componentExportName) {
701-
throw new Error(`No exports found in ${componentPath}`);
702-
}
703-
704-
// Ensure the import exists
705-
componentImportName = ensureImport(sceneFile, componentPath, componentExportName, componentImportName);
706-
707-
const components = [...sceneFile.getExportedDeclarations().keys()];
708-
const sceneExportName = components.includes(activeScene || "") ? activeScene : components[0];
709-
710-
if (!sceneExportName) {
711-
throw new Error(`No exports found in scene file`);
712-
}
713-
714-
const jsxElement = getExportJsxElement(sceneFile, sceneExportName);
715-
716-
const newComponentJsx = `<${componentImportName} />`;
717-
718-
insertAtEnd(sceneFile, jsxElement, newComponentJsx);
721+
return componentImportName;
719722
}
720723

721724
/** Ensures an import exists for the component */
722725
function ensureImport(
723726
sourceFile: SourceFile,
724727
modulePath: string,
725728
exportName: string,
726-
importName: string,
727729
): string {
730+
const importName = getUniqueImportName(exportName, modulePath, sourceFile);
728731
const baseFolderPath = dirname(sourceFile.getFilePath());
729732
const relativePath = omitFileExtension(prefixLocalPath(relative(baseFolderPath, modulePath)));
730733

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright (c) 2022—present Michael Dougall. All rights reserved.
3+
*
4+
* This repository utilizes multiple licenses across different directories. To
5+
* see this files license find the nearest LICENSE file up the source tree.
6+
*/
7+
export class DNDError extends Error {
8+
multipleExports?: string[];
9+
10+
constructor(public message: string, public type: string = 'unknown') {
11+
super(message);
12+
this.name = "DNDError";
13+
}
14+
15+
toJSON() {
16+
return {
17+
message: this.message,
18+
multipleExports: this.multipleExports,
19+
type: this.type,
20+
};
21+
}
22+
}

packages/lib/src/use-dnd.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export type UseDNDReturn = {
1717
export type UseDNDReturnError = {
1818
multipleExports: string[],
1919
type: 'multiple-exports'
20+
} | {
21+
message: string,
22+
type: 'unknown'
2023
}
2124

2225

0 commit comments

Comments
 (0)