Skip to content

Commit c1c87ad

Browse files
committed
Improve UI loading states & fix a few minor display bugs
1 parent 1aab176 commit c1c87ad

File tree

13 files changed

+264
-137
lines changed

13 files changed

+264
-137
lines changed

remote-workspace/src/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,20 @@ async function startServers() {
3333
? true
3434
: false;
3535

36-
await addAPIServer(server, expressApp, workspaceId, serverPort, frontendUrl);
36+
await addAPIServer(
37+
server,
38+
expressApp,
39+
"api-" + workspaceId,
40+
serverPort,
41+
frontendUrl,
42+
);
3743
console.log(
38-
`API server is running at ${isHttps ? "https" : "http"}://${address}:${serverPort}/${workspaceId}`,
44+
`API server is running at ${isHttps ? "https" : "http"}://${address}:${serverPort}/api-${workspaceId}`,
3945
);
4046

4147
await addTerminalServer(server, workspaceId);
4248
console.log(
43-
`Terminal server is running at ${isHttps ? "wss" : "ws"}://${address}:${serverPort}/${workspaceId}/terminal/ws`,
49+
`Terminal server is running at ${isHttps ? "wss" : "ws"}://${address}:${serverPort}/api-${workspaceId}/terminal/ws`,
4450
);
4551
}
4652

web/components/explorer/file-system/fs-explorer.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { usePlatformApi } from "@/lib/hooks/use-platform-api";
66
import { useTabViewManager } from "@/lib/hooks/use-tab-view-manager";
77
import { getPlatform } from "@/lib/platform-api/platform-checker";
88
import { TreeViewGroupRef } from "@/lib/types";
9-
import { addToast, Button } from "@heroui/react";
9+
import { addToast, Button, Spinner } from "@heroui/react";
1010
import { IMCMessageTypeEnum, ViewModeEnum } from "@pulse-editor/shared-utils";
11-
import { useContext, useEffect, useRef } from "react";
11+
import { useContext, useEffect, useRef, useState } from "react";
1212
import toast from "react-hot-toast";
1313
import Icon from "../../misc/icon";
1414
import { EditorContext } from "../../providers/editor-context-provider";
@@ -23,9 +23,11 @@ export default function FileSystemExplorer({
2323
const imcContext = useContext(IMCContext);
2424

2525
const platform = getPlatform();
26-
const { platformApi } = usePlatformApi();
26+
const { platformApi, refreshWorkspaceContent } = usePlatformApi();
2727
const { activeTabView } = useTabViewManager();
2828

29+
const [isLoading, setIsLoading] = useState(true);
30+
2931
const rootGroupRef = useRef<TreeViewGroupRef | null>(null);
3032

3133
const content = editorContext?.editorStates.workspaceContent ?? [];
@@ -45,6 +47,14 @@ export default function FileSystemExplorer({
4547
}
4648
}, [editorContext?.editorStates.explorerSelectedNodeRefs]);
4749

50+
useEffect(() => {
51+
if (editorContext?.editorStates.workspaceContent) {
52+
setIsLoading(false);
53+
} else {
54+
setIsLoading(true);
55+
}
56+
}, [editorContext?.editorStates.workspaceContent]);
57+
4858
async function viewFile(uri: string) {
4959
// Simply send uri to selected app node or app view
5060
if (activeTabView?.type === ViewModeEnum.App) {
@@ -191,21 +201,28 @@ export default function FileSystemExplorer({
191201
</div>
192202
</div>
193203

194-
{content?.length === 0 && (
195-
<p className="text-center">
196-
Empty content. Create a new file to get started.
197-
</p>
198-
)}
199-
200204
<div className="overflow-y-auto">
201205
<TreeViewGroup
202206
ref={rootGroupRef}
203207
objects={content}
204208
viewFile={viewFile}
205209
folderUri={fsPath}
206210
platformApi={platformApi}
211+
refreshWorkspaceContent={refreshWorkspaceContent}
207212
/>
208213
</div>
214+
215+
{isLoading && editorContext?.editorStates.currentWorkspace && (
216+
<div className="flex justify-center">
217+
<Spinner />
218+
</div>
219+
)}
220+
221+
{content.length === 0 && !isLoading && (
222+
<p className="text-center">
223+
Empty content. Create a new file to get started.
224+
</p>
225+
)}
209226
</div>
210227
</div>
211228
);

web/components/explorer/file-system/tree-view.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import ContextMenu from "@/components/interface/context-menu";
44
import Icon from "@/components/misc/icon";
55
import { EditorContext } from "@/components/providers/editor-context-provider";
66
import { PlatformEnum } from "@/lib/enums";
7-
import { useWorkspace } from "@/lib/hooks/use-workspace";
87
import { AbstractPlatformAPI } from "@/lib/platform-api/abstract-platform-api";
98
import { getPlatform } from "@/lib/platform-api/platform-checker";
109
import {
@@ -36,11 +35,13 @@ const TreeViewNode = forwardRef(function TreeViewNode(
3635
viewFile,
3736
platformApi,
3837
parentGroupRef,
38+
refreshWorkspaceContent,
3939
}: {
4040
object: FileSystemObject;
4141
viewFile: (uri: string) => Promise<void>;
4242
platformApi?: AbstractPlatformAPI;
4343
parentGroupRef: RefObject<TreeViewGroupRef>;
44+
refreshWorkspaceContent: () => Promise<void>;
4445
},
4546
ref: Ref<TreeViewNodeRef | null>,
4647
) {
@@ -56,7 +57,6 @@ const TreeViewNode = forwardRef(function TreeViewNode(
5657
},
5758
}));
5859

59-
const { refreshWorkspaceContent } = useWorkspace();
6060
const { setNodeRef, listeners } = useDraggable({
6161
id: `draggable-file-${object.uri}`,
6262
data: {
@@ -183,7 +183,7 @@ const TreeViewNode = forwardRef(function TreeViewNode(
183183
parentGroupRef.current?.getFolderUri() + "/" + newName;
184184

185185
platformApi?.rename(object.uri, newUri).then(() => {
186-
refreshWorkspaceContent(platformApi);
186+
refreshWorkspaceContent();
187187
});
188188

189189
setIsRenaming(false);
@@ -195,7 +195,7 @@ const TreeViewNode = forwardRef(function TreeViewNode(
195195
parentGroupRef.current?.getFolderUri() + "/" + newName;
196196

197197
platformApi?.rename(object.uri, newUri).then(() => {
198-
refreshWorkspaceContent(platformApi);
198+
refreshWorkspaceContent();
199199
});
200200

201201
setIsRenaming(false);
@@ -324,7 +324,7 @@ const TreeViewNode = forwardRef(function TreeViewNode(
324324
color="danger"
325325
onPress={(e) => {
326326
platformApi?.delete(object.uri).then(() => {
327-
refreshWorkspaceContent(platformApi);
327+
refreshWorkspaceContent();
328328
});
329329
setContextMenuState({ x: 0, y: 0, isOpen: false });
330330
}}
@@ -344,6 +344,7 @@ const TreeViewNode = forwardRef(function TreeViewNode(
344344
viewFile={viewFile}
345345
folderUri={object.uri}
346346
platformApi={platformApi}
347+
refreshWorkspaceContent={refreshWorkspaceContent}
347348
/>
348349
</div>
349350
)}
@@ -356,11 +357,13 @@ function TreeViewNodeWrapper({
356357
viewFile,
357358
platformApi,
358359
parentGroupRef,
360+
refreshWorkspaceContent,
359361
}: {
360362
object: FileSystemObject;
361363
viewFile: (uri: string) => Promise<void>;
362364
platformApi?: AbstractPlatformAPI;
363365
parentGroupRef: RefObject<TreeViewGroupRef>;
366+
refreshWorkspaceContent: () => Promise<void>;
364367
}) {
365368
const nodeRef = useRef<TreeViewNodeRef | null>(null);
366369

@@ -371,6 +374,7 @@ function TreeViewNodeWrapper({
371374
viewFile={viewFile}
372375
platformApi={platformApi}
373376
parentGroupRef={parentGroupRef}
377+
refreshWorkspaceContent={refreshWorkspaceContent}
374378
/>
375379
);
376380
}
@@ -380,11 +384,13 @@ const TreeViewGroup = forwardRef(function TreeViewGroup(
380384
objects,
381385
viewFile,
382386
folderUri,
387+
refreshWorkspaceContent,
383388
platformApi,
384389
}: {
385390
objects: FileSystemObject[];
386391
viewFile: (uri: string) => Promise<void>;
387392
folderUri: string;
393+
refreshWorkspaceContent: () => Promise<void>;
388394
platformApi?: AbstractPlatformAPI;
389395
},
390396
ref: Ref<TreeViewGroupRef>,
@@ -413,8 +419,6 @@ const TreeViewGroup = forwardRef(function TreeViewGroup(
413419
},
414420
}));
415421

416-
const { refreshWorkspaceContent } = useWorkspace();
417-
418422
const [isCreatingNewFile, setIsCreatingNewFile] = useState(false);
419423
const [isCreatingNewFolder, setIsCreatingNewFolder] = useState(false);
420424

@@ -430,7 +434,7 @@ const TreeViewGroup = forwardRef(function TreeViewGroup(
430434
}
431435

432436
platformApi.createFolder(uri).then(() => {
433-
refreshWorkspaceContent(platformApi);
437+
refreshWorkspaceContent();
434438
});
435439

436440
setFolderNameInputValue("");
@@ -446,7 +450,7 @@ const TreeViewGroup = forwardRef(function TreeViewGroup(
446450
}
447451

448452
platformApi.createFile(uri).then(() => {
449-
refreshWorkspaceContent(platformApi);
453+
refreshWorkspaceContent();
450454
});
451455

452456
setFileNameInputValue("");
@@ -463,6 +467,7 @@ const TreeViewGroup = forwardRef(function TreeViewGroup(
463467
viewFile={viewFile}
464468
platformApi={platformApi}
465469
parentGroupRef={ref as RefObject<TreeViewGroupRef>}
470+
refreshWorkspaceContent={refreshWorkspaceContent}
466471
/>
467472
);
468473
})}
@@ -499,12 +504,24 @@ const TreeViewGroup = forwardRef(function TreeViewGroup(
499504
onValueChange={setFileNameInputValue}
500505
onKeyDown={(e) => {
501506
if (e.key === "Enter") {
507+
// Cancel on empty input
508+
if (fileNameInputValue === "") {
509+
setIsCreatingNewFile(false);
510+
return;
511+
}
512+
502513
const uri = folderUri + "/" + fileNameInputValue;
503514
createNewFile(uri);
504515
}
505516
}}
506517
onFocusChange={(isFocused) => {
507518
if (!isFocused) {
519+
// Cancel on empty input
520+
if (fileNameInputValue === "") {
521+
setIsCreatingNewFile(false);
522+
return;
523+
}
524+
508525
const uri = folderUri + "/" + fileNameInputValue;
509526
createNewFile(uri);
510527
}
Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"use client";
22

33
import { SideMenuTabEnum } from "@/lib/enums";
4+
import { useAuth } from "@/lib/hooks/use-auth";
45
import { usePlatformApi } from "@/lib/hooks/use-platform-api";
56
import { ProjectInfo } from "@/lib/types";
6-
import { Button } from "@heroui/react";
7+
import { Button, Spinner } from "@heroui/react";
78
import { useContext, useEffect, useState } from "react";
89
import ProjectSettingsModal from "../../modals/project-settings-modal";
910
import { EditorContext } from "../../providers/editor-context-provider";
@@ -12,12 +13,14 @@ import ProjectItem from "./project-item";
1213
export default function ProjectExplorer() {
1314
const editorContext = useContext(EditorContext);
1415

16+
const { session } = useAuth();
1517
const { platformApi } = usePlatformApi();
1618

1719
const [settingsOpen, setSettingsOpen] = useState(false);
1820
const [settingsProject, setSettingsProject] = useState<
1921
ProjectInfo | undefined
2022
>(undefined);
23+
const [isLoading, setIsLoading] = useState(false);
2124

2225
useEffect(() => {
2326
if (editorContext?.editorStates.project) {
@@ -26,52 +29,70 @@ export default function ProjectExplorer() {
2629
}, [editorContext?.editorStates.project]);
2730

2831
useEffect(() => {
29-
if (platformApi) {
32+
if (platformApi && session) {
3033
const homePath = editorContext?.persistSettings?.projectHomePath;
3134

35+
setIsLoading(true);
3236
platformApi.listProjects(homePath).then((projects) => {
3337
editorContext?.setEditorStates((prev) => {
3438
return {
3539
...prev,
3640
projectsInfo: projects,
3741
};
3842
});
43+
setIsLoading(false);
3944
});
4045
}
41-
}, [editorContext?.persistSettings, platformApi]);
46+
}, [editorContext?.persistSettings, platformApi, session]);
4247

4348
return (
44-
<div className="flex w-full flex-col gap-2">
45-
<div>
46-
<p className="text-center text-lg font-medium">View Projects</p>
47-
<Button
48-
className="w-full"
49-
onPress={() => {
50-
setSettingsOpen(true);
51-
}}
52-
>
53-
New Project
54-
</Button>
55-
{editorContext?.editorStates.projectsInfo?.map((project, index) => (
56-
<ProjectItem
57-
key={index}
58-
project={project}
59-
setSettingsOpen={setSettingsOpen}
60-
setSettingsProject={setSettingsProject}
61-
onOpen={() => {
62-
editorContext.setEditorStates((prev) => ({
63-
...prev,
64-
sideMenuTab: SideMenuTabEnum.Apps,
65-
}));
49+
<div className="flex h-full w-full flex-col gap-2">
50+
{session ? (
51+
<div>
52+
<p className="text-center text-lg font-medium">View Projects</p>
53+
<Button
54+
className="w-full"
55+
onPress={() => {
56+
setSettingsOpen(true);
6657
}}
58+
>
59+
New Project
60+
</Button>
61+
{isLoading &&
62+
(editorContext?.editorStates.projectsInfo ?? []).length === 0 && (
63+
<div className="flex justify-center">
64+
<Spinner />
65+
</div>
66+
)}
67+
{editorContext?.editorStates.projectsInfo?.map((project, index) => (
68+
<ProjectItem
69+
key={index}
70+
project={project}
71+
setSettingsOpen={setSettingsOpen}
72+
setSettingsProject={setSettingsProject}
73+
onOpen={() => {
74+
editorContext.setEditorStates((prev) => ({
75+
...prev,
76+
sideMenuTab: SideMenuTabEnum.Apps,
77+
}));
78+
}}
79+
/>
80+
))}
81+
<ProjectSettingsModal
82+
isOpen={settingsOpen}
83+
setIsOpen={setSettingsOpen}
84+
projectInfo={settingsProject}
6785
/>
68-
))}
69-
<ProjectSettingsModal
70-
isOpen={settingsOpen}
71-
setIsOpen={setSettingsOpen}
72-
projectInfo={settingsProject}
73-
/>
74-
</div>
86+
</div>
87+
) : (
88+
<div className="flex h-full w-full flex-col items-center justify-center pb-24">
89+
<p className="text-center text-lg font-medium">
90+
Sign in to view your projects,
91+
<br />
92+
or open a local project with desktop client.
93+
</p>
94+
</div>
95+
)}
7596
</div>
7697
);
7798
}

0 commit comments

Comments
 (0)