feat: add change indicator to workspace tabs (#1236)

This commit is contained in:
Alex Bäuerle 2024-04-20 06:23:07 -07:00 committed by GitHub
parent 6340cd9aed
commit 49ff317b50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 13 deletions

View File

@ -1,10 +1,14 @@
import { Tab, Tabs } from "@nextui-org/react";
import React, { useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { IoIosGlobe } from "react-icons/io";
import { VscCode } from "react-icons/vsc";
import { useSelector } from "react-redux";
import Calendar from "../assets/calendar";
import { I18nKey } from "../i18n/declaration";
import { initialState as initialBrowserState } from "../state/browserSlice";
import { initialState as initialCodeState } from "../state/codeSlice";
import { RootState } from "../store";
import { AllTabs, TabOption, TabType } from "../types/TabOption";
import Browser from "./Browser";
import CodeEditor from "./CodeEditor";
@ -12,7 +16,18 @@ import Planner from "./Planner";
function Workspace() {
const { t } = useTranslation();
const plan = useSelector((state: RootState) => state.plan.plan);
const code = useSelector((state: RootState) => state.code.code);
const screenshotSrc = useSelector(
(state: RootState) => state.browser.screenshotSrc,
);
const [activeTab, setActiveTab] = useState<TabType>(TabOption.CODE);
const [changes, setChanges] = useState<Record<TabType, boolean>>({
[TabOption.PLANNER]: false,
[TabOption.CODE]: false,
[TabOption.BROWSER]: false,
});
const tabData = useMemo(
() => ({
@ -35,6 +50,30 @@ function Workspace() {
[t],
);
useEffect(() => {
if (activeTab !== TabOption.PLANNER && plan.mainGoal !== undefined) {
setChanges((prev) => ({ ...prev, [TabOption.PLANNER]: true }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [plan]);
useEffect(() => {
if (activeTab !== TabOption.CODE && code !== initialCodeState.code) {
setChanges((prev) => ({ ...prev, [TabOption.CODE]: true }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [plan]);
useEffect(() => {
if (
activeTab !== TabOption.BROWSER &&
screenshotSrc !== initialBrowserState.screenshotSrc
) {
setChanges((prev) => ({ ...prev, [TabOption.BROWSER]: true }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [screenshotSrc]);
return (
<div className="flex flex-col min-h-0 grow">
<div
@ -52,6 +91,7 @@ function Workspace() {
}}
size="lg"
onSelectionChange={(v) => {
setChanges((prev) => ({ ...prev, [v as TabType]: false }));
setActiveTab(v as TabType);
}}
>
@ -63,6 +103,9 @@ function Workspace() {
<div className="flex grow items-center gap-2 justify-center text-xs">
{tabData[tab].icon}
<span>{tabData[tab].name}</span>
{changes[tab] && (
<div className="w-2 h-2 rounded-full animate-pulse bg-blue-500" />
)}
</div>
}
/>

View File

@ -1,14 +1,16 @@
import { createSlice } from "@reduxjs/toolkit";
export const initialState = {
// URL of browser window (placeholder for now, will be replaced with the actual URL later)
url: "https://github.com/OpenDevin/OpenDevin",
// Base64-encoded screenshot of browser window (placeholder for now, will be replaced with the actual screenshot later)
screenshotSrc:
"data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN0uGvyHwAFCAJS091fQwAAAABJRU5ErkJggg==",
};
export const browserSlice = createSlice({
name: "browser",
initialState: {
// URL of browser window (placeholder for now, will be replaced with the actual URL later)
url: "https://github.com/OpenDevin/OpenDevin",
// Base64-encoded screenshot of browser window (placeholder for now, will be replaced with the actual screenshot later)
screenshotSrc:
"data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN0uGvyHwAFCAJS091fQwAAAABJRU5ErkJggg==",
},
initialState,
reducers: {
setUrl: (state, action) => {
state.url = action.payload;

View File

@ -3,13 +3,15 @@ import { INode, flattenTree } from "react-accessible-treeview";
import { IFlatMetadata } from "react-accessible-treeview/dist/TreeView/utils";
import { WorkspaceFile } from "../services/fileService";
export const initialState = {
code: "# Welcome to OpenDevin!",
selectedIds: [] as number[],
workspaceFolder: { name: "" } as WorkspaceFile,
};
export const codeSlice = createSlice({
name: "code",
initialState: {
code: "# Welcome to OpenDevin!",
selectedIds: [] as number[],
workspaceFolder: { name: "" } as WorkspaceFile,
},
initialState,
reducers: {
setCode: (state, action) => {
state.code = action.payload;