diff --git a/binocular-frontend-new/src/components/contextMenu/contextMenuHelper.ts b/binocular-frontend-new/src/components/contextMenu/contextMenuHelper.ts index e642c189..30aababf 100644 --- a/binocular-frontend-new/src/components/contextMenu/contextMenuHelper.ts +++ b/binocular-frontend-new/src/components/contextMenu/contextMenuHelper.ts @@ -19,7 +19,7 @@ export function showContextMenu(x: number, y: number, options: ContextMenuOption const optionLabel = document.createElement('span'); optionLabel.textContent = o.label; - const optionButton = document.createElement('a'); + const optionButton = document.createElement('span'); optionButton.addEventListener('click', o.function); optionButton.appendChild(optionIcon); optionButton.appendChild(optionLabel); diff --git a/binocular-frontend-new/src/components/dashboard/dashboardItem/dashboardItem.tsx b/binocular-frontend-new/src/components/dashboard/dashboardItem/dashboardItem.tsx index 87f516e4..ba07bfdf 100644 --- a/binocular-frontend-new/src/components/dashboard/dashboardItem/dashboardItem.tsx +++ b/binocular-frontend-new/src/components/dashboard/dashboardItem/dashboardItem.tsx @@ -42,6 +42,7 @@ function DashboardItem(props: { const [poppedOut, setPoppedOut] = useState(false); const authorLists = useSelector((state: RootState) => state.authors.authorLists); + const fileLists = useSelector((state: RootState) => state.files.fileLists); const sprintList = useSelector((state: RootState) => state.sprints.sprintList); const avaliableDataPlugins = useSelector((state: RootState) => state.settings.database.dataPlugins); @@ -89,7 +90,12 @@ function DashboardItem(props: { setAuthors(authorLists[props.item.dataPluginId]); } }, [authorLists, props.item.dataPluginId]); - + const [files, setFiles] = useState([]); + useEffect(() => { + if (props.item.dataPluginId !== undefined) { + setFiles(fileLists[props.item.dataPluginId]); + } + }, [fileLists, props.item.dataPluginId]); const [settings, setSettings] = useState(plugin.defaultSettings); /** @@ -152,6 +158,7 @@ function DashboardItem(props: { key={plugin.name} settings={settings} authorList={authors} + fileList={files} sprintList={sprintList} parameters={{ parametersGeneral: ignoreGlobalParameters ? parametersGeneralLocal : parametersGeneralGlobal, @@ -189,6 +196,7 @@ function DashboardItem(props: { key={plugin.name} settings={settings} authorList={authors} + fileList={files} sprintList={sprintList} parameters={{ parametersGeneral: ignoreGlobalParameters ? parametersGeneralLocal : parametersGeneralGlobal, diff --git a/binocular-frontend-new/src/components/overlayController/overlayController.tsx b/binocular-frontend-new/src/components/overlayController/overlayController.tsx index f670a997..eb14dc6e 100644 --- a/binocular-frontend-new/src/components/overlayController/overlayController.tsx +++ b/binocular-frontend-new/src/components/overlayController/overlayController.tsx @@ -4,7 +4,8 @@ import SettingsDialog from '../settingsDialog/settingsDialog.tsx'; import NotificationController from '../notificationController/notificationController.tsx'; import EditAuthorDialog from '../tabs/authors/editAuthorDialog/editAuthorDialog.tsx'; import ContextMenu from '../contextMenu/contextMenu.tsx'; -import LoadingLocalDatabaseOverlay from "./overlays/loadingLocalDatabaseOverlay/loadingLocalDatabaseOverlay.tsx"; +import LoadingLocalDatabaseOverlay from './overlays/loadingLocalDatabaseOverlay/loadingLocalDatabaseOverlay.tsx'; +import FileTreeElementInfoDialog from '../tabs/fileTree/fileTreeElementInfoDialog/fileTreeElementInfoDialog.tsx'; function OverlayController() { return ( @@ -14,6 +15,7 @@ function OverlayController() { + diff --git a/binocular-frontend-new/src/components/tabs/fileTree/fileList/fileListElements/fileListFile.tsx b/binocular-frontend-new/src/components/tabs/fileTree/fileList/fileListElements/fileListFile.tsx index df87fc4e..342dd69d 100644 --- a/binocular-frontend-new/src/components/tabs/fileTree/fileList/fileListElements/fileListFile.tsx +++ b/binocular-frontend-new/src/components/tabs/fileTree/fileList/fileListElements/fileListFile.tsx @@ -1,22 +1,48 @@ import fileListElementsStyles from './fileListElements.module.scss'; import { FileTreeElementType } from '../../../../../types/data/fileListType.ts'; import FileIcon from '../../../../../assets/file_gray.svg'; -import { updateFileListElement } from '../../../../../redux/reducer/data/filesReducer.ts'; +import { showFileTreeElementInfo, updateFileListElement } from '../../../../../redux/reducer/data/filesReducer.ts'; import { AppDispatch, useAppDispatch } from '../../../../../redux'; import { formatName } from '../fileListUtilities/fileTreeUtilities.tsx'; +import { showContextMenu } from '../../../../contextMenu/contextMenuHelper.ts'; +import infoIcon from '../../../../../assets/info_gray.svg'; -function FileListFile(props: { file: FileTreeElementType }) { +function FileListFile(props: { file: FileTreeElementType; listOnly?: boolean }) { const dispatch: AppDispatch = useAppDispatch(); + + function openFileContextMenu(e: React.MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + showContextMenu(e.clientX, e.clientY, [ + { + label: 'info', + icon: infoIcon, + function: () => dispatch(showFileTreeElementInfo(props.file)), + }, + ]); + } + return ( <>
- dispatch(updateFileListElement({ ...props.file, checked: e.target.checked }))} - /> -
+ {(props.listOnly === undefined || !props.listOnly) && ( + dispatch(updateFileListElement({ ...props.file, checked: e.target.checked }))} + /> + )} +
{ + if (props.listOnly === true) { + dispatch(showFileTreeElementInfo(props.file)); + } + }} + onContextMenu={(e) => { + openFileContextMenu(e); + }}> {`folder {formatName(props.file.searchTerm, props.file.name)}
diff --git a/binocular-frontend-new/src/components/tabs/fileTree/fileList/fileListElements/fileListFolder.tsx b/binocular-frontend-new/src/components/tabs/fileTree/fileList/fileListElements/fileListFolder.tsx index c85aa782..a8b6017b 100644 --- a/binocular-frontend-new/src/components/tabs/fileTree/fileList/fileListElements/fileListFolder.tsx +++ b/binocular-frontend-new/src/components/tabs/fileTree/fileList/fileListElements/fileListFolder.tsx @@ -4,18 +4,32 @@ import FolderIcon from '../../../../../assets/folder_gray.svg'; import FolderOpenIcon from '../../../../../assets/folder_open_gray.svg'; import FileListFile from './fileListFile.tsx'; import { AppDispatch, useAppDispatch } from '../../../../../redux'; -import { updateFileListElement } from '../../../../../redux/reducer/data/filesReducer.ts'; +import { showFileTreeElementInfo, updateFileListElement } from '../../../../../redux/reducer/data/filesReducer.ts'; import { formatName } from '../fileListUtilities/fileTreeUtilities.tsx'; +import { showContextMenu } from '../../../../contextMenu/contextMenuHelper.ts'; +import infoIcon from '../../../../../assets/info_gray.svg'; -function FileListFolder(props: { folder: FileTreeElementType; foldedOut: boolean }) { +function FileListFolder(props: { folder: FileTreeElementType; foldedOut: boolean; listOnly?: boolean }) { const dispatch: AppDispatch = useAppDispatch(); + function openFolderContextMenu(e: React.MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + showContextMenu(e.clientX, e.clientY, [ + { + label: 'info', + icon: infoIcon, + function: () => dispatch(showFileTreeElementInfo(props.folder)), + }, + ]); + } + return ( <> - {props.folder.foldedOut ? ( + {props.listOnly === true || props.folder.foldedOut ? ( <>
- {props.folder.id !== undefined && ( + {(props.listOnly === undefined || !props.listOnly) && props.folder.id !== undefined && ( dispatch(updateFileListElement({ ...props.folder, foldedOut: false }))}> + onClick={() => { + if (props.listOnly === true) { + dispatch(showFileTreeElementInfo(props.folder)); + } else { + dispatch(updateFileListElement({ ...props.folder, foldedOut: false })); + } + }} + onContextMenu={(e) => { + openFolderContextMenu(e); + }}> {`folder {formatName(props.folder.searchTerm, props.folder.name)}
@@ -46,9 +69,15 @@ function FileListFolder(props: { folder: FileTreeElementType; foldedOut: boolean .sort((e) => (e.type === FileTreeElementTypeType.Folder ? -1 : 1)) .map((element, i) => { if (element.type === FileTreeElementTypeType.Folder && element.children) { - return ; + return ( + + ); } else { - return ; + return ; } })}
@@ -63,6 +92,9 @@ function FileListFolder(props: { folder: FileTreeElementType; foldedOut: boolean />
dispatch(updateFileListElement({ ...props.folder, foldedOut: true }))} + onContextMenu={(e) => { + openFolderContextMenu(e); + }} className={fileListElementsStyles.element}> {`folder {formatName(props.folder.searchTerm, props.folder.name)} diff --git a/binocular-frontend-new/src/components/tabs/fileTree/fileTreeElementInfoDialog/fileTreeElementInfoDialog.tsx b/binocular-frontend-new/src/components/tabs/fileTree/fileTreeElementInfoDialog/fileTreeElementInfoDialog.tsx new file mode 100644 index 00000000..84cf8d94 --- /dev/null +++ b/binocular-frontend-new/src/components/tabs/fileTree/fileTreeElementInfoDialog/fileTreeElementInfoDialog.tsx @@ -0,0 +1,79 @@ +import { useSelector } from 'react-redux'; +import { RootState } from '../../../../redux'; +import { FileTreeElementType, FileTreeElementTypeType } from '../../../../types/data/fileListType.ts'; +import FileListFolder from '../fileList/fileListElements/fileListFolder.tsx'; +import { filterFileTree } from '../fileList/fileListUtilities/fileTreeUtilities.tsx'; +import FileSearch from '../fileSearch/fileSearch.tsx'; +import { useState } from 'react'; + +function FileTreeElementInfoDialog() { + const selectedFileTreeElement: FileTreeElementType | undefined = useSelector((state: RootState) => state.files.selectedFileTreeElement); + const [fileSearch, setFileSearch] = useState(''); + + return ( + +
+ {selectedFileTreeElement && ( + <> +

+ {selectedFileTreeElement.name} +

+

Type

+
{selectedFileTreeElement.type}
+ {selectedFileTreeElement.type === FileTreeElementTypeType.File && selectedFileTreeElement.element && ( + <> +

Path

+
{selectedFileTreeElement.element.path}
+

Max Length

+
{selectedFileTreeElement.element.maxLength}
+

Url

+ + + )} +

File Tree State

+
+ + {selectedFileTreeElement.foldedOut ? ( +
folded out
+ ) : ( +
folded in
+ )} +
+ + {selectedFileTreeElement.checked ? ( +
checked
+ ) : ( +
unchecked
+ )} +
+
+ + {selectedFileTreeElement.type === FileTreeElementTypeType.Folder && ( + <> +

Folder Content

+ + + + )} + + )} + +
+
+ +
+
+
+
+ +
+
+ ); +} + +export default FileTreeElementInfoDialog; diff --git a/binocular-frontend-new/src/index.scss b/binocular-frontend-new/src/index.scss index 7ab49525..a43ce90b 100644 --- a/binocular-frontend-new/src/index.scss +++ b/binocular-frontend-new/src/index.scss @@ -17,6 +17,10 @@ @apply text-lg; @apply underline; } + a { + @apply text-accent; + @apply underline; + } } @tailwind components; @tailwind utilities; diff --git a/binocular-frontend-new/src/plugins/interfaces/visualizationPlugin.ts b/binocular-frontend-new/src/plugins/interfaces/visualizationPlugin.ts index 4f927521..9dc9e532 100644 --- a/binocular-frontend-new/src/plugins/interfaces/visualizationPlugin.ts +++ b/binocular-frontend-new/src/plugins/interfaces/visualizationPlugin.ts @@ -4,6 +4,7 @@ import { AuthorType } from '../../types/data/authorType.ts'; import { SprintType } from '../../types/data/sprintType.ts'; import { Reducer, Store } from '@reduxjs/toolkit'; import { ParametersType } from '../../types/parameters/parametersType.ts'; +import { FileListElementType } from '../../types/data/fileListType.ts'; export interface VisualizationPlugin { name: string; @@ -15,6 +16,7 @@ export interface VisualizationPlugin { // Not every dataPlugin has all capabilities. // !! authorList: AuthorType[]; //list of Users set by Binocular + fileList: FileListElementType[]; //list of Users set by Binocular sprintList: SprintType[]; //list of Sprints set by Binocular parameters: ParametersType; // General Parameters Provided By Binocular chartContainerRef: RefObject; //forwarded ref that should reference the chart div diff --git a/binocular-frontend-new/src/redux/reducer/data/filesReducer.ts b/binocular-frontend-new/src/redux/reducer/data/filesReducer.ts index d43f94b1..2a53b074 100644 --- a/binocular-frontend-new/src/redux/reducer/data/filesReducer.ts +++ b/binocular-frontend-new/src/redux/reducer/data/filesReducer.ts @@ -7,6 +7,7 @@ export interface FilesInitialState { fileLists: { [id: number]: FileListElementType[] }; fileCounts: { [id: number]: number }; dataPluginId: number | undefined; + selectedFileTreeElement?: FileTreeElementType; } const initialState: FilesInitialState = { @@ -14,6 +15,7 @@ const initialState: FilesInitialState = { fileLists: {}, fileCounts: {}, dataPluginId: undefined, + selectedFileTreeElement: undefined, }; export const filesSlice = createSlice({ @@ -51,10 +53,14 @@ export const filesSlice = createSlice({ }); localStorage.setItem(`${filesSlice.name}StateV${Config.localStorageVersion}`, JSON.stringify(state)); }, + showFileTreeElementInfo: (state, action: PayloadAction) => { + (document.getElementById('fileTreeElementInfoDialog') as HTMLDialogElement).showModal(); + state.selectedFileTreeElement = action.payload; + }, }, }); -export const { setFilesDataPluginId, setFileList, updateFileListElement } = filesSlice.actions; +export const { setFilesDataPluginId, setFileList, updateFileListElement, showFileTreeElementInfo } = filesSlice.actions; export default filesSlice.reducer; function updateFileTreeRecursive(fileTree: FileTreeElementType, element: FileTreeElementType, checked?: boolean): string[] { diff --git a/binocular-frontend-new/src/types/data/fileListType.ts b/binocular-frontend-new/src/types/data/fileListType.ts index 592ed048..46c49142 100644 --- a/binocular-frontend-new/src/types/data/fileListType.ts +++ b/binocular-frontend-new/src/types/data/fileListType.ts @@ -17,6 +17,6 @@ export interface FileTreeElementType { } export enum FileTreeElementTypeType { - Folder, - File, + Folder = 'Folder', + File = 'File', }