Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions src/core/trainCategory.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
/**
* For representation of each Train Type depending on the train model/ company
*/

export class TrainCategory {
/** Name of the train category */
#name: string;
/** Full name of the train category */
#fullName: string;
/** Priority scale value amongst trains */
#priority: number = 0;
/** The time exceeding which will result in some delay (in seconds) */
Expand All @@ -13,31 +16,68 @@ export class TrainCategory {
/** Parameter describing capability of gaining the above speed in m/s^2 (maximal acceleration value) */
#acceleration: number = 0;

constructor(name: string, priority: number, maxWaitingTime: number, maxVelocity: number, acceleration: number) {
constructor(
name: string,
fullName: string,
priority: number,
maxWaitingTime: number,
maxVelocity: number,
acceleration: number
) {
this.#name = name;
this.#fullName = fullName;
this.#priority = priority;
this.#maxWaitingTime = maxWaitingTime;
this.#maxVelocity = maxVelocity;
this.#acceleration = acceleration;
}

/**
* The time exceeding which will result in giving up waiting (in seconds)
*/
get maxWaitingTime() {
return this.#maxWaitingTime;
}

// getters to use in the simulation logic and not to allow direct modification
get name() {
return this.#name;
}
get fullName() {
return this.#fullName;
}
get priority() {
return this.#priority;
}
get maxWaitingTime() {
return this.#maxWaitingTime;
}
get maxVelocity() {
return this.#maxVelocity;
}
get acceleration() {
return this.#acceleration;
}

// getters for the editable fields in the UI
get UIPriority() {
return this.#priority;
}
get UIMaxWaitingTime() {
return this.#maxWaitingTime;
}
get UIMaxVelocity() {
return this.#maxVelocity;
}
get UIAcceleration() {
return this.#acceleration;
}
// setters for the editable fields in the UI
set UIPriority(value: number) {
this.#priority = value;
}
set UIMaxWaitingTime(value: number) {
this.#maxWaitingTime = value;
}
set UIMaxVelocity(value: number) {
this.#maxVelocity = value;
}
set UIAcceleration(value: number) {
this.#acceleration = value;
}
}

export type EditableTrainCategoryFields = "UIPriority" | "UIMaxVelocity" | "UIAcceleration" | "UIMaxWaitingTime";
1 change: 1 addition & 0 deletions src/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@import "leaflet";
@import "./icons.css";
@import "./components.css";
@import "./scroll.css";

#map {
position: fixed;
Expand Down
27 changes: 27 additions & 0 deletions src/css/scroll.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
::-webkit-scrollbar {
width: 12px;
height: 12px;
}

::-webkit-scrollbar-track {
background: transparent;
border-radius: 10px;
}

::-webkit-scrollbar-thumb {
background: var(--color-stone-700);
border-radius: 10px;
}

::-webkit-scrollbar-thumb:hover {
background: var(--color-stone-800);
}

::-webkit-scrollbar-button {
display: none;
}

* {
scrollbar-width: thin;
scrollbar-color: var(--color-stone-700) transparent;
}
6 changes: 6 additions & 0 deletions src/ui/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import Loading from "./Loading";
import Stats from "./Stats";
import InfoPanel from "./InfoPanel";
import Search from "./Search";
import Settings from "./Settings";

export default function App() {
const [openStats, setOpenStats] = useState(false);
const [openSettings, setOpenSettings] = useState(false);

return (
<>
Expand All @@ -15,8 +17,12 @@ export default function App() {
onToggleStats={() => {
setOpenStats((s) => !s);
}}
onToggleSettings={() => {
setOpenSettings((s) => !s);
}}
/>
{openStats && <Stats onClose={() => setOpenStats(false)} />}
{openSettings && <Settings onClose={() => setOpenSettings(false)} />}
<InfoPanel />
<Search />
</>
Expand Down
37 changes: 37 additions & 0 deletions src/ui/components/CategorySetting.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { MdOutlineKeyboardArrowDown, MdOutlineKeyboardArrowRight } from "react-icons/md";
import type { TrainCategory } from "../../core/trainCategory";
import CategorySettingsField from "./CategorySettingsField";

interface CategorySettingsProps {
category: TrainCategory;
isCollapsed?: boolean;
onToggle?: () => void;
}

export default function CategorySettings({ category, isCollapsed, onToggle }: CategorySettingsProps) {
return (
<div className="border-b border-stone-700 ">
<div className="flex flex-row items-center gap-2 py-2 cursor-pointer" onClick={onToggle}>
{isCollapsed ? <MdOutlineKeyboardArrowRight /> : <MdOutlineKeyboardArrowDown />} {category.fullName}
</div>
{!isCollapsed && (
<div className="pl-6 pb-4 text-sm opacity-80 pr-4">
<CategorySettingsField field="UIPriority" label="Priority" category={category} unit="" />
<CategorySettingsField
field="UIMaxWaitingTime"
label="Max Waiting Time"
category={category}
unit="s"
/>
<CategorySettingsField field="UIMaxVelocity" label="Max Velocity" category={category} unit="m/s" />
<CategorySettingsField
field="UIAcceleration"
label="Acceleration"
category={category}
unit="m/s²"
/>
</div>
)}
</div>
);
}
31 changes: 31 additions & 0 deletions src/ui/components/CategorySettingsField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useEffect, useState } from "react";
import type { EditableTrainCategoryFields, TrainCategory } from "../../core/trainCategory";

interface CategorySettingsFieldProps {
field: EditableTrainCategoryFields;
label: string;
unit: string;
category: TrainCategory;
}

export default function CategorySettingsField({ field, label, unit, category }: CategorySettingsFieldProps) {
const [value, setValue] = useState(category[field]);
useEffect(() => {
category[field] = value;
}, [value, field, category]);

return (
<div className="flex flex-row justify-between py-1">
<span>{label}:</span>
<span className="flex flex-row items-center">
<input
type="number"
value={value}
className="bg-stone-700 p-1 rounded w-20 text-right"
onChange={(e) => setValue(parseFloat(e.currentTarget.value))}
/>
<span className="ml-2 w-[5ch]">{unit}</span>
</span>
</div>
);
}
7 changes: 6 additions & 1 deletion src/ui/components/Controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { FaChartLine, FaForwardStep, FaPause, FaPlay } from "react-icons/fa6";
import useSimulation from "../hooks/useSimulation";
import { VscDebugRestart } from "react-icons/vsc";
import Stats from "./Stats";
import { MdSettings } from "react-icons/md";

interface ControlsProps {
onToggleStats?: () => void;
onToggleSettings?: () => void;
}

export default function Controls({ onToggleStats }: ControlsProps) {
export default function Controls({ onToggleStats, onToggleSettings }: ControlsProps) {
const [simulation, simulationState, updateSimulationState] = useSimulation();

return (
Expand Down Expand Up @@ -46,6 +48,9 @@ export default function Controls({ onToggleStats }: ControlsProps) {
<button className="btn btn-icon" onClick={onToggleStats}>
<FaChartLine />
</button>
<button className="btn btn-icon" onClick={onToggleSettings}>
<MdSettings />
</button>
</div>

<div className="text-center">
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/InfoPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function InfoPanel() {
if (selected === null) return null;

return (
<div className="fixed top-4 left-4 w-lg p-4 bg-stone-900 text-white rounded shadow-lg max-h-[85vh] overflow-y-scroll">
<div className="fixed top-4 left-4 w-lg p-4 bg-stone-900 text-white rounded shadow-lg z-10">
<button className="absolute top-2 right-2 btn btn-icon btn-sm" onClick={() => setSelected(null)}>
<FaTimes />
</button>
Expand Down
4 changes: 2 additions & 2 deletions src/ui/components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function Search() {
}, [searchText]);

return (
<div className="fixed top-0 left-0 w-full flex items-center justify-center pointer-events-none z-10">
<div className="fixed top-0 left-0 w-full flex items-center justify-center pointer-events-none">
<input
type="text"
placeholder="Search..."
Expand All @@ -59,7 +59,7 @@ export default function Search() {
className="mt-4 w-md p-2 rounded-lg border border-gray-300 pointer-events-auto bg-stone-900 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{searchText.length > 0 && (
<div className="fixed top-0 mt-15 max-h-2/5 overflow-y-scroll w-md bg-stone-800 border border-gray-300 rounded-lg pointer-events-auto">
<div className="fixed top-0 mt-15 max-h-2/5 overflow-y-auto w-md bg-stone-800 border border-gray-300 rounded-lg pointer-events-auto">
{searchResults.map((result, index) => (
<div
key={index}
Expand Down
39 changes: 39 additions & 0 deletions src/ui/components/Settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { FaTimes } from "react-icons/fa";
import { categoryManager } from "../../utils/categories";
import CategorySettings from "./CategorySetting";
import { useState } from "react";

interface SettingsProps {
onClose?: () => void;
}

export default function Settings({ onClose }: SettingsProps) {
const [openCategory, setOpenCategory] = useState(null as string | null);

return (
<div
className="fixed top-0 left-0 h-screen w-screen bg-black/50 backdrop-blur-sm flex justify-center z-20 "
onClick={onClose}
>
<div
className="bg-stone-900 text-white rounded-lg p-6 mt-20 w-1/2 h-2/3 shadow-lg relative"
onClick={(e) => e.stopPropagation()}
>
<button className="absolute top-2 right-2 btn btn-icon btn-sm " onClick={onClose}>
<FaTimes />
</button>
<h2 className="text-2xl mb-4">Settings</h2>
<div className="overflow-y-auto pr-2" style={{ maxHeight: "calc(100% - 3rem)" }}>
{categoryManager.getCategories().map((category) => (
<CategorySettings
key={category.name}
category={category}
isCollapsed={category.name !== openCategory}
onToggle={() => setOpenCategory((c) => (c === category.name ? null : category.name))}
/>
))}
</div>
</div>
</div>
);
}
Loading