Skip to content

Commit 47e8850

Browse files
authored
Merge pull request #89 from dockersamples/88-adjust-automatic-link-tab-usage
Replace automatic tab opening with explicit directive
2 parents bca97ac + 5580d04 commit 47e8850

File tree

12 files changed

+247
-78
lines changed

12 files changed

+247
-78
lines changed

components/interface/client/package-lock.json

Lines changed: 57 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/interface/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"rehype-github-alerts": "^4.1.1",
2626
"rehype-mermaid": "^3.0.0",
2727
"rehype-raw": "^7.0.0",
28+
"remark-directive": "^4.0.0",
2829
"remark-gfm": "^4.0.1",
2930
"sass": "^1.89.2"
3031
},

components/interface/client/src/TabContext.jsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createContext, use, useCallback, useContext, useState } from "react";
1+
import { createContext, useCallback, useContext, useState } from "react";
22

33
const TabContext = createContext([]);
44

@@ -7,16 +7,17 @@ export function TabContextProvider({ children }) {
77
const [activeTab, setActiveTab] = useState(null);
88

99
const addTab = useCallback(
10-
(url) => {
11-
setTabs((prevTabs) => [...prevTabs, url]);
10+
(url, title) => {
11+
if (!title) title = url;
12+
setTabs((prevTabs) => [...prevTabs, { url, title }]);
1213
setActiveTab(url);
1314
},
1415
[setTabs, setActiveTab],
1516
);
1617

1718
const removeTab = useCallback(
1819
(url) => {
19-
setTabs((prevTabs) => prevTabs.filter((tab) => tab !== url));
20+
setTabs((prevTabs) => prevTabs.filter((tab) => tab.url !== url));
2021
setActiveTab((prevActiveTab) =>
2122
prevActiveTab === url ? null : prevActiveTab,
2223
);
@@ -25,10 +26,12 @@ export function TabContextProvider({ children }) {
2526
);
2627

2728
const displayLink = useCallback(
28-
(url) => {
29+
(url, title) => {
30+
if (!title) title = url;
31+
2932
setTabs((prevTabs) => {
30-
if (!prevTabs.includes(url)) {
31-
return [...prevTabs, url];
33+
if (!prevTabs.find((tab) => tab.url === url)) {
34+
return [...prevTabs, { url, title }];
3235
}
3336
return prevTabs;
3437
});

components/interface/client/src/components/ExternalContentPanel/ExternalContentPanel.jsx

Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,33 @@
11
import { IdePlaceholder } from "./IdePlaceholder";
22
import { useTabs } from "../../TabContext";
3-
import Nav from "react-bootstrap/Nav";
4-
import Button from "react-bootstrap/Button";
53
import "./ExternalContentPanel.scss";
6-
7-
function getTabTitle(url) {
8-
if (url === "http://localhost:8085") return "VS Code";
9-
return url;
10-
}
4+
import { ExternalTabs } from "./ExternalTabs";
5+
import { useRef } from "react";
116

127
export function ExternalContentPanel() {
138
const { tabs, setActiveTab, activeTab, addTab, removeTab } = useTabs();
9+
const iframeRef = useRef();
1410

1511
return (
1612
<div className="d-flex flex-fill flex-column">
1713
<div className="p-3 pt-2 pb-0 bg-secondary-subtle">
18-
<Nav
19-
variant="tabs"
20-
activeKey={activeTab}
21-
onSelect={(selectedKey) => setActiveTab(selectedKey)}
22-
id="external-content-tabs"
23-
>
24-
{tabs.map((tab) => (
25-
<Nav.Item key={tab} className="me-2 ms-2">
26-
<Nav.Link eventKey={tab} className="p-1 ps-3 pe-1">
27-
<span className="me-3">{getTabTitle(tab)}</span>
28-
29-
{!tab.endsWith("localhost:8085") && (
30-
<Button
31-
size="sm"
32-
variant="default"
33-
className="rounded-circle p-1 pt-0 pb-0"
34-
onClick={(e) => {
35-
e.stopPropagation();
36-
removeTab(tab);
37-
}}
38-
>
39-
&times;
40-
</Button>
41-
)}
42-
</Nav.Link>
43-
</Nav.Item>
44-
))}
45-
</Nav>
14+
<ExternalTabs
15+
activeTab={activeTab}
16+
setActiveTab={setActiveTab}
17+
tabs={tabs}
18+
onTabRemoval={removeTab}
19+
/>
4620
</div>
4721
{activeTab ? (
48-
<iframe style={{ flex: 1, border: "none" }} src={activeTab} />
22+
<iframe
23+
ref={iframeRef}
24+
style={{ flex: 1, border: "none" }}
25+
src={activeTab}
26+
/>
4927
) : (
50-
<IdePlaceholder onLaunch={() => addTab("http://localhost:8085")} />
28+
<IdePlaceholder
29+
onLaunch={() => addTab("http://localhost:8085", "Workspace")}
30+
/>
5131
)}
5232
</div>
5333
);

components/interface/client/src/components/ExternalContentPanel/ExternalContentPanel.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@
99
background-color: var(--bs-secondary);
1010
color: var(--bs-white);
1111
}
12+
13+
span {
14+
position: relative;
15+
top: 1px;
16+
}
1217
}
1318
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Nav from "react-bootstrap/Nav";
2+
import Button from "react-bootstrap/Button";
3+
4+
export function ExternalTabs({
5+
activeTab,
6+
setActiveTab,
7+
tabs,
8+
onTabRemoval,
9+
onRefreshClick,
10+
}) {
11+
return (
12+
<Nav
13+
variant="tabs"
14+
activeKey={activeTab}
15+
onSelect={(selectedKey) => setActiveTab(selectedKey)}
16+
id="external-content-tabs"
17+
>
18+
{tabs.map((tab) => (
19+
<Nav.Item key={tab.url} className="me-2 ms-2">
20+
<Nav.Link
21+
eventKey={tab.url}
22+
href={tab.url}
23+
className="p-1 ps-3 pe-1"
24+
onClick={(e) => {
25+
e.preventDefault();
26+
}}
27+
>
28+
<span className="me-3">{tab.title}</span>
29+
30+
{!tab.title !== "Workspace" && (
31+
<Button
32+
size="sm"
33+
variant="default"
34+
className="rounded-circle p-1 pt-0 pb-0"
35+
onClick={(e) => {
36+
e.stopPropagation();
37+
e.preventDefault();
38+
onTabRemoval(tab.url);
39+
}}
40+
>
41+
<span className="material-symbols-outlined">close</span>
42+
</Button>
43+
)}
44+
</Nav.Link>
45+
</Nav.Item>
46+
))}
47+
</Nav>
48+
);
49+
}

components/interface/client/src/components/WorkshopPanel/WorkshopBody.jsx

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
import { useActiveSection, useWorkshop } from "../../WorkshopContext";
2-
import { MarkdownHooks } from "react-markdown";
3-
import remarkGfm from "remark-gfm";
4-
import rehypeRaw from "rehype-raw";
5-
import rehypeMermaid from "rehype-mermaid";
6-
import { rehypeGithubAlerts } from "rehype-github-alerts";
7-
import { CodeBlock } from "./markdown/CodeBlock";
8-
import { remarkCodeIndexer } from "./markdown/codeIndexer";
92
import { useEffect, useRef } from "react";
10-
import { ExternalLink } from "./markdown/ExternalLink";
113
import Button from "react-bootstrap/Button";
12-
import Image from "react-bootstrap/Image";
13-
import { RenderedImage } from "./markdown/RenderedImage";
14-
import { RenderedSvg } from "./markdown/RenderedSvg";
4+
import { MarkdownRenderer } from "./markdown/MarkdownRenderer";
155

166
export function WorkshopBody() {
177
const bodyRef = useRef();
@@ -38,18 +28,7 @@ export function WorkshopBody() {
3828
<>
3929
<div className="overflow-auto" ref={bodyRef}>
4030
<div className="workshop-body p-5 pt-3 pb-3">
41-
<MarkdownHooks
42-
remarkPlugins={[remarkGfm, remarkCodeIndexer]}
43-
rehypePlugins={[rehypeRaw, rehypeMermaid, rehypeGithubAlerts]}
44-
components={{
45-
code: CodeBlock,
46-
a: ExternalLink,
47-
img: RenderedImage,
48-
svg: RenderedSvg,
49-
}}
50-
>
51-
{activeSection.content}
52-
</MarkdownHooks>
31+
<MarkdownRenderer>{activeSection.content}</MarkdownRenderer>
5332
</div>
5433
<div className="workshop-footer d-flex justify-content-between p-3 border-top">
5534
<div>

components/interface/client/src/components/WorkshopPanel/markdown/ExternalLink.jsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
import { useTabs } from "../../../TabContext";
2-
31
export function ExternalLink({ href, children, ...rest }) {
4-
const { displayLink } = useTabs();
5-
6-
const handleClick = (e) => {
7-
e.preventDefault();
8-
displayLink(href);
9-
};
10-
112
return (
12-
<a href={href} onClick={handleClick} {...rest}>
3+
<a href={href} target="_blank" rel="noopener noreferrer" {...rest}>
134
{children}
145
</a>
156
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { MarkdownHooks } from "react-markdown";
2+
import remarkGfm from "remark-gfm";
3+
import rehypeRaw from "rehype-raw";
4+
import rehypeMermaid from "rehype-mermaid";
5+
import remarkDirective from "remark-directive";
6+
import { rehypeGithubAlerts } from "rehype-github-alerts";
7+
import { CodeBlock } from "./CodeBlock";
8+
import { remarkCodeIndexer } from "./codeIndexer";
9+
import { ExternalLink } from "./ExternalLink";
10+
import { RenderedImage } from "./RenderedImage";
11+
import { RenderedSvg } from "./RenderedSvg";
12+
import { tabDirective } from "./reactDirective";
13+
import { TabLink } from "./TabLink";
14+
15+
export function MarkdownRenderer({ children }) {
16+
return (
17+
<MarkdownHooks
18+
remarkPlugins={[
19+
remarkGfm,
20+
remarkCodeIndexer,
21+
remarkDirective,
22+
tabDirective,
23+
]}
24+
rehypePlugins={[rehypeRaw, rehypeMermaid, rehypeGithubAlerts]}
25+
components={{
26+
code: CodeBlock,
27+
a: ExternalLink,
28+
img: RenderedImage,
29+
svg: RenderedSvg,
30+
tablink: TabLink,
31+
}}
32+
>
33+
{children}
34+
</MarkdownHooks>
35+
);
36+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useTabs } from "../../../TabContext";
2+
3+
export function TabLink({ href, title, children }) {
4+
const { displayLink } = useTabs();
5+
6+
return (
7+
<a
8+
href={href}
9+
title={title}
10+
onClick={(e) => {
11+
e.preventDefault();
12+
displayLink(href, title);
13+
}}
14+
>
15+
{children}
16+
</a>
17+
);
18+
}

0 commit comments

Comments
 (0)