Skip to content

Commit bca97ac

Browse files
authored
Merge pull request #87 from dockersamples/86-add-tabs-right-panel
Add tabbed layout to the code panel
2 parents 0a61204 + 6b7eb3f commit bca97ac

File tree

13 files changed

+224
-106
lines changed

13 files changed

+224
-106
lines changed

components/interface/client/src/App.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ $font-size-base: 0.875rem;
2525

2626
$light-bg-subtle-dark: $gray-800;
2727

28-
2928
@import "bootstrap/scss/functions";
3029
@import "bootstrap/scss/variables";
3130
@import "bootstrap/scss/bootstrap";

components/interface/client/src/AppRoute.jsx

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,39 @@
11
import "./App.scss";
22
import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
33
import { ToastContainer } from "react-toastify";
4-
import { IdePanel } from "./components/IdePanel/IdePanel";
54
import { WorkshopPanel } from "./components/WorkshopPanel/WorkshopPanel";
65
import { WorkshopContextProvider } from "./WorkshopContext";
6+
import { TabContextProvider } from "./TabContext";
7+
import { ExternalContentPanel } from "./components/ExternalContentPanel/ExternalContentPanel";
78

89
function AppRoute() {
910
return (
1011
<>
1112
<WorkshopContextProvider>
12-
<PanelGroup direction="horizontal" autoSaveId="persistence">
13-
<Panel defaultSize={50} minSize={20} className="resizable-panel">
14-
<div className="overflow-auto position-relative">
15-
<WorkshopPanel />
16-
</div>
17-
</Panel>
18-
<PanelResizeHandle className="panel-resize-handle">
19-
<svg viewBox="0 0 24 24" data-direction="horizontal">
20-
<path
21-
fill="currentColor"
22-
d="M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2m-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2m0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2m6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2m0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2m0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2"
23-
></path>
24-
</svg>
25-
</PanelResizeHandle>
26-
<Panel
27-
defaultSize={50}
28-
minSize={20}
29-
className="resizable-panel d-flex"
30-
>
31-
<IdePanel />
32-
</Panel>
33-
</PanelGroup>
13+
<TabContextProvider>
14+
<PanelGroup direction="horizontal" autoSaveId="persistence">
15+
<Panel defaultSize={50} minSize={20} className="resizable-panel">
16+
<div className="overflow-auto position-relative">
17+
<WorkshopPanel />
18+
</div>
19+
</Panel>
20+
<PanelResizeHandle className="panel-resize-handle">
21+
<svg viewBox="0 0 24 24" data-direction="horizontal">
22+
<path
23+
fill="currentColor"
24+
d="M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2m-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2m0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2m6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2m0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2m0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2"
25+
></path>
26+
</svg>
27+
</PanelResizeHandle>
28+
<Panel
29+
defaultSize={50}
30+
minSize={20}
31+
className="resizable-panel d-flex"
32+
>
33+
<ExternalContentPanel />
34+
</Panel>
35+
</PanelGroup>
36+
</TabContextProvider>
3437
</WorkshopContextProvider>
3538
<ToastContainer position="bottom-right" theme="dark" />
3639
</>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { createContext, use, useCallback, useContext, useState } from "react";
2+
3+
const TabContext = createContext([]);
4+
5+
export function TabContextProvider({ children }) {
6+
const [tabs, setTabs] = useState([]);
7+
const [activeTab, setActiveTab] = useState(null);
8+
9+
const addTab = useCallback(
10+
(url) => {
11+
setTabs((prevTabs) => [...prevTabs, url]);
12+
setActiveTab(url);
13+
},
14+
[setTabs, setActiveTab],
15+
);
16+
17+
const removeTab = useCallback(
18+
(url) => {
19+
setTabs((prevTabs) => prevTabs.filter((tab) => tab !== url));
20+
setActiveTab((prevActiveTab) =>
21+
prevActiveTab === url ? null : prevActiveTab,
22+
);
23+
},
24+
[setTabs, setActiveTab],
25+
);
26+
27+
const displayLink = useCallback(
28+
(url) => {
29+
setTabs((prevTabs) => {
30+
if (!prevTabs.includes(url)) {
31+
return [...prevTabs, url];
32+
}
33+
return prevTabs;
34+
});
35+
setActiveTab(url);
36+
},
37+
[setTabs, setActiveTab],
38+
);
39+
40+
return (
41+
<TabContext.Provider
42+
value={{ tabs, addTab, removeTab, activeTab, setActiveTab, displayLink }}
43+
>
44+
{children}
45+
</TabContext.Provider>
46+
);
47+
}
48+
49+
export const useTabs = () => useContext(TabContext);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { IdePlaceholder } from "./IdePlaceholder";
2+
import { useTabs } from "../../TabContext";
3+
import Nav from "react-bootstrap/Nav";
4+
import Button from "react-bootstrap/Button";
5+
import "./ExternalContentPanel.scss";
6+
7+
function getTabTitle(url) {
8+
if (url === "http://localhost:8085") return "VS Code";
9+
return url;
10+
}
11+
12+
export function ExternalContentPanel() {
13+
const { tabs, setActiveTab, activeTab, addTab, removeTab } = useTabs();
14+
15+
return (
16+
<div className="d-flex flex-fill flex-column">
17+
<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>
46+
</div>
47+
{activeTab ? (
48+
<iframe style={{ flex: 1, border: "none" }} src={activeTab} />
49+
) : (
50+
<IdePlaceholder onLaunch={() => addTab("http://localhost:8085")} />
51+
)}
52+
</div>
53+
);
54+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#external-content-tabs {
2+
.nav-link {
3+
color: var(--bs-body-color);
4+
}
5+
6+
button {
7+
&:hover,
8+
&:focus {
9+
background-color: var(--bs-secondary);
10+
color: var(--bs-white);
11+
}
12+
}
13+
}

components/interface/client/src/components/IdePanel/IdePanel.jsx

Lines changed: 0 additions & 18 deletions
This file was deleted.

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ export function WorkshopBody() {
5959
onClick={() => changeActiveSection(sections[index - 1].id)}
6060
className="d-flex align-items-center"
6161
>
62-
<span className="material-symbols-outlined me-1">arrow_back</span>
62+
<span className="material-symbols-outlined me-1">
63+
arrow_back
64+
</span>
6365
<span>Previous</span>
6466
</Button>
6567
)}

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

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,31 +57,30 @@ export function CodeBlock({ node, inline, className, children, ...props }) {
5757
{String(children).replace(/\n$/, "")}
5858
</SyntaxHighlighter>
5959
<div className="button-container pt-1 bg-light-subtle align-self-stretch d-flex align-items-center">
60-
{canCopy && (
61-
<CodeBlockAction
62-
icon="content_copy"
63-
onClick={onCopyClick}
64-
completedText="Copied!"
65-
tooltip="Copy to clipboard"
66-
/>
67-
)}
68-
{canRun && (
69-
<CodeBlockAction
70-
icon="play_circle"
71-
onClick={onRunClick}
72-
tooltip="Run code"
73-
/>
74-
)}
75-
76-
{canSaveAsFile && (
77-
<CodeBlockAction
78-
icon="save"
79-
onClick={onSaveAsClick}
80-
completedText="Saved!"
81-
tooltip="Save file"
82-
/>
83-
)}
60+
{canCopy && (
61+
<CodeBlockAction
62+
icon="content_copy"
63+
onClick={onCopyClick}
64+
completedText="Copied!"
65+
tooltip="Copy to clipboard"
66+
/>
67+
)}
68+
{canRun && (
69+
<CodeBlockAction
70+
icon="play_circle"
71+
onClick={onRunClick}
72+
tooltip="Run code"
73+
/>
74+
)}
8475

76+
{canSaveAsFile && (
77+
<CodeBlockAction
78+
icon="save"
79+
onClick={onSaveAsClick}
80+
completedText="Saved!"
81+
tooltip="Save file"
82+
/>
83+
)}
8584
</div>
8685
</div>
8786
);

0 commit comments

Comments
 (0)