Skip to content

Commit

Permalink
Some progress on the frontend.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jhuighuy committed Dec 20, 2024
1 parent 70287fb commit 34fabac
Show file tree
Hide file tree
Showing 8 changed files with 703 additions and 34 deletions.
4 changes: 3 additions & 1 deletion source/titfront/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.2.1"
"react-icons": "^5.2.1",
"three": "^0.170.0"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@tailwindcss/vite": "4.0.0-beta.8",
"@types/node": "^18.3.12",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/three": "^0.170.0",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"eslint": "^9.13.0",
Expand Down
56 changes: 56 additions & 0 deletions source/titfront/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *\
* Part of the Tit Solver project, under the MIT License.
* See /LICENSE.md for license information. SPDX-License-Identifier: MIT
\* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

import React from "react";
import { FiWind, FiPieChart, FiMove } from "react-icons/fi";

import { MyCanvas } from "./view/Canvas";
import { PhysicsPanel } from "./PhysicsPanel";
import { Panel, Sidebar } from "./common/Sidebar";

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

const Footer: React.FC = () => {
return <div className="h-6 bg-gradient-to-t from-gray-100 to-gray-200" />;
};

const Viewport: React.FC = () => {
return (
<div className="w-full h-full transition-none">
<MyCanvas />
</div>
);
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

export const App: React.FC = () => {
return (
<div className="flex flex-col h-screen w-screen">
<div className="flex-1 flex">
{/* Left sidebar. */}
<Sidebar>
<Panel label="Scene" icon={<FiMove />}>
Scene
</Panel>
<Panel label="Physics" icon={<FiWind />}>
<PhysicsPanel />
</Panel>
<Panel label="Visual" icon={<FiPieChart />}>
View
</Panel>
</Sidebar>
<div className="flex-grow relative">
{/* Main viewport. */}
<Viewport />
</div>
</div>
{/* Footer. */}
<Footer />
</div>
);
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
103 changes: 103 additions & 0 deletions source/titfront/src/components/PhysicsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *\
* Part of the Tit Solver project, under the MIT License.
* See /LICENSE.md for license information. SPDX-License-Identifier: MIT
\* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

import React from "react";
import { TreeView2 } from "./common/TreeView";

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

const treeData = [
{
id: "14",
name: "Kernel",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
},
{
id: "1",
name: "Parent 1",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
children: [
{
id: "2",
value: "123",
name: "Child 1-1",
},
{
id: "3",
name: "Child 1-2",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
children: [
{
id: "4",
name: "Child 1-2-1",
descr:
"A very very very **very** very very very very very very very very very very very very very very very very very very very very very very very long description.",
},
],
},
],
},
{
id: "5",
name: "Parent 2",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
children: [
{
id: "6",
name: "Child 2-1",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
},
],
},
{
id: "7",
name: "Parent 3",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
children: [
{
id: "8",
name: "Child 3-1",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
},
{
id: "9",
name: "Child 3-2",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
children: [
{
id: "10",
name: "Child 3-2-1",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
children: [
{
id: "11",
name: "Child 3-2-1-1",
descr:
"A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long description.",
},
],
},
],
},
],
},
];

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

export const PhysicsPanel: React.FC = () => {
return <TreeView2 nodes={treeData} />;
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
136 changes: 136 additions & 0 deletions source/titfront/src/components/common/ResizableDiv.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *\
* Part of the Tit Solver project, under the MIT License.
* See /LICENSE.md for license information. SPDX-License-Identifier: MIT
\* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

import React, { useState } from "react";

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

export const HorizontalResizableDiv = ({
children,
minWidth = 120,
maxWidth = 720,
initWidth = 320,
}: {
children: React.ReactNode;
minWidth?: number;
maxWidth?: number;
initWidth?: number;
}) => {
const [widthState, setWidthState] = useState(initWidth);

const resizeHandler = (e: React.MouseEvent) => {
e.preventDefault();

// Save the initial mouse position.
const initX = e.clientX;

// Update the width of the sidebar once the mouse is moved.
const onMouseMove = (e: MouseEvent) => {
const delta = e.clientX - initX;
const newWidth = Math.max(
minWidth,
Math.min(maxWidth, widthState + delta)
);
setWidthState(newWidth);
};

// Remove the event listener once the mouse is released.
const onMouseUp = () => {
// Reset the cursor back to the default.
document.body.style.cursor = "default";
// Remove the event listener.
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
};

// Change the cursor to the resize cursor.
document.body.style.cursor = "ew-resize";

// Setup the event listener.
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
};

return (
<div className="flex w-full">
<div
className="flex-grow overflow-auto"
style={{ width: `${widthState}px` }}
>
{children}
</div>
<div
className="w-0.5 hover:cursor-ew-resize bg-gray-300"
onMouseDown={resizeHandler}
/>
</div>
);
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

export const VerticalResizableDiv = ({
children,
minHeight = 120,
maxHeight = 720,
initHeight = 320,
}: {
children: React.ReactNode;
minHeight?: number;
maxHeight?: number;
initHeight?: number;
}) => {
const [heightState, setHeightState] = useState(initHeight);

const resizeHandler = (e: React.MouseEvent) => {
e.preventDefault();

// Save the initial mouse position.
const initY = e.clientY;

// Update the height of the sidebar once the mouse is moved.
const onMouseMove = (e: MouseEvent) => {
const delta = e.clientY - initY;
const newHeight = Math.max(
minHeight,
Math.min(maxHeight, heightState + delta)
);
setHeightState(newHeight);
};

// Remove the event listener once the mouse is released.
const onMouseUp = () => {
// Reset the cursor back to the default.
document.body.style.cursor = "default";
// Remove the event listener.
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
};

// Change the cursor to the resize cursor.
document.body.style.cursor = "ns-resize";

// Setup the event listener.
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
};

return (
<div className="flex flex-col">
<div
className="flex-grow overflow-auto"
style={{ height: `${heightState}px` }}
>
{children}
</div>
<div
className="h-0.5 hover:cursor-ns-resize bg-gray-300"
onMouseDown={resizeHandler}
/>
</div>
);
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
81 changes: 81 additions & 0 deletions source/titfront/src/components/common/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *\
* Part of the Tit Solver project, under the MIT License.
* See /LICENSE.md for license information. SPDX-License-Identifier: MIT
\* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

import React, { useState } from "react";

import { HorizontalResizableDiv, VerticalResizableDiv } from "./ResizableDiv";

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

interface TabProps {
label: string;
end?: boolean;
icon: React.ReactNode;
children: React.ReactNode;
}

export const Panel: React.FC<TabProps> = ({ children }) => {
return <div>{children}</div>;
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

export const Sidebar = ({
children,
}: {
children: React.ReactElement<TabProps>[];
}) => {
// Handle the active panel.
const [activePanelIndex, setActivePanelIndex] = useState(1);

const togglePanel = (index: number) => {
setActivePanelIndex((prev) => (prev === index ? -1 : index));
};

return (
<div className="flex h-full">
{/* Sidebar panel icons. */}
<div
className="w-12 pt-2 flex flex-col items-center
shadow-inner bg-gradient-to-r from-gray-100 to-gray-200"
>
{children.map((panel, index) => (
<div
key={index}
className={`p-2 mb-4 w-10 h-10 flex items-center justify-center
rounded text-6xl text-gray-800 ${
index === activePanelIndex
? "bg-gray-400 shadow text-black"
: "hover:bg-gray-300"
}`}
onClick={() => togglePanel(index)}
>
{panel.props.icon}
</div>
))}
</div>
{/* Sidebar active panel. */}
{activePanelIndex !== -1 && (
<HorizontalResizableDiv>
<div
className="flex items-center h-10 border-b-2
border-gray-300 bg-gray-200"
>
<span className="p-3 text-sm font-bold">
{children[activePanelIndex].props.label}
</span>
</div>
<VerticalResizableDiv>
<div className="p-1 inner-shadow-xl">
{children[activePanelIndex]}
</div>
</VerticalResizableDiv>
</HorizontalResizableDiv>
)}
</div>
);
};

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Loading

0 comments on commit 34fabac

Please sign in to comment.