Skip to content

Commit

Permalink
Add ability to reorder tabs
Browse files Browse the repository at this point in the history
* Add `Sortable` component

* Refactor explorer file events
  • Loading branch information
acheroncrypto committed Sep 20, 2023
1 parent 5fe5446 commit 3ce6f2b
Show file tree
Hide file tree
Showing 14 changed files with 333 additions and 110 deletions.
3 changes: 3 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"@codemirror/search": "^6.2.0",
"@codemirror/state": "^6.1.1",
"@codemirror/view": "^6.2.3",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^6.0.1",
"@dnd-kit/sortable": "^7.0.2",
"@floating-ui/react-dom": "^0.6.0",
"@isomorphic-git/lightning-fs": "^4.6.0",
"@metaplex-foundation/js": "^0.19.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ const CodeMirror = () => {
if (!editor) return;
let positionDataIntervalId: NodeJS.Timer;

const { dispose } = PgExplorer.onDidSwitchFile((curFile) => {
const { dispose } = PgExplorer.onDidOpenFile((curFile) => {
if (!curFile) return;

// Clear previous state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ const Monaco = () => {
if (!editor) return;
let positionDataIntervalId: NodeJS.Timer;

const switchFile = PgExplorer.onDidSwitchFile((curFile) => {
const switchFile = PgExplorer.onDidOpenFile((curFile) => {
if (!curFile) return;

// Clear previous state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const initLanguages = async (theme: RequiredKey<IRawTheme, "name">) => {
cache.languageIds.push(languageId);
};

return PgExplorer.onDidSwitchFile(async (file) => {
return PgExplorer.onDidOpenFile(async (file) => {
if (!file) return;

const lang = monaco.languages.getLanguages().find((lang) => {
Expand Down
199 changes: 109 additions & 90 deletions client/src/components/EditorWithTabs/Tabs/Tab.tsx
Original file line number Diff line number Diff line change
@@ -1,107 +1,125 @@
import { FC, MouseEvent, useCallback, useMemo, useRef, useState } from "react";
import {
ComponentPropsWithoutRef,
forwardRef,
MouseEvent,
useCallback,
useMemo,
useRef,
useState,
} from "react";
import styled, { css } from "styled-components";

import Button from "../../Button";
import LangIcon from "../../LangIcon";
import Menu from "../../Menu";
import { Close } from "../../Icons";
import { PgExplorer, PgTheme } from "../../../utils/pg";
import type { SortableItemProvidedProps } from "../../Sortable";

interface TabProps {
interface TabProps extends ComponentPropsWithoutRef<"div"> {
path: string;
index: number;
}

const Tab: FC<TabProps> = ({ path, index }) => {
const [isSelected, setIsSelected] = useState(false);

const [fileName, relativePath] = useMemo(
() => [
PgExplorer.getItemNameFromPath(path),
PgExplorer.getRelativePath(path),
],
[path]
);

const closeButtonRef = useRef<HTMLButtonElement>(null);
const changeTab = useCallback(
(ev: MouseEvent<HTMLDivElement>) => {
if (!closeButtonRef.current?.contains(ev.target as Node)) {
PgExplorer.openFile(path);
}
},
[path]
);

const closeFile = useCallback(() => PgExplorer.closeFile(path), [path]);

const closeOthers = useCallback(() => {
PgExplorer.tabs
.filter((tabPath) => tabPath !== path)
.forEach((tabPath) => PgExplorer.closeFile(tabPath));
}, [path]);

const closeToTheRight = useCallback(() => {
const tabIndex = PgExplorer.tabs.findIndex((tabPath) => tabPath === path);
PgExplorer.tabs
.slice(tabIndex + 1)
.forEach((tabPath) => PgExplorer.closeFile(tabPath));
}, [path]);

const closeAll = useCallback(() => {
PgExplorer.tabs.forEach((tabPath) => PgExplorer.closeFile(tabPath));
}, []);

return (
<Menu
kind="context"
items={[
{
name: "Close",
onClick: closeFile,
keybind: "ALT+W",
},
{
name: "Close Others",
onClick: closeOthers,
showCondition: PgExplorer.tabs.length > 1,
},
{
name: "Close To The Right",
onClick: closeToTheRight,
showCondition: PgExplorer.tabs.length - 1 > index,
},
{
name: "Close All",
onClick: closeAll,
},
]}
onShow={() => setIsSelected(true)}
onHide={() => setIsSelected(false)}
>
<Wrapper
isSelected={isSelected}
isCurrent={path === PgExplorer.currentFilePath}
onClick={changeTab}
title={relativePath}
const Tab = forwardRef<HTMLDivElement, TabProps>(
({ path, index, ...props }, ref) => {
const [isSelected, setIsSelected] = useState(false);

const [fileName, relativePath] = useMemo(
() => [
PgExplorer.getItemNameFromPath(path),
PgExplorer.getRelativePath(path),
],
[path]
);

const closeButtonRef = useRef<HTMLButtonElement>(null);
const changeTab = useCallback(
(ev: MouseEvent<HTMLDivElement>) => {
if (!closeButtonRef.current?.contains(ev.target as Node)) {
PgExplorer.openFile(path);
}
},
[path]
);

const closeFile = useCallback(() => PgExplorer.closeFile(path), [path]);

const closeOthers = useCallback(() => {
PgExplorer.tabs
.filter((tabPath) => tabPath !== path)
.forEach((tabPath) => PgExplorer.closeFile(tabPath));
}, [path]);

const closeToTheRight = useCallback(() => {
const tabIndex = PgExplorer.tabs.findIndex((tabPath) => tabPath === path);
PgExplorer.tabs
.slice(tabIndex + 1)
.forEach((tabPath) => PgExplorer.closeFile(tabPath));
}, [path]);

const closeAll = useCallback(() => {
PgExplorer.tabs.forEach((tabPath) => PgExplorer.closeFile(tabPath));
}, []);

return (
<Menu
kind="context"
items={[
{
name: "Close",
onClick: closeFile,
keybind: "ALT+W",
},
{
name: "Close Others",
onClick: closeOthers,
showCondition: PgExplorer.tabs.length > 1,
},
{
name: "Close To The Right",
onClick: closeToTheRight,
showCondition: PgExplorer.tabs.length - 1 > index,
},
{
name: "Close All",
onClick: closeAll,
},
]}
onShow={() => setIsSelected(true)}
onHide={() => setIsSelected(false)}
>
<LangIcon fileName={fileName} />
<Name>{fileName}</Name>
<Button
ref={closeButtonRef}
kind="icon"
onClick={closeFile}
title="Close (Alt+W)"
<Wrapper
isSelected={isSelected}
isCurrent={path === PgExplorer.currentFilePath}
onClick={changeTab}
title={relativePath}
ref={ref}
{...(props as ComponentPropsWithoutRef<"div"> &
SortableItemProvidedProps)}
>
<Close />
</Button>
</Wrapper>
</Menu>
);
};

const Wrapper = styled.div<{ isSelected: boolean; isCurrent: boolean }>`
${({ theme, isSelected, isCurrent }) => css`
<LangIcon fileName={fileName} />
<Name>{fileName}</Name>
<Button
ref={closeButtonRef}
kind="icon"
onClick={closeFile}
title="Close (Alt+W)"
>
<Close />
</Button>
</Wrapper>
</Menu>
);
}
);

const Wrapper = styled.div<{
isSelected: boolean;
isCurrent: boolean;
isDragging: boolean;
}>`
${({ theme, isSelected, isCurrent, isDragging }) => css`
& button {
${!isCurrent && "opacity: 0;"}
margin: 0 0.25rem 0 0.5rem;
Expand All @@ -119,6 +137,7 @@ const Wrapper = styled.div<{ isSelected: boolean; isCurrent: boolean }>`
${PgTheme.convertToCSS(theme.components.tabs.tab.default)};
${isSelected && PgTheme.convertToCSS(theme.components.tabs.tab.selected)};
${isCurrent && PgTheme.convertToCSS(theme.components.tabs.tab.current)};
${isDragging && PgTheme.convertToCSS(theme.components.tabs.tab.drag)};
`}
`;

Expand Down
25 changes: 22 additions & 3 deletions client/src/components/EditorWithTabs/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { SetStateAction, useCallback } from "react";
import styled, { css } from "styled-components";

import Tab from "./Tab";
import Button from "../../Button";
import Img from "../../Img";
import { Sortable, SortableItem } from "../../Sortable";
import { Id } from "../../../constants";
import { PgExplorer, PgTheme, PgWallet } from "../../../utils/pg";
import { useExplorer, useKeybind, useWallet } from "../../../hooks";

export const Tabs = () => {
const { explorer } = useExplorer();

// Set tabs, only runs after reorder
const setTabs = useCallback((action: SetStateAction<readonly string[]>) => {
const newTabs =
typeof action === "function" ? action(PgExplorer.tabs) : action;
PgExplorer.setTabs(newTabs as string[]);
}, []);

// Close the current tab with keybind
useKeybind(
"Alt+W",
Expand All @@ -24,10 +33,20 @@ export const Tabs = () => {
return (
<Wrapper id={Id.TABS}>
<TabsWrapper>
{explorer.tabs.map((path, i) => (
<Tab key={path} path={path} index={i} />
))}
{/* @ts-ignore */}
<Sortable items={explorer.tabs} setItems={setTabs}>
{explorer.tabs.map((path, i) => (
<SortableItem
key={path}
Item={Tab}
id={path}
path={path}
index={i}
/>
))}
</Sortable>
</TabsWrapper>

<Wallet />
</Wrapper>
);
Expand Down
Loading

0 comments on commit 3ce6f2b

Please sign in to comment.