forked from Mirrors/Dispatcharr
Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into dev
This commit is contained in:
commit
0a4d27c236
2 changed files with 185 additions and 44 deletions
|
|
@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Update docker/dev-build.sh to support private registries, multiple architectures and pushing. Now you can do things like `dev-build.sh -p -r my.private.registry -a linux/arm64,linux/amd64` - Thanks [@jdblack](https://github.com/jblack)
|
||||
- Updated dependencies: Django (5.2.4 → 5.2.9) includes CVE security patch, psycopg2-binary (2.9.10 → 2.9.11), celery (5.5.3 → 5.6.0), djangorestframework (3.16.0 → 3.16.1), requests (2.32.4 → 2.32.5), psutil (7.0.0 → 7.1.3), gevent (25.5.1 → 25.9.1), rapidfuzz (3.13.0 → 3.14.3), torch (2.7.1 → 2.9.1), sentence-transformers (5.1.0 → 5.2.0), lxml (6.0.0 → 6.0.2) (Closes #662)
|
||||
- Frontend dependencies updated: Vite (6.2.0 → 7.1.7), ESLint (9.21.0 → 9.27.0), and related packages; added npm `overrides` to enforce js-yaml@^4.1.1 for transitive security fix. All 6 reported vulnerabilities resolved with `npm audit fix`.
|
||||
- Floating video player now supports resizing via a drag handles, with minimum size enforcement and viewport/page boundary constraints to keep it visible.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,79 @@ export default function FloatingVideo() {
|
|||
const MIN_WIDTH = 220;
|
||||
const MIN_HEIGHT = 124;
|
||||
const VISIBLE_MARGIN = 48; // keep part of the window visible when dragging
|
||||
const HANDLE_SIZE = 18;
|
||||
const HANDLE_OFFSET = 0;
|
||||
const resizeHandleBaseStyle = {
|
||||
position: 'absolute',
|
||||
width: HANDLE_SIZE,
|
||||
height: HANDLE_SIZE,
|
||||
backgroundColor: 'transparent',
|
||||
borderRadius: 6,
|
||||
zIndex: 8,
|
||||
touchAction: 'none',
|
||||
};
|
||||
const resizeHandles = [
|
||||
{
|
||||
id: 'bottom-right',
|
||||
cursor: 'nwse-resize',
|
||||
xDir: 1,
|
||||
yDir: 1,
|
||||
isLeft: false,
|
||||
isTop: false,
|
||||
style: {
|
||||
bottom: HANDLE_OFFSET,
|
||||
right: HANDLE_OFFSET,
|
||||
borderBottom: '2px solid rgba(255, 255, 255, 0.9)',
|
||||
borderRight: '2px solid rgba(255, 255, 255, 0.9)',
|
||||
borderRadius: '0 0 6px 0',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'bottom-left',
|
||||
cursor: 'nesw-resize',
|
||||
xDir: -1,
|
||||
yDir: 1,
|
||||
isLeft: true,
|
||||
isTop: false,
|
||||
style: {
|
||||
bottom: HANDLE_OFFSET,
|
||||
left: HANDLE_OFFSET,
|
||||
borderBottom: '2px solid rgba(255, 255, 255, 0.9)',
|
||||
borderLeft: '2px solid rgba(255, 255, 255, 0.9)',
|
||||
borderRadius: '0 0 0 6px',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'top-right',
|
||||
cursor: 'nesw-resize',
|
||||
xDir: 1,
|
||||
yDir: -1,
|
||||
isLeft: false,
|
||||
isTop: true,
|
||||
style: {
|
||||
top: HANDLE_OFFSET,
|
||||
right: HANDLE_OFFSET,
|
||||
borderTop: '2px solid rgba(255, 255, 255, 0.9)',
|
||||
borderRight: '2px solid rgba(255, 255, 255, 0.9)',
|
||||
borderRadius: '0 6px 0 0',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'top-left',
|
||||
cursor: 'nwse-resize',
|
||||
xDir: -1,
|
||||
yDir: -1,
|
||||
isLeft: true,
|
||||
isTop: true,
|
||||
style: {
|
||||
top: HANDLE_OFFSET,
|
||||
left: HANDLE_OFFSET,
|
||||
borderTop: '2px solid rgba(255, 255, 255, 0.9)',
|
||||
borderLeft: '2px solid rgba(255, 255, 255, 0.9)',
|
||||
borderRadius: '6px 0 0 0',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Safely destroy the mpegts player to prevent errors
|
||||
const safeDestroyPlayer = () => {
|
||||
|
|
@ -344,6 +417,23 @@ export default function FloatingVideo() {
|
|||
[VISIBLE_MARGIN, videoSize.height, videoSize.width]
|
||||
);
|
||||
|
||||
const clampToVisibleWithSize = useCallback(
|
||||
(x, y, width, height) => {
|
||||
if (typeof window === 'undefined') return { x, y };
|
||||
|
||||
const minX = -(width - VISIBLE_MARGIN);
|
||||
const minY = -(height - VISIBLE_MARGIN);
|
||||
const maxX = window.innerWidth - VISIBLE_MARGIN;
|
||||
const maxY = window.innerHeight - VISIBLE_MARGIN;
|
||||
|
||||
return {
|
||||
x: Math.min(Math.max(x, minX), maxX),
|
||||
y: Math.min(Math.max(y, minY), maxY),
|
||||
};
|
||||
},
|
||||
[VISIBLE_MARGIN]
|
||||
);
|
||||
|
||||
const handleResizeMove = useCallback(
|
||||
(event) => {
|
||||
if (!resizeStateRef.current) return;
|
||||
|
|
@ -353,61 +443,111 @@ export default function FloatingVideo() {
|
|||
const clientY =
|
||||
event.touches && event.touches.length ? event.touches[0].clientY : event.clientY;
|
||||
|
||||
const deltaX = clientX - resizeStateRef.current.startX;
|
||||
const deltaY = clientY - resizeStateRef.current.startY;
|
||||
const aspectRatio = resizeStateRef.current.aspectRatio || aspectRatioRef.current;
|
||||
const {
|
||||
startX,
|
||||
startY,
|
||||
startWidth,
|
||||
startHeight,
|
||||
startPos,
|
||||
handle,
|
||||
aspectRatio,
|
||||
} = resizeStateRef.current;
|
||||
const deltaX = clientX - startX;
|
||||
const deltaY = clientY - startY;
|
||||
const widthDelta = deltaX * handle.xDir;
|
||||
const heightDelta = deltaY * handle.yDir;
|
||||
const ratio = aspectRatio || aspectRatioRef.current;
|
||||
|
||||
// Derive width/height while keeping the original aspect ratio
|
||||
let nextWidth = resizeStateRef.current.startWidth + deltaX;
|
||||
let nextHeight = nextWidth / aspectRatio;
|
||||
let nextWidth = startWidth + widthDelta;
|
||||
let nextHeight = nextWidth / ratio;
|
||||
|
||||
// Allow vertical-driven resize if the user drags mostly vertically
|
||||
if (Math.abs(deltaY) > Math.abs(deltaX)) {
|
||||
nextHeight = resizeStateRef.current.startHeight + deltaY;
|
||||
nextWidth = nextHeight * aspectRatio;
|
||||
nextHeight = startHeight + heightDelta;
|
||||
nextWidth = nextHeight * ratio;
|
||||
}
|
||||
|
||||
// Respect minimums while keeping the ratio
|
||||
if (nextWidth < MIN_WIDTH) {
|
||||
nextWidth = MIN_WIDTH;
|
||||
nextHeight = nextWidth / aspectRatio;
|
||||
nextHeight = nextWidth / ratio;
|
||||
}
|
||||
|
||||
if (nextHeight < MIN_HEIGHT) {
|
||||
nextHeight = MIN_HEIGHT;
|
||||
nextWidth = nextHeight * aspectRatio;
|
||||
nextWidth = nextHeight * ratio;
|
||||
}
|
||||
|
||||
// Keep within viewport with a margin based on current position
|
||||
const posX = dragPositionRef.current?.x ?? 0;
|
||||
const posY = dragPositionRef.current?.y ?? 0;
|
||||
const posX = startPos?.x ?? 0;
|
||||
const posY = startPos?.y ?? 0;
|
||||
const margin = VISIBLE_MARGIN;
|
||||
let maxWidth = null;
|
||||
let maxHeight = null;
|
||||
|
||||
const maxWidth = Math.max(MIN_WIDTH, window.innerWidth - posX - margin);
|
||||
const maxHeight = Math.max(MIN_HEIGHT, window.innerHeight - posY - margin);
|
||||
|
||||
if (nextWidth > maxWidth) {
|
||||
nextWidth = maxWidth;
|
||||
nextHeight = nextWidth / aspectRatio;
|
||||
if (!handle.isLeft) {
|
||||
maxWidth = Math.max(MIN_WIDTH, window.innerWidth - posX - margin);
|
||||
}
|
||||
|
||||
if (nextHeight > maxHeight) {
|
||||
if (!handle.isTop) {
|
||||
maxHeight = Math.max(MIN_HEIGHT, window.innerHeight - posY - margin);
|
||||
}
|
||||
|
||||
if (maxWidth != null && nextWidth > maxWidth) {
|
||||
nextWidth = maxWidth;
|
||||
nextHeight = nextWidth / ratio;
|
||||
}
|
||||
|
||||
if (maxHeight != null && nextHeight > maxHeight) {
|
||||
nextHeight = maxHeight;
|
||||
nextWidth = nextHeight * aspectRatio;
|
||||
nextWidth = nextHeight * ratio;
|
||||
}
|
||||
|
||||
// Final pass to honor both bounds while keeping the ratio
|
||||
if (nextWidth > maxWidth) {
|
||||
if (maxWidth != null && nextWidth > maxWidth) {
|
||||
nextWidth = maxWidth;
|
||||
nextHeight = nextWidth / aspectRatio;
|
||||
nextHeight = nextWidth / ratio;
|
||||
}
|
||||
|
||||
setVideoSize({
|
||||
width: Math.round(nextWidth),
|
||||
height: Math.round(nextHeight),
|
||||
});
|
||||
|
||||
if (handle.isLeft || handle.isTop) {
|
||||
let nextX = posX;
|
||||
let nextY = posY;
|
||||
|
||||
if (handle.isLeft) {
|
||||
nextX = posX + (startWidth - nextWidth);
|
||||
}
|
||||
|
||||
if (handle.isTop) {
|
||||
nextY = posY + (startHeight - nextHeight);
|
||||
}
|
||||
|
||||
const clamped = clampToVisibleWithSize(
|
||||
nextX,
|
||||
nextY,
|
||||
nextWidth,
|
||||
nextHeight
|
||||
);
|
||||
|
||||
if (handle.isLeft) {
|
||||
nextX = clamped.x;
|
||||
}
|
||||
|
||||
if (handle.isTop) {
|
||||
nextY = clamped.y;
|
||||
}
|
||||
|
||||
const nextPos = { x: nextX, y: nextY };
|
||||
setDragPosition(nextPos);
|
||||
dragPositionRef.current = nextPos;
|
||||
}
|
||||
},
|
||||
[MIN_HEIGHT, MIN_WIDTH, VISIBLE_MARGIN]
|
||||
[MIN_HEIGHT, MIN_WIDTH, VISIBLE_MARGIN, clampToVisibleWithSize]
|
||||
);
|
||||
|
||||
const endResize = useCallback(() => {
|
||||
|
|
@ -419,7 +559,7 @@ export default function FloatingVideo() {
|
|||
window.removeEventListener('touchend', endResize);
|
||||
}, [handleResizeMove]);
|
||||
|
||||
const startResize = (event) => {
|
||||
const startResize = (event, handle) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
|
|
@ -431,6 +571,10 @@ export default function FloatingVideo() {
|
|||
const aspectRatio =
|
||||
videoSize.height > 0 ? videoSize.width / videoSize.height : aspectRatioRef.current;
|
||||
aspectRatioRef.current = aspectRatio;
|
||||
const startPos =
|
||||
dragPositionRef.current ||
|
||||
initialPositionRef.current ||
|
||||
{ x: 0, y: 0 };
|
||||
|
||||
resizeStateRef.current = {
|
||||
startX: clientX,
|
||||
|
|
@ -438,6 +582,8 @@ export default function FloatingVideo() {
|
|||
startWidth: videoSize.width,
|
||||
startHeight: videoSize.height,
|
||||
aspectRatio,
|
||||
startPos,
|
||||
handle,
|
||||
};
|
||||
|
||||
setIsResizing(true);
|
||||
|
|
@ -666,27 +812,6 @@ export default function FloatingVideo() {
|
|||
</Box>
|
||||
)}
|
||||
|
||||
{/* Resize handle */}
|
||||
<Box
|
||||
className="floating-video-no-drag"
|
||||
onMouseDown={startResize}
|
||||
onTouchStart={startResize}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: '-12px',
|
||||
right: '-12px',
|
||||
width: '22px',
|
||||
height: '22px',
|
||||
background: 'transparent',
|
||||
borderBottom: '2px solid white',
|
||||
borderRight: '2px solid white',
|
||||
borderRadius: '0 0 6px 0',
|
||||
cursor: 'nwse-resize',
|
||||
zIndex: 6,
|
||||
touchAction: 'none',
|
||||
boxShadow: '0 0 6px rgba(0,0,0,0.35)',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Error message below video - doesn't block controls */}
|
||||
|
|
@ -703,6 +828,21 @@ export default function FloatingVideo() {
|
|||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Resize handles */}
|
||||
{resizeHandles.map((handle) => (
|
||||
<Box
|
||||
key={handle.id}
|
||||
className="floating-video-no-drag"
|
||||
onMouseDown={(event) => startResize(event, handle)}
|
||||
onTouchStart={(event) => startResize(event, handle)}
|
||||
style={{
|
||||
...resizeHandleBaseStyle,
|
||||
...handle.style,
|
||||
cursor: handle.cursor,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Draggable>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue