Skip to content

Commit

Permalink
feat(graph): add copy button for entire target configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongemi committed Jun 28, 2024
1 parent 336d371 commit e4e0101
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 162 deletions.
47 changes: 12 additions & 35 deletions graph/ui-code-block/src/lib/json-code-block.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import {
ClipboardDocumentCheckIcon,
ClipboardDocumentIcon,
} from '@heroicons/react/24/outline';
// @ts-ignore
import { CopyToClipboard } from 'react-copy-to-clipboard';
// @ts-ignore
import SyntaxHighlighter, { createElement } from 'react-syntax-highlighter';
import { JSX, ReactNode, useEffect, useMemo, useState } from 'react';
import { JSX, ReactNode, useMemo } from 'react';
import { twMerge } from 'tailwind-merge';
import { CopyToClipboardButton } from '@nx/graph/ui-components';

export function JsonCodeBlockPreTag({
children,
Expand All @@ -30,45 +25,27 @@ export function JsonCodeBlockPreTag({
export interface JsonCodeBlockProps {
data: any;
renderSource: (propertyName: string) => ReactNode;
copyTooltipText: string;
}

export function JsonCodeBlock(props: JsonCodeBlockProps): JSX.Element {
const [copied, setCopied] = useState(false);
const jsonString = useMemo(
() => JSON.stringify(props.data, null, 2),
[props.data]
);
useEffect(() => {
if (!copied) return;
const t = setTimeout(() => {
setCopied(false);
}, 3000);
return () => clearTimeout(t);
}, [copied]);
return (
<div className="code-block group relative w-full">
<div className="absolute right-0 top-0 z-10 flex">
<CopyToClipboard
<CopyToClipboardButton
text={jsonString}
onCopy={() => {
setCopied(true);
}}
>
<button
type="button"
className={twMerge(
'not-prose flex',
'border border-slate-200 bg-slate-50/50 p-2 dark:border-slate-700 dark:bg-slate-800/60',
'opacity-0 transition-opacity group-hover:opacity-100'
)}
>
{copied ? (
<ClipboardDocumentCheckIcon className="h-5 w-5 text-blue-500 dark:text-sky-500" />
) : (
<ClipboardDocumentIcon className="h-5 w-5" />
)}
</button>
</CopyToClipboard>
tooltipAlignment="right"
tooltipText={props.copyTooltipText}
className={twMerge(
'not-prose flex',
'border border-slate-200 bg-slate-50/50 p-2 dark:border-slate-700 dark:bg-slate-800/60',
'opacity-0 transition-opacity group-hover:opacity-100'
)}
/>
</div>
<SyntaxHighlighter
language="json"
Expand Down
1 change: 1 addition & 0 deletions graph/ui-components/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './lib/copy-to-clipboard-button';
export * from './lib/debounced-text-input';
export * from './lib/tag';
export * from './lib/dropdown';
Expand Down
20 changes: 20 additions & 0 deletions graph/ui-components/src/lib/copy-to-clipboard-button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Meta, StoryObj } from '@storybook/react';
import {
CopyToClipboardButton,
CopyToClipboardButtonProps,
} from './copy-to-clipboard-button';

const meta: Meta<typeof CopyToClipboardButton> = {
component: CopyToClipboardButton,
title: 'CopyToClipboardButton',
};
export default meta;

type Story = StoryObj<typeof CopyToClipboardButton>;

export const Simple: Story = {
args: {
text: 'Hello, world!',
tooltipAlignment: 'left',
} as CopyToClipboardButtonProps,
};
61 changes: 61 additions & 0 deletions graph/ui-components/src/lib/copy-to-clipboard-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// @ts-ignore
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { JSX, ReactNode, useEffect, useState } from 'react';
import {
ClipboardDocumentCheckIcon,
ClipboardDocumentIcon,
} from '@heroicons/react/24/outline';

export interface CopyToClipboardButtonProps {
text: string;
tooltipText?: string;
tooltipAlignment?: 'left' | 'right';
className?: string;
children?: ReactNode;
}

export function CopyToClipboardButton({
text,
tooltipAlignment,
tooltipText,
className,
children,
}: CopyToClipboardButtonProps) {
const [copied, setCopied] = useState(false);

useEffect(() => {
if (!copied) return;
const t = setTimeout(() => {
setCopied(false);
}, 3000);
return () => clearTimeout(t);
}, [copied]);

return (
<CopyToClipboard
text={text}
onCopy={() => {
setCopied(true);
}}
>
<button
type="button"
data-tooltip={tooltipText ? tooltipText : false}
data-tooltip-align-right={tooltipAlignment === 'right'}
data-tooltip-align-left={tooltipAlignment === 'left'}
className={className}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{copied ? (
<ClipboardDocumentCheckIcon className="inline h-5 w-5 text-blue-500 dark:text-sky-500" />
) : (
<ClipboardDocumentIcon className="inline h-5 w-5" />
)}
{children}
</button>
</CopyToClipboard>
);
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type { ProjectGraphProjectNode } from '@nx/devkit';
import type { ProjectConfiguration, ProjectGraphProjectNode } from '@nx/devkit';
// nx-ignore-next-line
import { GraphError } from 'nx/src/command-line/graph/graph';
/* eslint-enable @nx/enforce-module-boundaries */
import { EyeIcon } from '@heroicons/react/24/outline';
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
import { CopyToClipboardButton } from '@nx/graph/ui-components';
import { TooltipTriggerText } from '../target-configuration-details/tooltip-trigger-text';
import { twMerge } from 'tailwind-merge';
import { Pill } from '../pill';
Expand Down Expand Up @@ -36,6 +37,18 @@ const typeToProjectType = {
e2e: 'E2E',
};

type ProjectConfigurationToCopy = Pick<
ProjectConfiguration,
| 'name'
| 'root'
| 'tags'
| 'targets'
| 'sourceRoot'
| 'implicitDependencies'
| 'release'
| 'projectType'
>;

export const ProjectDetails = ({
project,
sourceMap,
Expand All @@ -48,6 +61,22 @@ export const ProjectDetails = ({
connectedToCloud,
}: ProjectDetailsProps) => {
const projectData = project.data;
const projectDataToCopy: ProjectConfigurationToCopy = pick(
[
'name',
'root',
'tags',
'targets',
'sourceRoot',
'implicitDependencies',
'release',
'projectType',
],
projectData
);
projectDataToCopy.tags = projectDataToCopy?.tags?.filter(
(tag) => !tag.startsWith('npm:')
);
const isCompact = variant === 'compact';

const technologies = [
Expand All @@ -71,7 +100,7 @@ export const ProjectDetails = ({
>
<div
className={twMerge(
`flex items-center justify-between`,
`flex flex-wrap items-center justify-between`,
isCompact ? `gap-1` : `mb-4 gap-2`
)}
>
Expand All @@ -90,17 +119,23 @@ export const ProjectDetails = ({
className="h-6 w-6"
/>
</div>
<span>
{onViewInProjectGraph && viewInProjectGraphPosition === 'top' && (
{onViewInProjectGraph && viewInProjectGraphPosition === 'top' && (
<div className="flex flex-wrap gap-2">
<CopyToClipboardButton
text={JSON.stringify(projectDataToCopy, null, 2)}
className="inline-flex cursor-pointer items-center gap-2 rounded-md px-2 py-1 text-base text-slate-600 ring-2 ring-inset ring-slate-400/40 hover:bg-slate-50 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-800/60"
>
Copy Project
</CopyToClipboardButton>
<ViewInProjectGraphButton
callback={() =>
onViewInProjectGraph({ projectName: project.name })
}
/>
)}{' '}
</span>
</div>
)}
</div>
<div className="flex justify-between py-2">
<div className="flex flex-wrap justify-between py-2">
<div>
{projectData.metadata?.description ? (
<p className="mb-2 text-sm capitalize text-gray-500 dark:text-slate-400">
Expand Down Expand Up @@ -133,16 +168,22 @@ export const ProjectDetails = ({
) : null}
</div>
<div className="self-end">
<span>
{onViewInProjectGraph &&
viewInProjectGraphPosition === 'bottom' && (
{onViewInProjectGraph &&
viewInProjectGraphPosition === 'bottom' && (
<div className="flex flex-wrap gap-2">
<CopyToClipboardButton
text={JSON.stringify(project, null, 2)}
className="inline-flex cursor-pointer items-center gap-2 rounded-md px-2 py-1 text-base text-slate-600 ring-2 ring-inset ring-slate-400/40 hover:bg-slate-50 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-800/60"
>
Copy Project
</CopyToClipboardButton>
<ViewInProjectGraphButton
callback={() =>
onViewInProjectGraph({ projectName: project.name })
}
/>
)}{' '}
</span>
</div>
)}
</div>
</div>
</header>
Expand Down Expand Up @@ -186,3 +227,19 @@ function ViewInProjectGraphButton({ callback }: { callback: () => void }) {
</button>
);
}

// https://github.com/pnpm/ramda/blob/50c6b57110b2f3631ed8633141f12012b7768d85/source/pick.js#L22
function pick<T extends Record<string, any>, N extends keyof T>(
names: Array<N>,
obj: T
): Pick<T, N> {
var result: Partial<T> = {};
var idx = 0;
while (idx < names.length) {
if (names[idx] in obj) {
result[names[idx]] = obj[names[idx]];
}
idx += 1;
}
return result as Pick<T, N>;
}
Loading

0 comments on commit e4e0101

Please sign in to comment.