281 lines
8.4 KiB
TypeScript
281 lines
8.4 KiB
TypeScript
import type { ProjectGraphNode } from '@nrwl/devkit';
|
|
import { useDepGraphService } from '../hooks/use-dep-graph';
|
|
import { useDepGraphSelector } from '../hooks/use-dep-graph-selector';
|
|
import {
|
|
allProjectsSelector,
|
|
selectedProjectNamesSelector,
|
|
workspaceLayoutSelector,
|
|
} from '../machines/selectors';
|
|
import { parseParentDirectoriesFromPilePath } from '../util';
|
|
|
|
function getProjectsByType(type: string, projects: ProjectGraphNode[]) {
|
|
return projects
|
|
.filter((project) => project.type === type)
|
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
interface SidebarProject {
|
|
projectGraphNode: ProjectGraphNode;
|
|
isSelected: boolean;
|
|
}
|
|
|
|
type DirectoryProjectRecord = Record<string, SidebarProject[]>;
|
|
|
|
function groupProjectsByDirectory(
|
|
projects: ProjectGraphNode[],
|
|
selectedProjects: string[],
|
|
workspaceLayout: { appsDir: string; libsDir: string }
|
|
): DirectoryProjectRecord {
|
|
let groups = {};
|
|
|
|
projects.forEach((project) => {
|
|
const workspaceRoot =
|
|
project.type === 'app' || project.type === 'e2e'
|
|
? workspaceLayout.appsDir
|
|
: workspaceLayout.libsDir;
|
|
const directories = parseParentDirectoriesFromPilePath(
|
|
project.data.root,
|
|
workspaceRoot
|
|
);
|
|
const directory = directories.join('/');
|
|
|
|
if (!groups.hasOwnProperty(directory)) {
|
|
groups[directory] = [];
|
|
}
|
|
groups[directory].push({
|
|
projectGraphNode: project,
|
|
isSelected: selectedProjects.includes(project.name),
|
|
});
|
|
});
|
|
|
|
return groups;
|
|
}
|
|
|
|
function ProjectListItem({
|
|
project,
|
|
toggleProject,
|
|
focusProject,
|
|
}: {
|
|
project: SidebarProject;
|
|
toggleProject: (projectId: string, currentlySelected: boolean) => void;
|
|
focusProject: (projectId: string) => void;
|
|
}) {
|
|
return (
|
|
<li className="text-xs text-gray-600 block cursor-default select-none relative py-1 pl-3 pr-9">
|
|
<div className="flex items-center">
|
|
<button
|
|
type="button"
|
|
className="flex rounded-md"
|
|
title="Focus on this library"
|
|
onClick={() => focusProject(project.projectGraphNode.name)}
|
|
>
|
|
<span className="p-1 rounded-md flex items-center font-medium bg-white transition hover:bg-gray-50 shadow-sm ring-1 ring-gray-200">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="h-5 w-5"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2h-1.528A6 6 0 004 9.528V4z" />
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M8 10a4 4 0 00-3.446 6.032l-1.261 1.26a1 1 0 101.414 1.415l1.261-1.261A4 4 0 108 10zm-2 4a2 2 0 114 0 2 2 0 01-4 0z"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
</span>
|
|
</button>
|
|
<label
|
|
className="font-mono font-normal ml-3 p-2 transition hover:bg-gray-50 cursor-pointer block rounded-md truncate w-full"
|
|
data-project={project.projectGraphNode.name}
|
|
data-active={project.isSelected.toString()}
|
|
onClick={() =>
|
|
toggleProject(project.projectGraphNode.name, project.isSelected)
|
|
}
|
|
>
|
|
{project.projectGraphNode.name}
|
|
</label>
|
|
</div>
|
|
|
|
{project.isSelected ? (
|
|
<span
|
|
title="This library is visible"
|
|
className="text-green-nx-base absolute inset-y-0 right-0 flex items-center cursor-pointer"
|
|
onClick={() =>
|
|
toggleProject(project.projectGraphNode.name, project.isSelected)
|
|
}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
className="h-5 w-5"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2"
|
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
/>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth="2"
|
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
/>
|
|
</svg>
|
|
</span>
|
|
) : null}
|
|
</li>
|
|
);
|
|
}
|
|
|
|
function SubProjectList({
|
|
headerText,
|
|
projects,
|
|
selectProject,
|
|
deselectProject,
|
|
focusProject,
|
|
}: {
|
|
headerText: string;
|
|
projects: SidebarProject[];
|
|
selectProject: (projectName: string) => void;
|
|
deselectProject: (projectName: string) => void;
|
|
focusProject: (projectName: string) => void;
|
|
}) {
|
|
let sortedProjects = [...projects];
|
|
sortedProjects.sort((a, b) => {
|
|
return a.projectGraphNode.name.localeCompare(b.projectGraphNode.name);
|
|
});
|
|
|
|
function toggleProject(projectName: string, currentlySelected: boolean) {
|
|
if (currentlySelected) {
|
|
deselectProject(projectName);
|
|
} else {
|
|
selectProject(projectName);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<h3 className="mt-4 py-2 uppercase tracking-wide font-semibold text-sm lg:text-xs text-gray-900 cursor-text">
|
|
{headerText}
|
|
</h3>
|
|
<ul className="mt-2 -ml-3">
|
|
{sortedProjects.map((project) => {
|
|
return (
|
|
<ProjectListItem
|
|
key={project.projectGraphNode.name}
|
|
project={project}
|
|
toggleProject={toggleProject}
|
|
focusProject={focusProject}
|
|
></ProjectListItem>
|
|
);
|
|
})}
|
|
</ul>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function ProjectList() {
|
|
const depGraphService = useDepGraphService();
|
|
|
|
function deselectProject(projectName: string) {
|
|
depGraphService.send({ type: 'deselectProject', projectName });
|
|
}
|
|
|
|
function selectProject(projectName: string) {
|
|
depGraphService.send({ type: 'selectProject', projectName });
|
|
}
|
|
|
|
function focusProject(projectName: string) {
|
|
depGraphService.send({ type: 'focusProject', projectName });
|
|
}
|
|
|
|
const projects = useDepGraphSelector(allProjectsSelector);
|
|
const workspaceLayout = useDepGraphSelector(workspaceLayoutSelector);
|
|
const selectedProjects = useDepGraphSelector(selectedProjectNamesSelector);
|
|
|
|
const appProjects = getProjectsByType('app', projects);
|
|
const libProjects = getProjectsByType('lib', projects);
|
|
const e2eProjects = getProjectsByType('e2e', projects);
|
|
|
|
const appDirectoryGroups = groupProjectsByDirectory(
|
|
appProjects,
|
|
selectedProjects,
|
|
workspaceLayout
|
|
);
|
|
const libDirectoryGroups = groupProjectsByDirectory(
|
|
libProjects,
|
|
selectedProjects,
|
|
workspaceLayout
|
|
);
|
|
const e2eDirectoryGroups = groupProjectsByDirectory(
|
|
e2eProjects,
|
|
selectedProjects,
|
|
workspaceLayout
|
|
);
|
|
|
|
const sortedAppDirectories = Object.keys(appDirectoryGroups).sort();
|
|
const sortedLibDirectories = Object.keys(libDirectoryGroups).sort();
|
|
const sortedE2EDirectories = Object.keys(e2eDirectoryGroups).sort();
|
|
|
|
return (
|
|
<div id="project-lists" className="mt-8 px-4 border-t border-gray-200">
|
|
<h2 className="mt-8 text-lg font-bold border-b border-gray-50 border-solid">
|
|
app projects
|
|
</h2>
|
|
|
|
{sortedAppDirectories.map((directoryName) => {
|
|
return (
|
|
<SubProjectList
|
|
key={'app-' + directoryName}
|
|
headerText={directoryName}
|
|
projects={appDirectoryGroups[directoryName]}
|
|
deselectProject={deselectProject}
|
|
selectProject={selectProject}
|
|
focusProject={focusProject}
|
|
></SubProjectList>
|
|
);
|
|
})}
|
|
|
|
<h2 className="mt-8 text-lg font-bold border-b border-gray-50 border-solid">
|
|
e2e projects
|
|
</h2>
|
|
|
|
{sortedE2EDirectories.map((directoryName) => {
|
|
return (
|
|
<SubProjectList
|
|
key={'e2e-' + directoryName}
|
|
headerText={directoryName}
|
|
projects={e2eDirectoryGroups[directoryName]}
|
|
deselectProject={deselectProject}
|
|
selectProject={selectProject}
|
|
focusProject={focusProject}
|
|
></SubProjectList>
|
|
);
|
|
})}
|
|
|
|
<h2 className="mt-8 text-lg font-bold border-b border-gray-50 border-solid">
|
|
lib projects
|
|
</h2>
|
|
|
|
{sortedLibDirectories.map((directoryName) => {
|
|
return (
|
|
<SubProjectList
|
|
key={'lib-' + directoryName}
|
|
headerText={directoryName}
|
|
projects={libDirectoryGroups[directoryName]}
|
|
deselectProject={deselectProject}
|
|
selectProject={selectProject}
|
|
focusProject={focusProject}
|
|
></SubProjectList>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default ProjectList;
|