Skip to content

Commit b796265

Browse files
committed
#855 Add code usage dialog
1 parent 8618b0a commit b796265

27 files changed

+1761
-247
lines changed

browser/data-browser/index.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@
2929
background-color: var(--background-color);
3030
}
3131

32+
.loader {
33+
opacity: 1;
34+
transition: opacity 300ms ease-out, transform 300ms ease-out;
35+
transform: translateY(0);
36+
37+
@starting-style {
38+
opacity: 0;
39+
transform: translateY(20px);
40+
}
41+
}
3242
</style>
3343
<!-- Meta tags and code added by Atomic-Server -->
3444
<!-- { inject_html_head } -->
@@ -39,7 +49,7 @@
3949
<body>
4050
<div id="root">
4151
<svg width="647" height="75" viewBox="0 0 647 75" fill="none" xmlns="http://www.w3.org/2000/svg"
42-
style="max-width: 70vw; width: 30rem; margin: auto; display: block; margin-top: 45vh;">
52+
style="max-width: 70vw; width: 30rem; margin: auto; display: block; margin-top: 45vh;" class="loader">
4353
<path
4454
d="M57.512 58.4H23.712L17.264 74H0L32.448 1.19995H49.088L81.64 74H63.96L57.512 58.4ZM52.208 45.608L40.664 17.736L29.12 45.608H52.208Z"
4555
fill="var(--text-splash)" />

browser/data-browser/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@tomic/react": "workspace:*",
2121
"emoji-mart": "^5.5.2",
2222
"polished": "^4.1.0",
23+
"prismjs": "^1.29.0",
2324
"query-string": "^7.0.0",
2425
"quick-score": "^0.0.10",
2526
"react": "^18.2.0",
@@ -45,19 +46,21 @@
4546
"yamde": "^1.7.1"
4647
},
4748
"devDependencies": {
48-
"vite": "^5.2.10",
4949
"@swc/plugin-styled-components": "^1.5.110",
50+
"@types/prismjs": "^1.26.4",
5051
"@types/react-pdf": "^6.2.0",
5152
"@types/react-window": "^1.8.7",
5253
"@vitejs/plugin-react-swc": "^3.5.0",
5354
"csstype": "^3.1.0",
5455
"gh-pages": "^5.0.0",
5556
"lint-staged": "^10.5.4",
5657
"types-wm": "^1.1.0",
58+
"typescript": "^5.4.5",
59+
"vite": "^5.2.10",
60+
"vite-plugin-prismjs": "^0.0.11",
5761
"vite-plugin-pwa": "^0.17.0",
5862
"vite-plugin-webfont-dl": "^3.9.1",
59-
"workbox-cli": "^6.4.1",
60-
"typescript": "^5.4.5"
63+
"workbox-cli": "^6.4.1"
6164
},
6265
"type": "module",
6366
"homepage": "https://atomicdata.dev/",
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
import { styled } from 'styled-components';
3+
import Prism from 'prismjs';
4+
import { ScrollArea } from '@radix-ui/react-scroll-area';
5+
import {
6+
IconButton,
7+
IconButtonVariant,
8+
} from '../../components/IconButton/IconButton';
9+
import { FaCheck, FaCopy } from 'react-icons/fa6';
10+
11+
export interface HiglightedCodeBlockProps {
12+
code: string;
13+
className?: string;
14+
}
15+
16+
/** Codeblock with sytax hightlighting for typescript code.
17+
* Do not import this component directly, use {@link HighlightedCodeBlock} instead.
18+
*/
19+
export default function AsyncHighlightedCodeBlock({
20+
code,
21+
className,
22+
}: React.PropsWithChildren<HiglightedCodeBlockProps>): React.JSX.Element {
23+
const [copied, setIsCopied] = useState(false);
24+
25+
const ref = useRef<HTMLElement>(null);
26+
27+
const copyToClipboard = () => {
28+
setIsCopied(true);
29+
navigator.clipboard.writeText(code);
30+
};
31+
32+
useEffect(() => {
33+
if (!ref.current) return;
34+
setTimeout(() => Prism.highlightElement(ref.current!), 0);
35+
}, [code]);
36+
37+
useEffect(() => {
38+
if (copied) {
39+
const timeout = setTimeout(() => setIsCopied(false), 2000);
40+
41+
return () => clearTimeout(timeout);
42+
}
43+
}, [copied]);
44+
45+
return (
46+
<Wrapper>
47+
<StyledScrollArea type='hover' className={className}>
48+
<StyledPre>
49+
<code ref={ref} className='language-typescript'>
50+
{code}
51+
</code>
52+
</StyledPre>
53+
</StyledScrollArea>
54+
<CopyButton
55+
title='Copy code'
56+
variant={IconButtonVariant.Fill}
57+
color='textLight'
58+
size='1.2em'
59+
onClick={copyToClipboard}
60+
>
61+
{copied ? <FaCheck /> : <FaCopy />}
62+
</CopyButton>
63+
</Wrapper>
64+
);
65+
}
66+
67+
const Wrapper = styled.div`
68+
position: relative;
69+
`;
70+
71+
// We have to use a && selector to increase the specificity because prismjs styles have a high specificity by default.
72+
const StyledPre = styled.pre`
73+
&& {
74+
font-size: 0.85rem;
75+
line-height: 1.8em;
76+
margin: 0;
77+
padding: 1rem;
78+
overflow: visible;
79+
height: min-content;
80+
background-color: ${p => p.theme.colors.bg1};
81+
code[class*='language-'],
82+
&[class*='language-'] {
83+
color: ${p => p.theme.colors.text};
84+
text-shadow: none;
85+
}
86+
& .operator {
87+
background-color: ${p => p.theme.colors.bg1};
88+
}
89+
}
90+
`;
91+
92+
const StyledScrollArea = styled(ScrollArea)`
93+
filter: ${p => (p.theme.darkMode ? 'brightness(1.5)' : 'none')};
94+
border-radius: ${p => p.theme.radius};
95+
background-color: ${p => p.theme.colors.bg1};
96+
overflow: auto;
97+
`;
98+
99+
const CopyButton = styled(IconButton)`
100+
position: absolute;
101+
top: 0.5rem;
102+
right: 0.5rem;
103+
`;

browser/data-browser/src/components/Dialog/index.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export enum DialogSlot {
3737
}
3838

3939
export const DIALOG_MEDIA_BREAK_POINT = '640px';
40+
export const VAR_DIALOG_INNER_WIDTH = '--dialog-inner-width';
4041

4142
const ANIM_MS = 80;
4243
const ANIM_SPEED = `${ANIM_MS}ms`;
@@ -158,8 +159,8 @@ const InnerDialog: React.FC<React.PropsWithChildren<InternalDialogProps>> = ({
158159
onMouseDown={handleOutSideClick}
159160
$width={width}
160161
>
161-
<StyledInnerDialog ref={innerDialogRef}>
162-
<PopoverContainer>
162+
<PopoverContainer>
163+
<StyledInnerDialog ref={innerDialogRef}>
163164
<DropdownContainer>
164165
<CloseButtonSlot slot='close'>
165166
<Button icon onClick={cancelDialog} aria-label='close'>
@@ -168,8 +169,8 @@ const InnerDialog: React.FC<React.PropsWithChildren<InternalDialogProps>> = ({
168169
</CloseButtonSlot>
169170
{children}
170171
</DropdownContainer>
171-
</PopoverContainer>
172-
</StyledInnerDialog>
172+
</StyledInnerDialog>
173+
</PopoverContainer>
173174
</StyledDialog>
174175
);
175176
};
@@ -235,6 +236,7 @@ const StyledInnerDialog = styled.div`
235236
grid-template-rows: 1fr auto auto;
236237
gap: 1rem;
237238
grid-template-areas: 'title close' 'content content' 'actions actions';
239+
max-block-size: calc(100vh - ${p => p.theme.margin}rem * 2);
238240
`;
239241

240242
const fadeInForground = keyframes`
@@ -261,6 +263,12 @@ const fadeInBackground = keyframes`
261263

262264
const StyledDialog = styled.dialog<{ $width?: CSS.Property.Width }>`
263265
--animation-speed: 500ms;
266+
--dialog-width: min(90vw, ${p => p.$width ?? '60ch'});
267+
268+
${VAR_DIALOG_INNER_WIDTH}: calc(
269+
var(--dialog-width) - 2 * ${p => p.theme.margin}rem
270+
);
271+
264272
box-sizing: border-box;
265273
inset: 0px;
266274
position: relative;
@@ -270,11 +278,9 @@ const StyledDialog = styled.dialog<{ $width?: CSS.Property.Width }>`
270278
background-color: ${props => props.theme.colors.bg};
271279
border-radius: ${props => props.theme.radius};
272280
border: solid 1px ${props => props.theme.colors.bg2};
273-
max-inline-size: min(90vw, ${p => p.$width ?? '100ch'});
274-
min-inline-size: min(90vw, ${p => p.$width ?? '60ch'});
281+
inline-size: var(--dialog-width);
275282
max-block-size: 100vh;
276283
height: fit-content;
277-
max-height: 90vh;
278284
overflow: visible;
279285
box-shadow: ${p => p.theme.boxShadowSoft};
280286

browser/data-browser/src/components/ErrorLook.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,18 @@ export function ErrorBlock({ error, showTrace }: ErrorBlockProps): JSX.Element {
2525
<FaExclamationTriangle />
2626
Something went wrong
2727
</BiggerText>
28-
<CodeBlock>{error.message}</CodeBlock>
29-
{showTrace && (
30-
<>
31-
<span>Stack trace:</span>
32-
<CodeBlock>{error.stack}</CodeBlock>
33-
</>
34-
)}
28+
<Pre>
29+
<code>{error.message}</code>
30+
{showTrace && (
31+
<>
32+
<br />
33+
<br />
34+
<span>Stack trace:</span>
35+
<br />
36+
<code>{error.stack}</code>
37+
</>
38+
)}
39+
</Pre>
3540
</ErrorLookBig>
3641
);
3742
}
@@ -45,11 +50,12 @@ const ErrorLookBig = styled.div`
4550
background-color: ${p => p.theme.colors.bg1};
4651
`;
4752

48-
const CodeBlock = styled.code`
53+
const Pre = styled.pre`
4954
white-space: pre-wrap;
5055
border-radius: ${p => p.theme.radius};
5156
padding: ${p => p.theme.margin}rem;
5257
background-color: ${p => p.theme.colors.bg};
58+
font-size: 0.9rem;
5359
`;
5460

5561
const BiggerText = styled.p`

browser/data-browser/src/components/ExternalLink.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ ExternalLink.defaultProps = {
3434
};
3535

3636
const ExternalLinkPlain = styled.a`
37-
display: flex;
37+
display: inline-flex;
3838
align-items: center;
39-
gap: 0.5rem;
39+
gap: 0.6ch;
40+
color: ${props => props.theme.colors.main};
41+
text-decoration: none;
4042
`;
4143

4244
const ExternalLinkButton = styled.a`
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Suspense, lazy } from 'react';
2+
import type { HiglightedCodeBlockProps } from '../chunks/HighlightedCode/HighlightedCodeBlock';
3+
4+
const CodeBlock = lazy(
5+
() => import('../chunks/HighlightedCode/HighlightedCodeBlock'),
6+
);
7+
8+
export function HighlightedCodeBlock({
9+
children,
10+
...props
11+
}: React.PropsWithChildren<HiglightedCodeBlockProps>): React.JSX.Element {
12+
return (
13+
<Suspense fallback={<div>Loading...</div>}>
14+
<CodeBlock {...props}>{children}</CodeBlock>
15+
</Suspense>
16+
);
17+
}

browser/data-browser/src/components/Navigation.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,10 @@ const SideBarWrapper = styled('div')`
216216
bottom: 0;
217217
left: 0;
218218
right: 0;
219+
220+
opacity: 1;
221+
transition: opacity 0.3s ease-out;
222+
@starting-style {
223+
opacity: 0;
224+
}
219225
`;

browser/data-browser/src/components/ResourceContextMenu/index.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { DropdownTriggerRenderFunction } from '../Dropdown/DropdownTrigger';
1717
import { buildDefaultTrigger } from '../Dropdown/DefaultTrigger';
1818
import {
1919
FaClock,
20+
FaCode,
2021
FaDownload,
2122
FaEdit,
2223
FaEllipsisV,
@@ -33,6 +34,7 @@ import {
3334
import { ResourceInline } from '../../views/ResourceInline';
3435
import { ResourceUsage } from '../ResourceUsage';
3536
import { useCurrentSubject } from '../../helpers/useCurrentSubject';
37+
import { ResourceCodeUsageDialog } from '../../views/CodeUsage/ResourceCodeUsageDialog';
3638

3739
export enum ContextMenuOptions {
3840
View = 'view',
@@ -44,6 +46,7 @@ export enum ContextMenuOptions {
4446
Delete = 'delete',
4547
History = 'history',
4648
Import = 'import',
49+
UseInCode = 'useInCode',
4750
}
4851

4952
export interface ResourceContextMenuProps {
@@ -76,6 +79,8 @@ function ResourceContextMenu({
7679
const location = useLocation();
7780
const resource = useResource(subject);
7881
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
82+
const [showCodeUsageDialog, setShowCodeUsageDialog] = useState(false);
83+
7984
const [currentSubject] = useCurrentSubject();
8085

8186
const { enableScope } = useQueryScopeHandler(subject);
@@ -143,6 +148,14 @@ function ResourceContextMenu({
143148
shortcut: simple ? '' : shortcuts.edit,
144149
onClick: () => navigate(editURL(subject)),
145150
},
151+
{
152+
id: ContextMenuOptions.UseInCode,
153+
label: 'use in code',
154+
helper:
155+
'Usage instructions for how to fetch and use the resource in your code.',
156+
icon: <FaCode />,
157+
onClick: () => setShowCodeUsageDialog(true),
158+
},
146159
{
147160
id: ContextMenuOptions.Scope,
148161
label: 'search in',
@@ -219,6 +232,13 @@ function ResourceContextMenu({
219232
<ResourceUsage resource={resource} />
220233
</>
221234
</ConfirmationDialog>
235+
{currentSubject && (
236+
<ResourceCodeUsageDialog
237+
subject={currentSubject}
238+
show={showCodeUsageDialog}
239+
bindShow={setShowCodeUsageDialog}
240+
/>
241+
)}
222242
</>
223243
);
224244
}

browser/data-browser/src/components/Row.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const Flex = styled.div<FlexProps>`
5858
gap: ${p => p.gap ?? `${p.theme.margin}rem`};
5959
justify-content: ${p => p.justify ?? 'start'};
6060
flex-direction: ${p => p.direction ?? 'row'};
61-
flex-wrap: ${p => (p.wrapItems ? 'wrap' : 'no-wrap')};
61+
flex-wrap: ${p => (p.wrapItems ? 'wrap' : 'nowrap')};
6262
width: ${p => (p.fullWidth ? '100%' : 'initial')};
6363
height: ${p => (p.fullHeight ? '100%' : 'initial')};
6464

0 commit comments

Comments
 (0)