Skip to content

Commit

Permalink
ui-manchette: integration manchette + spacetimechart done
Browse files Browse the repository at this point in the history
  • Loading branch information
Math-R committed Jul 9, 2024
1 parent 102e682 commit 9acd20c
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 55 deletions.
5 changes: 3 additions & 2 deletions ui-manchette/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"dependencies": {
"classnames": "^2.5.1",
"lodash.isequal": "^4.5.0",
"tailwindcss": "^3.4.1"
"tailwindcss": "^3.4.1",
"@osrd-project/ui-speedspacechart": "^0.0.25"
},
"peerDependencies": {
"react": ">=18.0"
Expand All @@ -52,4 +53,4 @@
"rollup-plugin-serve": "^1.1.1"
},
"gitHead": "973ad1478be4544e1c97303b844903247d9a9cd7"
}
}
161 changes: 127 additions & 34 deletions ui-manchette/src/components/Manchette.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import React, { useEffect } from 'react';
import React, { useEffect, useRef } from 'react';
import OperationalPointList from './OperationalPointList';
import { OperationalPointType, ProjectPathTrainResult, StyledOperationalPointType } from '../types';
import { ZoomIn, ZoomOut } from '@osrd-project/ui-icons';
import { SpaceTimeChart, PathLayer } from '@osrd-project/ui-spacetimechart';
import { calcOperationalPointsToDisplay, operationalPointsHeight } from './helpers';
import { OperationalPoint, PathData } from 'ui-spacetimechart/dist/lib/types';
import { BASE_KM_HEIGHT } from './consts';
import { SpaceScale } from 'ui-spacetimechart/dist/lib/types';

import {
OperationalPoint,
PathData,
SpaceScale,
} from '@osrd-project/ui-spacetimechart/dist/lib/types';
import {
BASE_KM_HEIGHT,
BASE_OP_HEIGHT,
INITIAL_OP_LIST_HEIGHT,
INITIAL_SPACE_TIME_CHART_HEIGHT,
} from './consts';
import { getDiff } from '../utils/vector';
import { useIsOverflow } from '../hooks/isOverFlowed';
type ManchetteProps = {
operationalPoints: OperationalPointType[];
projectPathTrainResult: ProjectPathTrainResult[];
Expand All @@ -19,13 +28,20 @@ const Manchette: React.FC<ManchetteProps> = ({
projectPathTrainResult,
isProportionnal = false,
}) => {
const manchette = useRef<HTMLDivElement>(null);

const [zoomY, setZoomY] = React.useState(1);

const xZoomLevel = 1;

const [xOffset, setXOffset] = React.useState(0);

const [yOffset, setYOffset] = React.useState(0);
const [panning, setPanning] = React.useState<{ initialOffset: { x: number; y: number } } | null>(
null
);

const [scrollPosition, setScrollPosition] = React.useState(0);

const [operationalPointsToDisplay, setOperationalPointsToDisplay] = React.useState<
StyledOperationalPointType[]
Expand All @@ -35,6 +51,8 @@ const Manchette: React.FC<ManchetteProps> = ({
[]
);

const [panY, setPanY] = React.useState<boolean>(false);

const [scales, setScales] = React.useState<SpaceScale[]>([]);

const zoomYIn = () => {
Expand All @@ -45,6 +63,10 @@ const Manchette: React.FC<ManchetteProps> = ({
if (zoomY > 1) setZoomY(zoomY - 0.5);
};

useIsOverflow(manchette, (isOverflowFromCallback) => {
setPanY(isOverflowFromCallback);
});

const paths: (PathData & { color: string })[] = projectPathTrainResult.map((path, index) => ({
id: path.departure_time,
label: `Train ${index + 1}`,
Expand Down Expand Up @@ -80,13 +102,31 @@ const Manchette: React.FC<ManchetteProps> = ({
from: point.position,
to: ops[i + 1].position,
...(!isProportionnal
? { size: 50 * zoomY }
? { size: BASE_OP_HEIGHT * zoomY }
: { coefficient: ((1000 / BASE_KM_HEIGHT) * 1000) / zoomY }),
};
});
return scales;
};

const handleScroll = () => {
if (manchette.current) {
const { scrollTop } = manchette.current;
if (scrollTop || scrollTop === 0) {
setScrollPosition(scrollTop);
setYOffset(scrollTop);
}
}
};

useEffect(() => {
window.addEventListener('scroll', handleScroll, { passive: true });

return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);

useEffect(() => {
const computedOperationalPoints = calcOperationalPointsToDisplay(
operationalPoints,
Expand All @@ -102,41 +142,94 @@ const Manchette: React.FC<ManchetteProps> = ({

useEffect(() => {
setScales(getScales(operationalPointsChart));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [operationalPointsChart]);

return (
<div className="manchette flex">
<div className="manchette-container">
<OperationalPointList operationalPoints={operationalPointsToDisplay} />
<div className="manchette-actions flex items-center">
<div className=" flex items-center ">
<button className="h-full px-3 w-full" onClick={zoomYOut} disabled={zoomY === 1}>
<ZoomOut />
</button>
<div className="manchette-space-time-chart-wrapper">
<div
className="header bg-ambientB-5 w-full border-b border-grey-30"
style={{ height: '40px' }}
>
test
</div>
<div ref={manchette} className="manchette flex" onScroll={handleScroll}>
<div className="manchette-container ">
<div
className=" bg-ambientB-10 border-r border-grey-30"
style={{ minHeight: `${INITIAL_OP_LIST_HEIGHT}px` }}
>
<OperationalPointList operationalPoints={operationalPointsToDisplay} />
</div>
<div className=" flex items-center border-x border-black-25 h-full">
<button className="h-full px-3 w-full" onClick={zoomYIn}>
<ZoomIn />
</button>
<div className="manchette-actions flex items-center">
<div className=" flex items-center ">
<button
className="h-full px-3 w-full zoom-out"
onClick={zoomYOut}
disabled={zoomY === 1}
>
<ZoomOut />
</button>
</div>
<div className=" flex items-center border-x border-black-25 h-full">
<button className="h-full px-3 w-full" onClick={zoomYIn}>
<ZoomIn />
</button>
</div>
<div className="items-center ml-auto"></div>
</div>
</div>
</div>
{scales.length > 0 && (
<SpaceTimeChart
className="inset-0 absolute"
operationalPoints={operationalPointsChart}
spaceOrigin={0}
spaceScales={scales}
timeOrigin={Math.min(...projectPathTrainResult.map((p) => +new Date(p.departure_time)))}
timeScale={60000 / xZoomLevel}
xOffset={xOffset}
yOffset={yOffset}
<div
className="space-time-chart-container w-full sticky"
style={{ bottom: 0, left: 0, top: 0, height: `${INITIAL_SPACE_TIME_CHART_HEIGHT}px` }}
>
{paths.map((path, i) => (
<PathLayer key={path.id} index={i} path={path} color={path.color} />
))}
</SpaceTimeChart>
)}
{scales.length > 0 && (
<SpaceTimeChart
className="inset-0 absolute h-full"
style={{ top: 0, height: 'calc(100% - 6px)' }}
operationalPoints={operationalPointsChart}
spaceOrigin={0}
spaceScales={scales}
timeOrigin={Math.min(
...projectPathTrainResult.map((p) => +new Date(p.departure_time))
)}
timeScale={60000 / xZoomLevel}
xOffset={xOffset}
yOffset={-scrollPosition + 14}
onPan={({ initialPosition, position, isPanning }) => {
const diff = getDiff(initialPosition, position);
if (!isPanning) {
setPanning(null);
} else if (!panning) {
setPanning({ initialOffset: { x: xOffset, y: yOffset } });
} else {
const { initialOffset } = panning;
setXOffset(initialOffset.x + diff.x);
if (panY) {
const newYPos = initialOffset.y - diff.y;

if (
manchette.current &&
newYPos >= 0 &&
newYPos + INITIAL_SPACE_TIME_CHART_HEIGHT <= manchette.current?.scrollHeight
) {
setYOffset(newYPos);
setScrollPosition(yOffset);
if (manchette.current && manchette.current.scrollHeight) {
manchette.current.scrollTop = newYPos;
}
}
}
}
}}
>
{paths.map((path, i) => (
<PathLayer key={path.id} index={i} path={path} color={path.color} />
))}
</SpaceTimeChart>
)}
</div>
</div>
</div>
);
};
Expand Down
2 changes: 1 addition & 1 deletion ui-manchette/src/components/OperationalPointList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type OperationalPointListProps = {

const OperationalPointList: React.FC<OperationalPointListProps> = ({ operationalPoints }) => {
return (
<div className="operational-point-list">
<div className="operational-point-list ">
{operationalPoints.map((op, index) => (
<div
key={index}
Expand Down
2 changes: 2 additions & 0 deletions ui-manchette/src/components/consts.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export const BASE_OP_HEIGHT = 32;
export const BASE_KM_HEIGHT = 8;
export const INITIAL_OP_LIST_HEIGHT = 521;
export const INITIAL_SPACE_TIME_CHART_HEIGHT = INITIAL_OP_LIST_HEIGHT + 40;
39 changes: 39 additions & 0 deletions ui-manchette/src/hooks/isOverFlowed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useState, useLayoutEffect, useRef, RefObject } from 'react';

interface CallbackFunction {
(hasOverflow: boolean): void;
}

interface CallbackFunction {
(hasOverflow: boolean): void;
}

export const useIsOverflow = (
ref: RefObject<HTMLElement>,
callback: CallbackFunction
): boolean | undefined => {
const [isOverflow, setIsOverflow] = useState<boolean | undefined>(undefined);

useLayoutEffect(() => {
const { current } = ref;

const trigger = () => {
if (current) {
const hasOverflow = current.scrollHeight > current.clientHeight;
setIsOverflow(hasOverflow);

if (callback) callback(hasOverflow);
}
};

if (current) {
if ('ResizeObserver' in window) {
new ResizeObserver(trigger).observe(current);
}

trigger();
}
}, [callback, ref]);

return isOverflow;
};
56 changes: 38 additions & 18 deletions ui-manchette/src/styles/manchette.css
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
.manchette {
height: 561px;
overflow: auto;
-webkit-border-bottom-left-radius: 4px;
-moz-border-radius-bottomleft: 4px;
border-bottom-left-radius: 4px;
.manchette-space-time-chart-wrapper{
border-radius: 10px;
box-shadow: 0px 4px 9px rgba(0, 0, 0, 0.06), 0px 1px 2px rgba(0, 0, 0, 0.19);
opacity: 1;
background-color: rgba(255, 255, 255, 1);
.manchette {
height: 561px;
overflow: auto;
-webkit-border-bottom-left-radius: 4px;
-moz-border-radius-bottomleft: 4px;
border-bottom-left-radius: 4px;


&-actions {
background-color: rgba(250, 249, 245, 0.20);
height: 2.5rem;
width: inherit;
position: sticky;
bottom: 0;
left: 0;
width: 100%;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border-top: solid 1px;
border-right: solid 1px;
@apply border-black-25;
border-radius: 0px 0px 0px 10px;


&-actions {
background-color: rgba(250, 249, 245, 0.20);
height: 2.5rem;
width: inherit;
position: sticky;
bottom: 0;
left: 0;
width: 100%;
backdrop-filter: blur(8px);
border: solid 1px;
@apply border-black-25
.zoom-out:disabled {
opacity: 0.4;

}
}

.space-time-chart-container {
height: 561px;
width: 100%;
bottom:0;
}
}
}
11 changes: 11 additions & 0 deletions ui-manchette/src/utils/vector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type Point = {
x: number;
y: number;
};

export function getDiff(a: Point, b: Point): Point {
return {
x: b.x - a.x,
y: b.y - a.y,
};
}

0 comments on commit 9acd20c

Please sign in to comment.