Commit 84fb8506 authored by tom's avatar tom

unique file names in tabs and sticky bar

parent f3329841
const stripLeadingSlash = (str: string) => str[0] === '/' ? str.slice(1) : str;
export default stripLeadingSlash;
...@@ -81,7 +81,7 @@ const CodeEditor = ({ data }: Props) => { ...@@ -81,7 +81,7 @@ const CodeEditor = ({ data }: Props) => {
}, [ data, index ]); }, [ data, index ]);
return ( return (
<Flex overflow="hidden" borderRadius="md" height="540px"> <Flex overflow="hidden" borderRadius="md" height="540px" position="relative">
<Box flexGrow={ 1 }> <Box flexGrow={ 1 }>
<CodeEditorTabs tabs={ tabs } activeTab={ data[index].file_path } onTabSelect={ handleTabSelect } onTabClose={ handleTabClose }/> <CodeEditorTabs tabs={ tabs } activeTab={ data[index].file_path } onTabSelect={ handleTabSelect } onTabClose={ handleTabClose }/>
<MonacoEditor <MonacoEditor
......
...@@ -29,9 +29,9 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco }: Props) => { ...@@ -29,9 +29,9 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco }: Props) => {
}; };
return ( return (
<Box w="250px" flexShrink={ 0 } bgColor="lightpink" fontSize="sm" overflowY="scroll" px={ 3 } position="relative"> <Box w="250px" flexShrink={ 0 } bgColor="lightpink" fontSize="sm" overflowY="scroll" px={ 3 }>
<Tabs isLazy lazyBehavior="keepMounted" variant="unstyled" size="sm"> <Tabs isLazy lazyBehavior="keepMounted" variant="unstyled" size="sm">
<TabList columnGap={ 3 }> <TabList columnGap={ 3 } position="sticky" top={ 0 } left={ 0 } bgColor="lightcoral" zIndex="sticky">
<Tab { ...tabProps }>Explorer</Tab> <Tab { ...tabProps }>Explorer</Tab>
<Tab { ...tabProps }>Search</Tab> <Tab { ...tabProps }>Search</Tab>
</TabList> </TabList>
......
import { Flex, IconButton } from '@chakra-ui/react'; import { Flex, IconButton, chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import iconCross from 'icons/cross.svg'; import iconCross from 'icons/cross.svg';
import getFileName from './utils/getFileName'; import getFilePathParts from './utils/getFilePathParts';
interface Props { interface Props {
isActive?: boolean; isActive?: boolean;
...@@ -11,10 +11,11 @@ interface Props { ...@@ -11,10 +11,11 @@ interface Props {
onClick: (path: string) => void; onClick: (path: string) => void;
onClose: (path: string) => void; onClose: (path: string) => void;
isCloseDisabled: boolean; isCloseDisabled: boolean;
tabsPathChunks: Array<Array<string>>;
} }
const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled }: Props) => { const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled, tabsPathChunks }: Props) => {
const name = getFileName(path); const [ fileName, folderName ] = getFilePathParts(path, tabsPathChunks);
const handleClick = React.useCallback(() => { const handleClick = React.useCallback(() => {
onClick(path); onClick(path);
...@@ -49,7 +50,8 @@ const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled }: Pr ...@@ -49,7 +50,8 @@ const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled }: Pr
}, },
}} }}
> >
<span>{ name }</span> <span>{ fileName }</span>
{ folderName && <chakra.span fontSize="xs" color="text_secondary" ml={ 1 }>{ folderName[0] === '.' ? '' : '...' }{ folderName }</chakra.span> }
<IconButton <IconButton
as={ iconCross } as={ iconCross }
boxSize={ 5 } boxSize={ 5 }
......
...@@ -11,6 +11,10 @@ interface Props { ...@@ -11,6 +11,10 @@ interface Props {
} }
const CodeEditorTabs = ({ tabs, activeTab, onTabSelect, onTabClose }: Props) => { const CodeEditorTabs = ({ tabs, activeTab, onTabSelect, onTabClose }: Props) => {
const tabsPathChunks = React.useMemo(() => {
return tabs.map((tab) => tab.split('/'));
}, [ tabs ]);
return ( return (
<Flex <Flex
bgColor="lightblue" bgColor="lightblue"
...@@ -26,6 +30,7 @@ const CodeEditorTabs = ({ tabs, activeTab, onTabSelect, onTabClose }: Props) => ...@@ -26,6 +30,7 @@ const CodeEditorTabs = ({ tabs, activeTab, onTabSelect, onTabClose }: Props) =>
onClick={ onTabSelect } onClick={ onTabSelect }
onClose={ onTabClose } onClose={ onTabClose }
isCloseDisabled={ tabs.length === 1 } isCloseDisabled={ tabs.length === 1 }
tabsPathChunks={ tabsPathChunks }
/> />
)) } )) }
</Flex> </Flex>
......
...@@ -12,7 +12,7 @@ interface Props { ...@@ -12,7 +12,7 @@ interface Props {
const CoderEditorCollapseButton = ({ onClick, label, isDisabled }: Props) => { const CoderEditorCollapseButton = ({ onClick, label, isDisabled }: Props) => {
return ( return (
<Tooltip label={ label } isDisabled={ isDisabled }> <Tooltip label={ label } isDisabled={ isDisabled }>
<Flex position="absolute" right={ 3 } top={ 2 }> <Flex position="absolute" right={ 3 } top={ 2 } zIndex="sticky">
<IconButton <IconButton
as={ iconCopy } as={ iconCopy }
boxSize={ 4 } boxSize={ 4 }
......
import type { File, FileTree } from '../types'; import type { File, FileTree } from '../types';
import sortFileTree from './sortFileTree'; import stripLeadingSlash from 'lib/stripLeadingSlash';
const stripLeadingSlash = (str: string) => str[0] === '/' ? str.slice(1) : str; import sortFileTree from './sortFileTree';
export default function composeFileTree(files: Array<File>) { export default function composeFileTree(files: Array<File>) {
const result: FileTree = []; const result: FileTree = [];
......
import getFilePathParts from './getFilePathParts';
it('computes correct chunks if all file name are unique', () => {
const result = getFilePathParts(
'/src/utils/Ownable.sol',
[
'/src/utils/EIP712.sol'.split('/'),
'/src/token/BaseERC20.sol'.split('/'),
],
);
expect(result).toEqual([ 'Ownable.sol', undefined ]);
});
it('computes correct chunks if files with the same name is not in folders with the same name', () => {
const result = getFilePathParts(
'/src/utils/Ownable.sol',
[
'/src/utils/EIP712.sol'.split('/'),
'/lib/_openzeppelin/contracts/contracts/access/Ownable.sol'.split('/'),
],
);
expect(result).toEqual([ 'Ownable.sol', '/utils' ]);
});
it('computes correct chunks if files with the same name is in folders with the same name', () => {
const result = getFilePathParts(
'/src/utils/Ownable.sol',
[
'/src/utils/EIP712.sol'.split('/'),
'/lib/_openzeppelin/contracts/src/utils/Ownable.sol'.split('/'),
],
);
expect(result).toEqual([ 'Ownable.sol', './src/utils' ]);
});
it('computes correct chunks if file is in root directory', () => {
const result = getFilePathParts(
'/Ownable.sol',
[
'/src/utils/EIP712.sol'.split('/'),
'/lib/_openzeppelin/contracts/src/utils/Ownable.sol'.split('/'),
],
);
expect(result).toEqual([ 'Ownable.sol', './' ]);
});
export default function getFilePathParts(path: string, tabsPathChunks: Array<Array<string>>) {
const chunks = path.split('/');
const fileName = chunks[chunks.length - 1];
const folderName = getFolderName(chunks, tabsPathChunks);
return [ fileName, folderName ];
}
function getFolderName(chunks: Array<string>, tabsPathChunks: Array<Array<string>>): string | undefined {
const fileName = chunks[chunks.length - 1];
const otherTabsPathChunks = tabsPathChunks.filter((item) => item.join('/') !== chunks.join('/'));
const tabsWithSameFileName = otherTabsPathChunks.filter((tabChunks) => tabChunks[tabChunks.length - 1] === fileName);
if (tabsWithSameFileName.length === 0 || chunks.length <= 1) {
return;
}
if (chunks.length === 2) {
return './' + chunks[chunks.length - 2];
}
let result = '/' + chunks[chunks.length - 2];
for (let index = 3; index <= chunks.length; index++) {
const element = chunks[chunks.length - index];
if (element === '') {
result = '.' + result;
}
const subFolderNames = tabsWithSameFileName.map((tab) => tab[tab.length - index]);
if (subFolderNames.includes(element)) {
result = '/' + element + result;
} else {
break;
}
}
return result;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment