diff --git a/src/client/src/appState.ts b/src/client/src/appState.ts index 4052f04..9882631 100644 --- a/src/client/src/appState.ts +++ b/src/client/src/appState.ts @@ -21,7 +21,7 @@ export interface AppState { actionPaletteOpen: boolean; projectDialogOpen: boolean; workspaceTool: QualifiedContributionId; - mainView: "chat" | QualifiedContributionId; + mainView: "navigation" | "chat" | QualifiedContributionId; fileTree: FileTreeEntry[]; expandedDirs: Record; selectedFilePath: string | undefined; diff --git a/src/client/src/components/PiWebApp.ts b/src/client/src/components/PiWebApp.ts index 036439b..79c9ec3 100644 --- a/src/client/src/components/PiWebApp.ts +++ b/src/client/src/components/PiWebApp.ts @@ -138,7 +138,7 @@ export class PiWebApp extends LitElement { workspaceId: this.state.selectedWorkspace?.id, sessionId: this.state.selectedSession?.id, tool: this.state.workspaceTool, - view: this.state.mainView, + view: this.state.mainView === "navigation" ? undefined : this.state.mainView, file: this.state.selectedFilePath, diff: this.state.selectedDiffPath, }); @@ -152,6 +152,12 @@ export class PiWebApp extends LitElement { } private selectMainView(view: AppState["mainView"]) { + if (view === "navigation") { + this.setState({ mainView: view }); + this.updateUrl(); + this.git.updatePolling(); + return; + } this.setState({ mainView: view, workspaceTool: view === "chat" ? this.state.workspaceTool : view }); this.updateUrl(); if (view !== "chat") this.refreshSelectedWorkspaceTool(view); @@ -178,8 +184,25 @@ export class PiWebApp extends LitElement { if (tool === "core:workspace.git") void this.git.refreshGit(); } - private renderWorkspacePanel() { - return html` { this.selectWorkspaceTool(tool); }} .onRefreshFiles=${() => this.files.refreshFiles()} .onExpandDir=${(path: string) => this.files.expandDir(path)} .onSelectFile=${(path: string) => this.files.selectFile(path)} .onRefreshGit=${() => this.git.refreshGit()} .onSelectDiff=${(path: string) => this.git.selectDiff(path)}>`; + private renderWorkspacePanel(hideToolTabs = false) { + return html` { this.selectWorkspaceTool(tool); }} .onRefreshFiles=${() => this.files.refreshFiles()} .onExpandDir=${(path: string) => this.files.expandDir(path)} .onSelectFile=${(path: string) => this.files.selectFile(path)} .onRefreshGit=${() => this.git.refreshGit()} .onSelectDiff=${(path: string) => this.git.selectDiff(path)}>`; + } + + private renderNavigationPanel(autoSwitchToChat: boolean) { + const openChatAfter = (action: () => Promise) => this.withChatScrollTransition(async () => { + await action(); + if (autoSwitchToChat) this.setState({ mainView: "chat" }); + if (autoSwitchToChat) this.updateUrl(); + }); + return html` +
+ Pi Web POC + +
+ this.withChatScrollTransition(() => this.workspaces.selectProject(project))} .onClose=${(project: Project) => this.projects.closeProject(project.id)}> + openChatAfter(() => this.workspaces.selectWorkspace(workspace))}> + openChatAfter(() => this.sessions.startSession())} .onSelect=${(session: SessionInfo) => openChatAfter(() => this.sessions.selectSession(session))} .onArchive=${(session: SessionInfo) => this.sessions.archiveSession(session)} .onRestore=${(session: SessionInfo) => openChatAfter(() => this.sessions.restoreSession(session))}> + `; } private visibleWorkspacePanels(): QualifiedWorkspacePanelContribution[] { @@ -216,30 +239,24 @@ export class PiWebApp extends LitElement { const state = this.state; return html`
- -
+ +
+ ${this.visibleWorkspacePanels().map((panel) => html` `)}
${state.error ? html`
${state.error}
` : null} +
${this.renderNavigationPanel(true)}
${state.selectedSession ? html` 0} .loadingMore=${state.isLoadingEarlierMessages} .isCompacting=${state.status?.isCompacting === true} .pendingMessageCount=${state.status?.pendingMessageCount ?? 0} .status=${state.status} .activity=${state.activity} .onLoadMore=${() => this.withChatPrependTransition(() => this.sessions.loadEarlierMessages())}> this.sessions.send(text, streamingBehavior)} .onStop=${() => this.sessions.stopActiveWork()}> ${state.commandDialog !== undefined ? html` this.sessions.respondToCommand(state.commandDialog?.requestId ?? "", value)} .onCancel=${() => { this.sessions.cancelCommand(); }}>` : null} ` : html`
Select or start a session.
`} -
${this.renderWorkspacePanel()}
+
${this.renderWorkspacePanel(true)}
${this.renderWorkspacePanel()} ${state.actionPaletteOpen ? html` { this.setState({ actionPaletteOpen: false }); this.runAction(actionId); }} .onCancel=${() => { this.setState({ actionPaletteOpen: false }); }}>` : null} diff --git a/src/client/src/components/WorkspacePanel.ts b/src/client/src/components/WorkspacePanel.ts index 751ba02..4ac90f0 100644 --- a/src/client/src/components/WorkspacePanel.ts +++ b/src/client/src/components/WorkspacePanel.ts @@ -9,6 +9,7 @@ export class WorkspacePanel extends LitElement { @property({ attribute: false }) workspace: Workspace | undefined; @property() tool: QualifiedContributionId = "core:workspace.files"; @property({ attribute: false }) panels: QualifiedWorkspacePanelContribution[] = []; + @property({ type: Boolean }) hideToolTabs = false; @property({ attribute: false }) fileTree: FileTreeEntry[] = []; @property({ attribute: false }) expandedDirs: Record = {}; @property({ attribute: false }) selectedFilePath: string | undefined; @@ -33,11 +34,13 @@ export class WorkspacePanel extends LitElement { const selectedPanel = visiblePanels.find((panel) => panel.id === this.tool) ?? visiblePanels[0]; return html`
-
- ${visiblePanels.map((panel) => html` - - `)} -
+ ${this.hideToolTabs ? null : html` +
+ ${visiblePanels.map((panel) => html` + + `)} +
+ `} ${workspace.label}
${selectedPanel === undefined ? html`
No workspace panels registered.
` : selectedPanel.render(this.createPanelContext(workspace))} diff --git a/src/client/src/components/shared.ts b/src/client/src/components/shared.ts index b7e04e0..f24d491 100644 --- a/src/client/src/components/shared.ts +++ b/src/client/src/components/shared.ts @@ -32,8 +32,8 @@ export const appStyles = css` project-list, workspace-list { flex: 0 0 auto; max-height: 26%; overflow: auto; border-bottom: 1px solid #21262d; } session-list { flex: 1 1 auto; min-height: 0; overflow: auto; } main { display: flex; flex-direction: column; min-width: 0; min-height: 0; } - .mobile-tabs { display: none; flex: 0 0 auto; gap: 6px; padding: 8px; border-bottom: 1px solid #30363d; } - .mobile-panel { display: none; } + .mobile-tabs { display: none; flex: 0 0 auto; gap: 6px; padding: 8px; border-bottom: 1px solid #30363d; overflow-x: auto; } + .mobile-navigation-tab, .mobile-navigation-panel, .mobile-panel { display: none; } .mobile-tabs button.selected { border-color: #58a6ff; background: #0d2847; } workspace-panel { min-width: 0; min-height: 0; border-left: 1px solid #30363d; overflow: hidden; } @media (max-width: 1180px) { @@ -42,10 +42,18 @@ export const appStyles = css` .mobile-tabs { display: flex; } main.workspace-view chat-view, main.workspace-view prompt-editor, main.workspace-view status-bar, main.workspace-view .empty { display: none; } - main.chat-view .mobile-panel { display: none; } + main.chat-view .mobile-panel, main.navigation-view .mobile-panel { display: none; } .mobile-panel { flex: 1 1 auto; min-height: 0; display: flex; } .mobile-panel workspace-panel { flex: 1 1 auto; border-left: 0; } } + @media (max-width: 760px) { + .shell { grid-template-columns: minmax(0, 1fr); } + aside { display: none; } + .mobile-navigation-tab { display: block; } + main.navigation-view chat-view, main.navigation-view prompt-editor, main.navigation-view status-bar, + main.navigation-view .empty { display: none; } + main.navigation-view .mobile-navigation-panel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; } + } status-bar { flex: 0 0 auto; } chat-view { flex: 1 1 auto; min-height: 0; overflow: hidden; } prompt-editor, chat-composer { flex: 0 0 auto; }