Gestionado todo el frontend para domain driven design
feat: Implement LlamadorAPI component for API interaction with dynamic request handling feat: Create MetodoSelect component for selecting HTTP methods with visual feedback feat: Add VisualizacionesRandom component to display various charts using ECharts feat: Develop custom 404 Error page with holographic shader effect feat: Create HoloShader component for dynamic background effects on 404 page style: Add CSS styles for Appshell layout and navigation feat: Build Appshell component to manage application layout and navigation feat: Add ColorSchemeToggle component for switching between light and dark themes feat: Create Plantilla component as a template for future pages style: Define styles for Welcome component feat: Implement Welcome component with introductory text and links feat: Develop HomePage component to serve as the main entry point of the application feat: Create Biblioteca component for managing notes and libraries with rich text editor feat: Add Editor_Test component for testing rich text editor functionality style: Define styles for the rich text editor in Biblioteca
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
cd ./frontend
|
||||||
|
npm run dev
|
||||||
|
cd ..
|
||||||
|
|
||||||
Generated
+7
@@ -8,6 +8,7 @@
|
|||||||
"name": "mantine-vite-template",
|
"name": "mantine-vite-template",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cycjimmy/jsmpeg-player": "^6.1.2",
|
||||||
"@mantine/core": "^8.0.1",
|
"@mantine/core": "^8.0.1",
|
||||||
"@mantine/hooks": "^8.0.1",
|
"@mantine/hooks": "^8.0.1",
|
||||||
"@mantine/tiptap": "^8.0.1",
|
"@mantine/tiptap": "^8.0.1",
|
||||||
@@ -548,6 +549,12 @@
|
|||||||
"@csstools/css-tokenizer": "^3.0.3"
|
"@csstools/css-tokenizer": "^3.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@cycjimmy/jsmpeg-player": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-U9DBDe5fxHmbwQww9rFxMLNI2Wlg7DhPzI7AVFpq8GehiUP7+NwuMPXpP4zAd52sgkxtOqOeMjgE5g0ZLnQZ0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@dimforge/rapier3d-compat": {
|
"node_modules/@dimforge/rapier3d-compat": {
|
||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"storybook:build": "storybook build"
|
"storybook:build": "storybook build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cycjimmy/jsmpeg-player": "^6.1.2",
|
||||||
"@mantine/core": "^8.0.1",
|
"@mantine/core": "^8.0.1",
|
||||||
"@mantine/hooks": "^8.0.1",
|
"@mantine/hooks": "^8.0.1",
|
||||||
"@mantine/tiptap": "^8.0.1",
|
"@mantine/tiptap": "^8.0.1",
|
||||||
|
|||||||
+10
-10
@@ -1,12 +1,12 @@
|
|||||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||||
import { HomePage } from './pages/Home.page';
|
import { HomePage } from './domains/Home/Home.page';
|
||||||
import { Consulta_API } from './pages/Consulta_api';
|
import { Consulta_API } from './domains/Experiments/Consulta_api';
|
||||||
import { Error_404 } from './pages/404'; // Ajusta si está en otra carpeta
|
import { Error_404 } from './domains/FitzStudio/404/404'; // Ajusta si está en otra carpeta
|
||||||
import { Grid_Dashboard } from './pages/Grid_dashboard'; // Ajusta si está en otra carpeta
|
import { Grid_Dashboard } from './domains/Experiments/Grid_dashboard'; // Ajusta si está en otra carpeta
|
||||||
import { Biblioteca } from './pages/Biblioteca';
|
import { Biblioteca } from './domains/TextEditor/Biblioteca';
|
||||||
import { VisualizacionesRandom } from './pages/Visualizaciones_Random';
|
import { VisualizacionesRandom } from './domains/Experiments/Visualizaciones_Random';
|
||||||
import { Camara_noir } from './pages/Camaras_noir';
|
import { Camara_noir } from './domains/CamaraNoir/Camaras_noir';
|
||||||
import EditorTest from "./pages/Editor_Test"
|
import EditorTest from "./domains/TextEditor/Editor_Test"
|
||||||
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
@@ -33,7 +33,7 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
// Camara
|
// CamaraNoir
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/camara/principal',
|
path: '/camara/principal',
|
||||||
@@ -60,7 +60,7 @@ const router = createBrowserRouter([
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// FitzStudio Pages -------------------------------------------------------
|
||||||
// Error 404
|
// Error 404
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
// Archivo: components/CanvasDisplay.tsx
|
|
||||||
import { Box } from '@mantine/core';
|
|
||||||
import { RefObject } from 'react';
|
|
||||||
|
|
||||||
interface CanvasDisplayProps {
|
|
||||||
canvasRef: RefObject<HTMLCanvasElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CanvasDisplay({ canvasRef }: CanvasDisplayProps) {
|
|
||||||
return (
|
|
||||||
<Box style={{ position: 'relative', width: '100%', height: 480 }}>
|
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
width={640}
|
|
||||||
height={480}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: 8,
|
|
||||||
backgroundColor: '#000',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// Archivo: components/CaptureGrid.tsx
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { SimpleGrid } from '@mantine/core';
|
|
||||||
import { FrameCard } from './FrameCard';
|
|
||||||
|
|
||||||
interface CaptureGridProps {
|
|
||||||
totalSlots: number;
|
|
||||||
capturas: string[][];
|
|
||||||
setCapturas: (value: string[][]) => void;
|
|
||||||
frameIndices: number[];
|
|
||||||
setFrameIndices: (value: number[]) => void;
|
|
||||||
fijados: boolean[];
|
|
||||||
setFijados: (value: boolean[]) => void;
|
|
||||||
frameTemporal: number | null;
|
|
||||||
onScroll: (e: React.WheelEvent, index: number) => void;
|
|
||||||
onSliderChange: (val: number) => void;
|
|
||||||
onSliderEnd: (index: number, val: number) => void;
|
|
||||||
numColumnas: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CaptureGrid({
|
|
||||||
totalSlots,
|
|
||||||
capturas,
|
|
||||||
setCapturas,
|
|
||||||
frameIndices,
|
|
||||||
setFrameIndices,
|
|
||||||
fijados,
|
|
||||||
setFijados,
|
|
||||||
frameTemporal,
|
|
||||||
onScroll,
|
|
||||||
onSliderChange,
|
|
||||||
onSliderEnd,
|
|
||||||
numColumnas,
|
|
||||||
}: CaptureGridProps) {
|
|
||||||
const handleImageClick = useCallback((index: number, dataUrl: string) => {
|
|
||||||
const nuevasCapturas = [...capturas];
|
|
||||||
nuevasCapturas[index] = [dataUrl];
|
|
||||||
setCapturas(nuevasCapturas);
|
|
||||||
|
|
||||||
const nuevosFijados = [...fijados];
|
|
||||||
nuevosFijados[index] = true;
|
|
||||||
setFijados(nuevosFijados);
|
|
||||||
|
|
||||||
const nuevosIndices = [...frameIndices];
|
|
||||||
nuevosIndices[index] = 0;
|
|
||||||
setFrameIndices(nuevosIndices);
|
|
||||||
}, [capturas, fijados, frameIndices, setCapturas, setFijados, setFrameIndices]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SimpleGrid cols={numColumnas} style={{ flex: 1, columnGap: 12, rowGap: 4 }}>
|
|
||||||
{Array.from({ length: totalSlots }).map((_, i) => {
|
|
||||||
const frames = capturas[i] ?? [];
|
|
||||||
const currentIndex =
|
|
||||||
!fijados[i] && frameTemporal !== null && frames.length > 0
|
|
||||||
? Math.max(0, Math.min(frames.length - 1, frameTemporal))
|
|
||||||
: frameIndices[i];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FrameCard
|
|
||||||
key={i}
|
|
||||||
index={i}
|
|
||||||
frames={frames}
|
|
||||||
currentIndex={currentIndex}
|
|
||||||
fijado={fijados[i]}
|
|
||||||
onScroll={onScroll}
|
|
||||||
onSliderChange={onSliderChange}
|
|
||||||
onSliderEnd={onSliderEnd}
|
|
||||||
onImageClick={handleImageClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</SimpleGrid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// Archivo: components/ControlPanel.tsx
|
|
||||||
import { Button, Group } from '@mantine/core';
|
|
||||||
|
|
||||||
interface ControlPanelProps {
|
|
||||||
grabando: boolean;
|
|
||||||
onAlternar: () => void;
|
|
||||||
onLimpiar: () => void;
|
|
||||||
onDesfijar: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ControlPanel({ grabando, onAlternar, onLimpiar, onDesfijar }: ControlPanelProps) {
|
|
||||||
return (
|
|
||||||
<Group gap="sm">
|
|
||||||
<Button onClick={onAlternar} variant="light" color={grabando ? 'orange' : 'blue'}>
|
|
||||||
{grabando ? 'Grabando... (Presiona Espacio)' : 'Iniciar grabación'}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onLimpiar} variant="filled" color="red">
|
|
||||||
Eliminar imágenes
|
|
||||||
</Button>
|
|
||||||
<Button onClick={onDesfijar} variant="default" color="gray">
|
|
||||||
Desfijar todos (D)
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
// Archivo: components/FrameCard.tsx
|
|
||||||
import { Box, HoverCard, Image, Slider, Stack, Text } from '@mantine/core';
|
|
||||||
|
|
||||||
interface FrameCardProps {
|
|
||||||
index: number;
|
|
||||||
frames: string[];
|
|
||||||
currentIndex: number;
|
|
||||||
fijado: boolean;
|
|
||||||
onScroll: (e: React.WheelEvent, index: number) => void;
|
|
||||||
onSliderChange: (val: number) => void;
|
|
||||||
onSliderEnd: (index: number, val: number) => void;
|
|
||||||
onImageClick?: (index: number, dataUrl: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FrameCard({
|
|
||||||
index,
|
|
||||||
frames,
|
|
||||||
currentIndex,
|
|
||||||
fijado,
|
|
||||||
onScroll,
|
|
||||||
onSliderChange,
|
|
||||||
onSliderEnd,
|
|
||||||
onImageClick,
|
|
||||||
}: FrameCardProps) {
|
|
||||||
const borderColor = fijado ? '#4caf50' : '#ccc';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HoverCard key={index} width={700} shadow="md" position="top" withArrow>
|
|
||||||
<HoverCard.Target>
|
|
||||||
<Box
|
|
||||||
style={{
|
|
||||||
width: 160,
|
|
||||||
height: 120,
|
|
||||||
borderRadius: 8,
|
|
||||||
border: `2px solid ${borderColor}`,
|
|
||||||
overflow: 'hidden',
|
|
||||||
position: 'relative',
|
|
||||||
background: '#f1f1f1',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{frames.length > 0 && currentIndex < frames.length && (
|
|
||||||
<>
|
|
||||||
<Image
|
|
||||||
src={frames[currentIndex]}
|
|
||||||
alt={`Captura ${index}`}
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
fit="cover"
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
size="xs"
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 4,
|
|
||||||
right: 6,
|
|
||||||
backgroundColor: 'rgba(0,0,0,0.6)',
|
|
||||||
color: 'white',
|
|
||||||
borderRadius: 4,
|
|
||||||
padding: '2px 4px',
|
|
||||||
fontSize: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{currentIndex + 1} / {frames.length}
|
|
||||||
</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</HoverCard.Target>
|
|
||||||
|
|
||||||
<HoverCard.Dropdown p="xs" onWheel={(e) => onScroll(e, index)}>
|
|
||||||
{frames.length > 0 && currentIndex < frames.length && (
|
|
||||||
<Stack gap={6}>
|
|
||||||
<Image
|
|
||||||
src={frames[currentIndex]}
|
|
||||||
alt={`Vista ampliada ${index}`}
|
|
||||||
style={{ width: '100%', height: 'auto', maxWidth: '100%', cursor: 'zoom-in' }}
|
|
||||||
fit="contain"
|
|
||||||
onClick={async (e) => {
|
|
||||||
const imgElement = e.currentTarget;
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = frames[currentIndex];
|
|
||||||
await img.decode();
|
|
||||||
|
|
||||||
const rect = imgElement.getBoundingClientRect();
|
|
||||||
const clickX = e.clientX - rect.left;
|
|
||||||
const clickY = e.clientY - rect.top;
|
|
||||||
const ratioX = clickX / rect.width;
|
|
||||||
const ratioY = clickY / rect.height;
|
|
||||||
|
|
||||||
const zoomFactor = 2;
|
|
||||||
const cropWidth = img.width / zoomFactor;
|
|
||||||
const cropHeight = img.height / zoomFactor;
|
|
||||||
|
|
||||||
const centerX = img.width * ratioX;
|
|
||||||
const centerY = img.height * ratioY;
|
|
||||||
|
|
||||||
const x = Math.max(0, Math.min(img.width - cropWidth, centerX - cropWidth / 2));
|
|
||||||
const y = Math.max(0, Math.min(img.height - cropHeight, centerY - cropHeight / 2));
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = cropWidth;
|
|
||||||
canvas.height = cropHeight;
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (ctx) {
|
|
||||||
ctx.drawImage(img, x, y, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
||||||
const dataUrl = canvas.toDataURL('image/jpeg');
|
|
||||||
onImageClick?.(index, dataUrl);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Slider
|
|
||||||
value={currentIndex}
|
|
||||||
onChange={(val) => onSliderChange(val)}
|
|
||||||
onChangeEnd={(val) => onSliderEnd(index, val)}
|
|
||||||
min={0}
|
|
||||||
max={frames.length - 1}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</HoverCard.Dropdown>
|
|
||||||
</HoverCard>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// Archivo: components/GridConfigPanel.tsx
|
|
||||||
import { NumberInput, Stack, Text } from '@mantine/core';
|
|
||||||
|
|
||||||
interface GridConfigPanelProps {
|
|
||||||
numFilas: number;
|
|
||||||
setNumFilas: (val: number) => void;
|
|
||||||
numColumnas: number;
|
|
||||||
setNumColumnas: (val: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GridConfigPanel({ numFilas, setNumFilas, numColumnas, setNumColumnas }: GridConfigPanelProps) {
|
|
||||||
return (
|
|
||||||
<Stack align="center" gap="xs" ml="md">
|
|
||||||
<Text size="sm">Cartas</Text>
|
|
||||||
<NumberInput
|
|
||||||
value={numFilas}
|
|
||||||
onChange={(val) => setNumFilas(Number(val))}
|
|
||||||
min={1}
|
|
||||||
max={5}
|
|
||||||
step={1}
|
|
||||||
size="xs"
|
|
||||||
style={{ width: 60 }}
|
|
||||||
/>
|
|
||||||
<Text size="sm">Jugadores</Text>
|
|
||||||
<NumberInput
|
|
||||||
value={numColumnas}
|
|
||||||
onChange={(val) => setNumColumnas(Number(val))}
|
|
||||||
min={1}
|
|
||||||
max={11}
|
|
||||||
step={1}
|
|
||||||
size="xs"
|
|
||||||
style={{ width: 60 }}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export * from './CanvasDisplay';
|
|
||||||
export * from './ControlPanel';
|
|
||||||
export * from './GridConfigPanel';
|
|
||||||
export * from './CaptureGrid';
|
|
||||||
export * from './FrameCard';
|
|
||||||
export * from './useCamaraNoir';
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
export function useCamaraNoir() {
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
||||||
const bufferPrevioRef = useRef<string[]>([]);
|
|
||||||
const bufferGrabacionRef = useRef<string[]>([]);
|
|
||||||
const bufferAcumuladoRef = useRef<string[]>([]);
|
|
||||||
const pregrabacionActivaRef = useRef(true);
|
|
||||||
const primeraGrabacionRealizadaRef = useRef(false);
|
|
||||||
|
|
||||||
const [bufferGlobal, setBufferGlobal] = useState<string[]>([]);
|
|
||||||
const [intervaloId, setIntervaloId] = useState<ReturnType<typeof setInterval> | null>(null);
|
|
||||||
const [grabando, setGrabando] = useState(false);
|
|
||||||
|
|
||||||
const [capturas, setCapturas] = useState<string[][]>([]);
|
|
||||||
const [frameIndices, setFrameIndices] = useState<number[]>([]);
|
|
||||||
const [fijados, setFijados] = useState<boolean[]>([]);
|
|
||||||
const [frameTemporal, setFrameTemporal] = useState<number | null>(null);
|
|
||||||
|
|
||||||
const [numFilas, setNumFilas] = useState(2);
|
|
||||||
const [numColumnas, setNumColumnas] = useState(10);
|
|
||||||
|
|
||||||
const DELAY_ENTRE_FRAMES_MS = 10;
|
|
||||||
const SEGUNDOS_PRE_GRABACION = 5;
|
|
||||||
const FPS = 1000 / DELAY_ENTRE_FRAMES_MS;
|
|
||||||
const FRAMES_PRE_GRABACION = Math.floor(FPS * SEGUNDOS_PRE_GRABACION);
|
|
||||||
const totalSlots = numFilas * numColumnas;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const id = setInterval(() => {
|
|
||||||
if (!pregrabacionActivaRef.current) return;
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
if (!canvas) return;
|
|
||||||
const frame = canvas.toDataURL('image/jpeg');
|
|
||||||
bufferPrevioRef.current.push(frame);
|
|
||||||
if (bufferPrevioRef.current.length > FRAMES_PRE_GRABACION) {
|
|
||||||
bufferPrevioRef.current.shift();
|
|
||||||
}
|
|
||||||
}, DELAY_ENTRE_FRAMES_MS);
|
|
||||||
|
|
||||||
return () => clearInterval(id);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const desfijarTodos = () => {
|
|
||||||
setFijados(Array(totalSlots).fill(false));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
if (!canvas) return;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!ctx) return;
|
|
||||||
|
|
||||||
const socket = new WebSocket('ws://10.8.0.9:8000/ws');
|
|
||||||
socket.binaryType = 'blob';
|
|
||||||
|
|
||||||
socket.onmessage = async (event) => {
|
|
||||||
if (event.data instanceof Blob) {
|
|
||||||
const imgBitmap = await createImageBitmap(event.data);
|
|
||||||
ctx.drawImage(imgBitmap, 0, 0, canvas.width, canvas.height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
socket.onerror = (e) => console.error('WebSocket error:', e);
|
|
||||||
socket.onclose = () => console.warn('WebSocket cerrado');
|
|
||||||
|
|
||||||
return () => socket.close();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
|
||||||
if (event.code === 'Space') {
|
|
||||||
event.preventDefault();
|
|
||||||
alternarGrabacion();
|
|
||||||
} else if (event.key.toLowerCase() === 'f') {
|
|
||||||
event.preventDefault();
|
|
||||||
limpiarCapturas();
|
|
||||||
} else if (event.key.toLowerCase() === 'd') {
|
|
||||||
event.preventDefault();
|
|
||||||
desfijarTodos();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
}, [grabando, bufferGlobal, totalSlots]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const total = numFilas * numColumnas;
|
|
||||||
const nuevoCapturas = Array(total)
|
|
||||||
.fill(null)
|
|
||||||
.map((_, i) => capturas[i] ?? bufferGlobal);
|
|
||||||
const nuevoIndices = Array(total)
|
|
||||||
.fill(null)
|
|
||||||
.map((_, i) => frameIndices[i] ?? (frameTemporal ?? Math.floor(bufferGlobal.length / 2)));
|
|
||||||
const nuevosFijados = Array(total)
|
|
||||||
.fill(null)
|
|
||||||
.map((_, i) => fijados[i] ?? false);
|
|
||||||
|
|
||||||
setCapturas(nuevoCapturas);
|
|
||||||
setFrameIndices(nuevoIndices);
|
|
||||||
setFijados(nuevosFijados);
|
|
||||||
}, [numFilas, numColumnas]);
|
|
||||||
|
|
||||||
const iniciarGrabacion = () => {
|
|
||||||
if (grabando) return;
|
|
||||||
setGrabando(true);
|
|
||||||
bufferGrabacionRef.current = [];
|
|
||||||
|
|
||||||
if (!primeraGrabacionRealizadaRef.current) {
|
|
||||||
pregrabacionActivaRef.current = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = setInterval(() => {
|
|
||||||
const frame = capturarFrame();
|
|
||||||
if (frame) {
|
|
||||||
bufferGrabacionRef.current.push(frame);
|
|
||||||
}
|
|
||||||
}, DELAY_ENTRE_FRAMES_MS);
|
|
||||||
|
|
||||||
setIntervaloId(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const capturarFrame = (): string | null => {
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
if (!canvas) return null;
|
|
||||||
return canvas.toDataURL('image/jpeg');
|
|
||||||
};
|
|
||||||
|
|
||||||
const detenerGrabacion = () => {
|
|
||||||
if (!grabando) return;
|
|
||||||
setGrabando(false);
|
|
||||||
if (intervaloId) clearInterval(intervaloId);
|
|
||||||
|
|
||||||
const nuevaSesion = primeraGrabacionRealizadaRef.current
|
|
||||||
? [...bufferGrabacionRef.current] // solo frames nuevos
|
|
||||||
: [...bufferPrevioRef.current, ...bufferGrabacionRef.current];
|
|
||||||
|
|
||||||
bufferAcumuladoRef.current.push(...nuevaSesion);
|
|
||||||
primeraGrabacionRealizadaRef.current = true;
|
|
||||||
|
|
||||||
setBufferGlobal([...bufferAcumuladoRef.current]);
|
|
||||||
setCapturas(Array(totalSlots).fill([...bufferAcumuladoRef.current]));
|
|
||||||
const frameInicial = Math.floor(bufferAcumuladoRef.current.length / 2);
|
|
||||||
setFrameIndices(Array(totalSlots).fill(frameInicial));
|
|
||||||
setFijados(Array(totalSlots).fill(false));
|
|
||||||
setFrameTemporal(frameInicial);
|
|
||||||
};
|
|
||||||
|
|
||||||
const alternarGrabacion = () => {
|
|
||||||
grabando ? detenerGrabacion() : iniciarGrabacion();
|
|
||||||
};
|
|
||||||
|
|
||||||
const limpiarCapturas = () => {
|
|
||||||
setCapturas([]);
|
|
||||||
setFrameIndices([]);
|
|
||||||
setFijados([]);
|
|
||||||
setBufferGlobal([]);
|
|
||||||
bufferGrabacionRef.current = [];
|
|
||||||
bufferPrevioRef.current = [];
|
|
||||||
bufferAcumuladoRef.current = [];
|
|
||||||
primeraGrabacionRealizadaRef.current = false;
|
|
||||||
pregrabacionActivaRef.current = true;
|
|
||||||
setFrameTemporal(null);
|
|
||||||
if (intervaloId) clearInterval(intervaloId);
|
|
||||||
setGrabando(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const moverFrame = (capturaIndex: number, nuevoIndice: number) => {
|
|
||||||
setFrameIndices((prev) => {
|
|
||||||
const nuevos = [...prev];
|
|
||||||
const max = capturas[capturaIndex]?.length - 1 ?? 0;
|
|
||||||
nuevos[capturaIndex] = Math.max(0, Math.min(max, nuevoIndice));
|
|
||||||
return nuevos;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const moverFrameYFijar = (capturaIndex: number, nuevoIndice: number) => {
|
|
||||||
moverFrame(capturaIndex, nuevoIndice);
|
|
||||||
setFijados((prev) => {
|
|
||||||
const actualizados = [...prev];
|
|
||||||
actualizados[capturaIndex] = true;
|
|
||||||
return actualizados;
|
|
||||||
});
|
|
||||||
setFrameTemporal(nuevoIndice);
|
|
||||||
};
|
|
||||||
|
|
||||||
const manejarScrollEnSlider = (e: React.WheelEvent, index: number) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!fijados[index]) return;
|
|
||||||
moverFrame(index, frameIndices[index] + (e.deltaY > 0 ? 1 : -1));
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
canvasRef,
|
|
||||||
bufferGlobal,
|
|
||||||
grabando,
|
|
||||||
capturas,
|
|
||||||
frameIndices,
|
|
||||||
fijados,
|
|
||||||
frameTemporal,
|
|
||||||
numFilas,
|
|
||||||
setNumFilas,
|
|
||||||
numColumnas,
|
|
||||||
setNumColumnas,
|
|
||||||
alternarGrabacion,
|
|
||||||
limpiarCapturas,
|
|
||||||
desfijarTodos,
|
|
||||||
manejarScrollEnSlider,
|
|
||||||
moverFrameYFijar,
|
|
||||||
setFrameTemporal,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { AppShellWithMenu } from '../FitzStudio/Appshell/Appshell';
|
||||||
|
import { Card, Text, Center, Container } from '@mantine/core';
|
||||||
|
|
||||||
|
export function Camara_noir() {
|
||||||
|
return (
|
||||||
|
<AppShellWithMenu>
|
||||||
|
<Container
|
||||||
|
size="lg"
|
||||||
|
style={{
|
||||||
|
height: '100vh', // Ocupa toda la altura de la ventana
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card shadow="sm" padding="xl" radius="md" withBorder>
|
||||||
|
<Text size="lg">
|
||||||
|
Aquí irá la cámara Noir
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" color="dimmed">
|
||||||
|
Este espacio está reservado para la funcionalidad de la cámara Noir.
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
</AppShellWithMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import { LlamadorAPI } from '../components/LlamadorAPI';
|
import { LlamadorAPI } from './LlamadorAPI';
|
||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
import { AppShellWithMenu } from '../FitzStudio/Appshell/Appshell';
|
||||||
|
|
||||||
|
|
||||||
export function Consulta_API() {
|
export function Consulta_API() {
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { Grid } from '@mantine/core';
|
import { Grid } from '@mantine/core';
|
||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
import { AppShellWithMenu } from '../FitzStudio/Appshell/Appshell';
|
||||||
import { GridDashboard } from '../components/Grid_dashboard';
|
import { GridDashboard } from './Grid_dashboard_component';
|
||||||
|
|
||||||
export function Grid_Dashboard() {
|
export function Grid_Dashboard() {
|
||||||
return (
|
return (
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { Select, Group } from '@mantine/core';
|
import { Select, Group } from '@mantine/core';
|
||||||
import { IconCheck } from '../assets/icons';
|
import { IconCheck } from '../../assets/icons';
|
||||||
|
|
||||||
interface MetodoSelectProps {
|
interface MetodoSelectProps {
|
||||||
metodo: string;
|
metodo: string;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
import { AppShellWithMenu } from '../FitzStudio/Appshell/Appshell';
|
||||||
import { Card, Grid, Title, Loader } from '@mantine/core';
|
import { Card, Grid, Title, Loader } from '@mantine/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import ReactECharts from 'echarts-for-react';
|
import ReactECharts from 'echarts-for-react';
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Box, Title, Text, Button, Group, Stack, Image, Center } from '@mantine/core';
|
import { Box, Title, Text, Button, Group, Stack, Image, Center } from '@mantine/core';
|
||||||
import { useMantineTheme } from '@mantine/core';
|
import { useMantineTheme } from '@mantine/core';
|
||||||
import { IconArrowLeft } from '../assets/icons';
|
import { IconArrowLeft } from '../../../assets/icons';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { MantineCardWithShader } from '../components/HoloShader'; // Ajusta ruta si es necesario
|
import { MantineCardWithShader } from './HoloShader_404'; // Ajusta ruta si es necesario
|
||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
import { AppShellWithMenu } from '../../FitzStudio/Appshell/Appshell';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function Error_404() {
|
export function Error_404() {
|
||||||
+3
-3
@@ -11,9 +11,9 @@ import { useDisclosure, useMediaQuery } from '@mantine/hooks';
|
|||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { default as LogoIcon } from '../../assets/icons/favicon';
|
import { default as LogoIcon } from '../../../assets/icons/favicon';
|
||||||
import { mainLinksdata } from '../../data/navigationsLinks_1';
|
import { mainLinksdata } from '../../../data/navigationsLinks_1';
|
||||||
import { submenuLinks } from '../../data/submenuLinks_1';
|
import { submenuLinks } from '../../../data/submenuLinks_1';
|
||||||
|
|
||||||
import classes from './Appshell.module.css';
|
import classes from './Appshell.module.css';
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
import { AppShellWithMenu } from '../FitzStudio/Appshell/Appshell';
|
||||||
|
|
||||||
|
|
||||||
export function Plantilla() {
|
export function Plantilla() {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
import { AppShellWithMenu } from '../FitzStudio/Appshell/Appshell';
|
||||||
import { Welcome } from '@/components/Welcome/Welcome';
|
import { Welcome } from '@/domains/FitzStudio/Welcome/Welcome';
|
||||||
import { ColorSchemeToggle } from '@/components/ColorSchemeToggle/ColorSchemeToggle';
|
import { ColorSchemeToggle } from '@/domains/FitzStudio/ColorSchemeToggle/ColorSchemeToggle';
|
||||||
|
|
||||||
|
|
||||||
export function HomePage() {
|
export function HomePage() {
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Modal,
|
Modal,
|
||||||
Box
|
Box
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
import { AppShellWithMenu } from '../FitzStudio/Appshell/Appshell';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import { RichTextEditor } from '@mantine/tiptap';
|
import { RichTextEditor } from '@mantine/tiptap';
|
||||||
@@ -21,7 +21,7 @@ import '@mantine/tiptap/styles.css';
|
|||||||
import TurndownService from 'turndown';
|
import TurndownService from 'turndown';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
|
|
||||||
import '../components/Editor_biblioteca.css';
|
import './Editor_biblioteca.css';
|
||||||
|
|
||||||
type Nota = {
|
type Nota = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function Prueba_1() {
|
|
||||||
return (
|
|
||||||
<AppShellWithMenu>
|
|
||||||
|
|
||||||
</AppShellWithMenu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { AppShellWithMenu } from '../components/Appshell/Appshell';
|
|
||||||
import { Stack, Box } from '@mantine/core';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CanvasDisplay,
|
|
||||||
ControlPanel,
|
|
||||||
CaptureGrid,
|
|
||||||
GridConfigPanel,
|
|
||||||
useCamaraNoir
|
|
||||||
} from '../components/Camara_noir';
|
|
||||||
|
|
||||||
export function Camara_noir() {
|
|
||||||
const camara = useCamaraNoir();
|
|
||||||
const totalSlots = camara.numFilas * camara.numColumnas;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppShellWithMenu>
|
|
||||||
<Stack p="md" gap="md">
|
|
||||||
|
|
||||||
{/* Contenedor para botones encima del video */}
|
|
||||||
<Box style={{ position: 'relative', width: '100%' }}>
|
|
||||||
<ControlPanel
|
|
||||||
grabando={camara.grabando}
|
|
||||||
onAlternar={camara.alternarGrabacion}
|
|
||||||
onLimpiar={camara.limpiarCapturas}
|
|
||||||
onDesfijar={camara.desfijarTodos}
|
|
||||||
/>
|
|
||||||
<Box style={{ marginTop: 8 }}>
|
|
||||||
<CanvasDisplay canvasRef={camara.canvasRef} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Grilla de capturas + configurador lateral */}
|
|
||||||
<Box style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
|
|
||||||
<CaptureGrid
|
|
||||||
totalSlots={totalSlots}
|
|
||||||
capturas={camara.capturas}
|
|
||||||
frameIndices={camara.frameIndices}
|
|
||||||
fijados={camara.fijados}
|
|
||||||
frameTemporal={camara.frameTemporal}
|
|
||||||
onScroll={camara.manejarScrollEnSlider}
|
|
||||||
onSliderChange={camara.setFrameTemporal}
|
|
||||||
onSliderEnd={camara.moverFrameYFijar}
|
|
||||||
numColumnas={camara.numColumnas}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<GridConfigPanel
|
|
||||||
numFilas={camara.numFilas}
|
|
||||||
setNumFilas={camara.setNumFilas}
|
|
||||||
numColumnas={camara.numColumnas}
|
|
||||||
setNumColumnas={camara.setNumColumnas}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Stack>
|
|
||||||
</AppShellWithMenu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -222,6 +222,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz"
|
resolved "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz"
|
||||||
integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==
|
integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==
|
||||||
|
|
||||||
|
"@cycjimmy/jsmpeg-player@^6.1.2":
|
||||||
|
version "6.1.2"
|
||||||
|
resolved "https://registry.npmjs.org/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.1.2.tgz"
|
||||||
|
integrity sha512-U9DBDe5fxHmbwQww9rFxMLNI2Wlg7DhPzI7AVFpq8GehiUP7+NwuMPXpP4zAd52sgkxtOqOeMjgE5g0ZLnQZ0w==
|
||||||
|
|
||||||
"@dimforge/rapier3d-compat@^0.12.0":
|
"@dimforge/rapier3d-compat@^0.12.0":
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz"
|
resolved "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user