diff --git a/.vscode/settings.json b/.vscode/settings.json index 83b13217..a0bc55e0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,7 +12,8 @@ "typescript.tsc.autoDetect": "off", "cmake.configureOnOpen": true, "cSpell.words": [ + "datas", "hoverable", "treesitter" ] -} \ No newline at end of file +} diff --git a/gui-sidebar/src/App.tsx b/gui-sidebar/src/App.tsx index 0bc0034c..57eeb94c 100644 --- a/gui-sidebar/src/App.tsx +++ b/gui-sidebar/src/App.tsx @@ -18,6 +18,8 @@ import useSubmenuContextProviders from "./hooks/useSubmenuContextProviders"; import { useVscTheme } from "./hooks/useVscTheme"; import Stats from "./pages/stats"; +import CodeContextPanel from "./pages/codeContextPanel"; + const router = createMemoryRouter([ { path: "/", @@ -35,6 +37,10 @@ const router = createMemoryRouter([ { path: "/history", element: , + }, + { + path: "/CodeContextPanel", + element: , }, { path: "/stats", diff --git a/gui-sidebar/src/components/Layout.tsx b/gui-sidebar/src/components/Layout.tsx index 7e136df4..40935e03 100644 --- a/gui-sidebar/src/components/Layout.tsx +++ b/gui-sidebar/src/components/Layout.tsx @@ -171,6 +171,19 @@ const Layout = () => { }, [location, navigate], ); + useWebviewListener( + "viewDataStorage", + async () => { + // Toggle the history page / main page + if (location.pathname === "/CodeContextPanel") { + navigate("/"); + } else { + navigate("/CodeContextPanel"); + } + }, + [location, navigate], + ); + useWebviewListener("indexProgress", async (data) => { setIndexingProgress(data.progress); diff --git a/gui-sidebar/src/hooks/useNavigationListener.tsx b/gui-sidebar/src/hooks/useNavigationListener.tsx index 0c4ef547..b95af7d9 100644 --- a/gui-sidebar/src/hooks/useNavigationListener.tsx +++ b/gui-sidebar/src/hooks/useNavigationListener.tsx @@ -47,4 +47,17 @@ export const useNavigationListener = () => { }, [location, navigate] ); + useWebviewListener( + "viewDataStorage", + async () => { + // Toggle the history page / main page + if (location.pathname === "/viewDataStorage") { + navigate("/"); + } else { + navigate("/codeContextPanel"); + } + }, + [location, navigate] + ); + }; diff --git a/gui-sidebar/src/pages/codeContextPanel.tsx b/gui-sidebar/src/pages/codeContextPanel.tsx new file mode 100644 index 00000000..134c2d03 --- /dev/null +++ b/gui-sidebar/src/pages/codeContextPanel.tsx @@ -0,0 +1,634 @@ +import React, { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import { ideRequest } from '../util/ide'; +import { useWebviewListener } from '../hooks/useWebviewListener'; + +interface CodeSample { + id: string; + filePath: string; + codeContext: string; + code: string; + doc: string; +} + +interface CodeContext { + id: string; + filePath: string; + codeContext: string; + code: string; + doc: string; +} + +interface Group { + name: string; + itemMap: Map; +} + +const Container = styled.div` + font-family: Arial, sans-serif; + margin: 20px; + background-color: #000; + color: #fff; +`; + +const Tabs = styled.div` + display: flex; + margin-bottom: 20px; +`; + +const Tab = styled.div<{ active: boolean }>` + padding: 10px 20px; + cursor: pointer; + border: 1px solid #ddd; + background-color: ${({ active }) => (active ? '#555' : '#333')}; + color: #fff; + margin-right: 10px; +`; + +const TabContent = styled.div<{ active: boolean }>` + display: ${({ active }) => (active ? 'block' : 'none')}; + padding: 20px; + border: 1px solid #555; + background-color: #222; + color: #fff; +`; + +const FormContainer = styled.div` + margin-top: 20px; + padding: 20px; + border: 1px solid #555; + background-color: #333; + color: #fff; +`; + +const Input = styled.input` + width: 100%; + padding: 10px; + margin-bottom: 10px; + box-sizing: border-box; + background-color: #444; + color: #fff; + border: 1px solid #555; +`; + +const TextArea = styled.textarea` + width: 100%; + padding: 10px; + margin-bottom: 10px; + box-sizing: border-box; + background-color: #444; + color: #fff; + border: 1px solid #555; +`; + +const Button = styled.button` + padding: 10px 20px; + cursor: pointer; + background-color: #555; + color: #fff; + border: 1px solid #777; +`; + +const CodeContent = styled.div` + white-space: pre; + font-family: monospace; + max-height: 200px; + overflow-y: auto; + border: 1px solid #555; + padding: 10px; + box-sizing: border-box; + background-color: #444; + color: #fff; +`; + +const CodeSample = styled.div` + margin-top: 20px; + padding: 20px; + border: 1px solid #555; + background-color: #333; + color: #fff; +`; + +const Actions = styled.div` + margin-top: 10px; + display: flex; + gap: 10px; +`; + +const Radio = styled.input.attrs({ type: 'radio' })` + margin-right: 10px; +`; + +const GroupNameModal = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; +`; + +const GroupNameContent = styled.div` + background-color: #333; + padding: 20px; + border-radius: 5px; + width: 300px; +`; + +const GroupItem = styled.div` + margin-top: 20px; + padding: 20px; + border: 1px solid #555; + background-color: #333; + color: #fff; +`; + +const CodeContextPanel: React.FC = () => { + const [activeTab, setActiveTab] = useState<'CodeSample' | 'FrameworkCodeFragment' | 'Groups'>('CodeSample'); + const [codeSamples, setCodeSamples] = useState([]); + const [codeContexts, setCodeContexts] = useState([]); + const [groups, setGroups] = useState([]); + const [formData, setFormData] = useState({ + id: '', + filePath: '', + code: '', + doc: '', + codeContext: '', + }); + const [formTitle, setFormTitle] = useState('添加代码样例'); + const [editIndex, setEditIndex] = useState(null); + const [originalItem, setOriginalItem] = useState(null); + const [selectedItems, setSelectedItems] = useState>(new Map()); + const [showGroupNameModal, setShowGroupNameModal] = useState(false); + const [groupName, setGroupName] = useState(''); + const [isDataLoaded, setIsDataLoaded] = useState(false); + const [selectedGroup, setSelectedGroup] = useState(null); // 添加 selectedGroup 状态 + + + useEffect(() => { + ideRequest("WorkspaceService.GetDataStorage", "CodeSample"); + ideRequest("WorkspaceService.GetDataStorage", "FrameworkCodeFragment"); + ideRequest("WorkspaceService.Groups.GetGroups", ""); + }, []); + + useWebviewListener("WorkspaceService_GetDataStorage", async (data) => { + switch (data.key) { + case "CodeSample": + if (data.storages) { + let temp = JSON.parse(data.storages) as CodeSample[]; + setCodeSamples(temp); + } + break; + case "FrameworkCodeFragment": + if (data.storages) { + let temp2 = JSON.parse(data.storages) as CodeContext[]; + setCodeContexts(temp2); + } + break; + default: + break; + } + setIsDataLoaded(true); + }); + + useWebviewListener("WorkspaceService_Groups_GetGroups", async (data) => { + if (data.groups) { + const parsedGroups = jsonToGroups(data.groups); + setGroups(parsedGroups); + ideRequest("WorkspaceService.Groups.GetSelectedGroupName",""); + } + }); + useWebviewListener("WorkspaceService_Groups_GetSelectedGroupName", async (data) => { + if (data.groupName) { + setSelectedGroup(data.groupName); + } + }); + + useWebviewListener("WorkspaceService_AddDataStorage", async (data) => { + refreshItems(); + }); + + const refreshItems = () => { + ideRequest("WorkspaceService.GetDataStorage", "CodeSample"); + ideRequest("WorkspaceService.GetDataStorage", "FrameworkCodeFragment"); + }; + + const handleTabChange = (tabName: 'CodeSample' | 'FrameworkCodeFragment' | 'Groups') => { + const currentSelectedItems = selectedItems.get(activeTab) || []; + setSelectedItems(new Map(selectedItems.set(activeTab, currentSelectedItems))); + + setActiveTab(tabName); + setFormTitle(tabName === 'CodeSample' ? '添加代码样例' : tabName === 'FrameworkCodeFragment' ? '添加代码上下文' : '管理编组'); + clearForm(); + + if (tabName === 'Groups' && !isDataLoaded) { + refreshItems(); + } + }; + + const handleInputChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setFormData({ ...formData, [name]: value }); + }; + + const handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + if (editIndex === null) { + const newItem = { + id: Date.now().toString(), + filePath: formData.filePath, + code: formData.code, + doc: formData.doc, + codeContext: formData.codeContext, + }; + + if (activeTab === 'CodeSample') { + setCodeSamples([...codeSamples, newItem as CodeSample]); + } else { + setCodeContexts([...codeContexts, newItem as CodeContext]); + } + let dataformat = { + key: activeTab, + originalItem: JSON.stringify(newItem), + } + ideRequest("WorkspaceService.AddDataStorage", dataformat); + } else { + const items = activeTab === 'CodeSample' ? codeSamples : codeContexts; + items[editIndex] = { + id: formData.id, + filePath: formData.filePath, + code: formData.code, + doc: formData.doc, + codeContext: formData.codeContext, + }; + + if (activeTab === 'CodeSample') { + setCodeSamples([...items]); + } else { + setCodeContexts([...items]); + } + + let originalItemJson = JSON.stringify(originalItem); + let formDataJson = JSON.stringify(formData); + let dataformat = { + key: activeTab, + originalItem: originalItemJson, + newItem: formDataJson + } + ideRequest("WorkspaceService.ChangeDataStorage", dataformat); + } + + clearForm(); + }; + + const editItem = (index: number) => { + const items = activeTab === 'CodeSample' ? codeSamples : codeContexts; + const item = items[index]; + setFormData({ + id: item.id, + filePath: item.filePath, + code: item.code, + doc: item.doc, + codeContext: formData.codeContext, + }); + setOriginalItem({ ...item }); + setEditIndex(index); + setFormTitle(activeTab === 'CodeSample' ? '编辑样例说明' : '编辑上下文说明'); + }; + + const deleteItem = (index: number) => { + if (activeTab === 'CodeSample') { + let dataformat = { + key: activeTab, + originalItem: JSON.stringify(codeSamples[index]), + } + ideRequest("WorkspaceService.RemoveDataStorage", dataformat); + setCodeSamples(codeSamples.filter((_, i) => i !== index)); + } else { + let dataformat = { + key: activeTab, + originalItem: JSON.stringify(codeContexts[index]), + } + ideRequest("WorkspaceService.RemoveDataStorage", dataformat); + setCodeContexts(codeContexts.filter((_, i) => i !== index)); + } + }; + + const clearForm = () => { + setFormData({ + id: '', + filePath: '', + code: '', + doc: '', + codeContext: '', + }); + setOriginalItem(null); + setEditIndex(null); + setFormTitle(activeTab === 'CodeSample' ? '添加代码样例' : '添加代码上下文'); + }; + + const handleCheckboxChange = (id: string) => { + const type = activeTab === 'CodeSample' ? 'CodeSample' : 'FrameworkCodeFragment'; + const currentSelectedItems = selectedItems.get(type) || []; + + if (currentSelectedItems.includes(id)) { + setSelectedItems(new Map(selectedItems.set(type, currentSelectedItems.filter(i => i !== id)))); + } else { + setSelectedItems(new Map(selectedItems.set(type, [...currentSelectedItems, id]))); + } + }; + + const handleGroupItems = () => { + setShowGroupNameModal(true); + }; + + const handleGroupNameChange = (event: React.ChangeEvent) => { + setGroupName(event.target.value); + }; + + const handleGroupNameSubmit = () => { + const newItemMap = new Map(selectedItems); + const newGroup: Group = { + name: groupName, + itemMap: newItemMap, + }; + setGroups([...groups, newGroup]); + + ideRequest("WorkspaceService.Groups.AddGroup", { data: GroupToJson(newGroup) }); + + setShowGroupNameModal(false); + setSelectedItems(new Map()); + setGroupName(''); + }; + + const deleteGroup = (index: number) => { + const groupToDelete = groups[index]; + setGroups(groups.filter((_, i) => i !== index)); + ideRequest("WorkspaceService.Groups.RemoveGroup", { group: JSON.stringify(groupToDelete) }); + }; + + const getItemById = (id: string) => { + const sample = codeSamples.find(sample => sample.id == id); + if (sample) { + return { type: 'CodeSample', item: sample }; + } + const context = codeContexts.find(context => context.id == id); + if (context) { + return { type: 'CodeContext', item: context }; + } + return null; + }; + + const handleGroupItemRadioChange = (groupIndex: number, itemId: string) => { + const group = groups[groupIndex]; + const itemMap = new Map(group.itemMap); + itemMap.forEach((ids, type) => { + itemMap.set(type, ids.filter(id => id === itemId)); + }); + const updatedGroup = { ...group, itemMap }; + const updatedGroups = [...groups]; + updatedGroups[groupIndex] = updatedGroup; + setGroups(updatedGroups); + }; + + return ( + +

代码样例管理

+ + + handleTabChange('CodeSample')}> + 代码样例 + + handleTabChange('FrameworkCodeFragment')}> + 代码上下文 + + handleTabChange('Groups')}> + 编组管理 + + + + + +

{formTitle}

+
+ +