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) => {
}, [ data, index ]);
return (
<Flex overflow="hidden" borderRadius="md" height="540px">
<Flex overflow="hidden" borderRadius="md" height="540px" position="relative">
<Box flexGrow={ 1 }>
<CodeEditorTabs tabs={ tabs } activeTab={ data[index].file_path } onTabSelect={ handleTabSelect } onTabClose={ handleTabClose }/>
<MonacoEditor
......
......@@ -29,9 +29,9 @@ const CodeEditorSideBar = ({ onFileSelect, data, monaco }: Props) => {
};
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">
<TabList columnGap={ 3 }>
<TabList columnGap={ 3 } position="sticky" top={ 0 } left={ 0 } bgColor="lightcoral" zIndex="sticky">
<Tab { ...tabProps }>Explorer</Tab>
<Tab { ...tabProps }>Search</Tab>
</TabList>
......
import { Flex, IconButton } from '@chakra-ui/react';
import { Flex, IconButton, chakra } from '@chakra-ui/react';
import React from 'react';
import iconCross from 'icons/cross.svg';
import getFileName from './utils/getFileName';
import getFilePathParts from './utils/getFilePathParts';
interface Props {
isActive?: boolean;
......@@ -11,10 +11,11 @@ interface Props {
onClick: (path: string) => void;
onClose: (path: string) => void;
isCloseDisabled: boolean;
tabsPathChunks: Array<Array<string>>;
}
const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled }: Props) => {
const name = getFileName(path);
const CodeEditorTab = ({ isActive, path, onClick, onClose, isCloseDisabled, tabsPathChunks }: Props) => {
const [ fileName, folderName ] = getFilePathParts(path, tabsPathChunks);
const handleClick = React.useCallback(() => {
onClick(path);
......@@ -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
as={ iconCross }
boxSize={ 5 }
......
......@@ -11,6 +11,10 @@ interface Props {
}
const CodeEditorTabs = ({ tabs, activeTab, onTabSelect, onTabClose }: Props) => {
const tabsPathChunks = React.useMemo(() => {
return tabs.map((tab) => tab.split('/'));
}, [ tabs ]);
return (
<Flex
bgColor="lightblue"
......@@ -26,6 +30,7 @@ const CodeEditorTabs = ({ tabs, activeTab, onTabSelect, onTabClose }: Props) =>
onClick={ onTabSelect }
onClose={ onTabClose }
isCloseDisabled={ tabs.length === 1 }
tabsPathChunks={ tabsPathChunks }
/>
)) }
</Flex>
......
......@@ -12,7 +12,7 @@ interface Props {
const CoderEditorCollapseButton = ({ onClick, label, isDisabled }: Props) => {
return (
<Tooltip label={ label } isDisabled={ isDisabled }>
<Flex position="absolute" right={ 3 } top={ 2 }>
<Flex position="absolute" right={ 3 } top={ 2 } zIndex="sticky">
<IconButton
as={ iconCopy }
boxSize={ 4 }
......
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>) {
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