Esquema de interfaz de tabla para movil

master
Araozu 2022-10-13 18:29:19 -05:00
parent 76f723a097
commit 10749c5900
12 changed files with 304 additions and 157 deletions

View File

@ -42,7 +42,7 @@ type Output = {
class MapaCeldas {
// Almacena referencias a input
private mapa: Map<number, Map<number, Input>> = new Map();
private mapa: Map<number, Map<number, null>> = new Map();
private disponible(nroFila: number, nroColumna: number): boolean {
if (!this.mapa.has(nroFila)) return true;
@ -52,9 +52,9 @@ class MapaCeldas {
return fila.has(nroColumna) === false;
}
private obtenerFilaOCrear(nro: number): Map<number, Input> {
private obtenerFilaOCrear(nro: number): Map<number, null> {
if (!this.mapa.has(nro)) {
const m = new Map<number, Input>();
const m = new Map<number, null>();
this.mapa.set(nro, m);
return m;
}
@ -63,7 +63,7 @@ class MapaCeldas {
}
// Devuelve el offset
public solicitar(inicio: number, cantidad: number, input: Input): number {
public solicitar(inicio: number, cantidad: number): number {
const filas = [];
for (let i = 0; i < cantidad; i += 1) filas.push(inicio + i);
@ -80,7 +80,7 @@ class MapaCeldas {
// Crear estas celdas y almacenar
filas.forEach((nroFila) => {
const fila = this.obtenerFilaOCrear(nroFila);
fila.set(offsetActual, input);
fila.set(offsetActual, null);
});
// Devolver nro de offset
@ -112,7 +112,7 @@ function generarMapaCeldas(entrada: Readonly<Array<Input>>): Array<Output> {
// Obtener los offsets de cada curso
for (const input of entrada) {
const offset = mapa.solicitar(input.horaInicio, input.nroHoras, input);
const offset = mapa.solicitar(input.horaInicio, input.nroHoras);
salida.push({
...input,
offset,
@ -174,29 +174,29 @@ describe("MapaCeldas", () => {
it("crea 1", () => {
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
const offset = mapa.solicitar(0, 2, input);
const offset = mapa.solicitar(0, 2);
expect(offset).toBe(0);
});
it("crea varios que no se solapan", () => {
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
let offset = mapa.solicitar(0, 2, input);
let offset = mapa.solicitar(0, 2);
expect(offset).toBe(0);
offset = mapa.solicitar(4, 3, input);
offset = mapa.solicitar(4, 3);
expect(offset).toBe(0);
offset = mapa.solicitar(7, 4, input);
offset = mapa.solicitar(7, 4);
expect(offset).toBe(0);
});
it("crea varios que se solapan", () => {
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
let offset = mapa.solicitar(0, 2, input);
let offset = mapa.solicitar(0, 2);
expect(offset).toBe(0);
offset = mapa.solicitar(0, 2, input);
offset = mapa.solicitar(0, 2);
expect(offset).toBe(1);
offset = mapa.solicitar(0, 4, input);
offset = mapa.solicitar(0, 4);
expect(offset).toBe(2);
});
@ -212,33 +212,33 @@ describe("MapaCeldas", () => {
*/
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
let offset = mapa.solicitar(0, 2, input);
let offset = mapa.solicitar(0, 2);
expect(offset).toBe(0);
offset = mapa.solicitar(1, 3, input);
offset = mapa.solicitar(1, 3);
expect(offset).toBe(1);
offset = mapa.solicitar(1, 4, input);
offset = mapa.solicitar(1, 4);
expect(offset).toBe(2);
offset = mapa.solicitar(2, 3, input);
offset = mapa.solicitar(2, 3);
expect(offset).toBe(0);
offset = mapa.solicitar(4, 2, input);
offset = mapa.solicitar(4, 2);
expect(offset).toBe(1);
});
it("genera offsets", () => {
const mapa = new MapaCeldas();
const input = {} as unknown as Input;
let offset = mapa.solicitar(0, 2, input);
let offset = mapa.solicitar(0, 2);
expect(offset).toBe(0);
let fraccion = mapa.generarFraccion(0, offset, 2);
expect(fraccion).toBe(1);
offset = mapa.solicitar(1, 3, input);
offset = mapa.solicitar(1, 3);
fraccion = mapa.generarFraccion(1, offset, 3);
expect(fraccion).toBe(2);
mapa.solicitar(1, 4, input);
mapa.solicitar(2, 3, input);
offset = mapa.solicitar(4, 2, input);
mapa.solicitar(1, 4);
mapa.solicitar(2, 3);
offset = mapa.solicitar(4, 2);
fraccion = mapa.generarFraccion(4, offset, 2);
expect(fraccion).toBe(3);
});

View File

@ -4,6 +4,7 @@ import { Editor } from "./Views/Editor";
import { useRouter } from "./Router";
import { Switch, Match, Show } from "solid-js";
import { Wallpaper } from "./Wallpaper";
import { SistemasMovil } from "./Views/SistemasMovil";
function App() {
const route = useRouter();
@ -21,6 +22,9 @@ function App() {
<Match when={route() === "/editor/"}>
<Editor />
</Match>
<Match when={route() === "/sistemas-movil/"}>
<SistemasMovil />
</Match>
<Match when={route() === "/sistemas/"}>
<Main />
</Match>

View File

@ -1,11 +1,11 @@
import { StyleSheet, css } from "aphrodite"
import { batch, createMemo, For } from "solid-js"
import { produce, SetStoreFunction } from "solid-js/store"
import { estilosGlobales } from "../Estilos"
import { Cursos, ListaCursosUsuario, DataProcesada, DatosGrupo } from "../types/DatosHorario"
import { Dia, dias, horas } from "../Store"
import { FilaTabla } from "./Tabla/FilaTabla"
import { TablaObserver } from "./TablaObserver"
import { StyleSheet, css } from "aphrodite";
import { batch, createMemo, For } from "solid-js";
import { produce, SetStoreFunction } from "solid-js/store";
import { estilosGlobales } from "../Estilos";
import { Cursos, ListaCursosUsuario, DataProcesada, DatosGrupo } from "../types/DatosHorario";
import { Dia, dias, horas } from "../Store";
import { FilaTabla } from "./Tabla/FilaTabla";
import { TablaObserver } from "./TablaObserver";
export const coloresBorde = Object.freeze([
"rgba(33,150,243,1)",
@ -13,22 +13,22 @@ export const coloresBorde = Object.freeze([
"rgba(236,64,122 ,1)",
"rgba(29,233,182 ,1)",
"rgba(244,67,54,1)",
])
]);
export const diaANum = (d: Dia) => {
switch (d) {
case "Lunes":
return 0
return 0;
case "Martes":
return 1
return 1;
case "Miercoles":
return 2
return 2;
case "Jueves":
return 3
return 3;
case "Viernes":
return 4
}
return 4;
}
};
const e = StyleSheet.create({
fila: {
@ -81,34 +81,34 @@ const e = StyleSheet.create({
celdaCursoTeoria: {
fontWeight: "bold",
},
})
});
type FnSetCursosUsuarios = SetStoreFunction<ListaCursosUsuario>;
const procesarAnio = (data: Cursos, anio: string, version: number, setCursosUsuarios: FnSetCursosUsuarios) => {
const obj: DataProcesada = {}
const obj: DataProcesada = {};
for (const [indiceCurso, curso] of Object.entries(data)) {
if (curso.oculto) continue
if (curso.oculto) continue;
const nombreAbreviado = curso.abreviado
const nombreAbreviado = curso.abreviado;
for (const [grupoStr, grupo] of Object.entries(curso.Teoria)) {
for (const hora of grupo.Horas) {
const dia = hora.substring(0, 2)
const horas = hora.substring(2, 4)
const minutos = hora.substr(4)
const dia = hora.substring(0, 2);
const horas = hora.substring(2, 4);
const minutos = hora.substr(4);
const horaCompleta = `${horas}:${minutos}`
const horaCompleta = `${horas}:${minutos}`;
const id = `${version}_${anio}_${nombreAbreviado}_T_${grupoStr}`
const id = `${version}_${anio}_${nombreAbreviado}_T_${grupoStr}`;
if (!(horaCompleta in obj)) {
obj[horaCompleta] = {}
obj[horaCompleta] = {};
}
if (!(dia in obj[horaCompleta])) {
obj[horaCompleta][dia] = []
obj[horaCompleta][dia] = [];
}
obj[horaCompleta][dia].push({
@ -118,37 +118,37 @@ const procesarAnio = (data: Cursos, anio: string, version: number, setCursosUsua
datosGrupo: grupo,
fnSeleccionar: () => {
setCursosUsuarios("cursos", Number(indiceCurso), "Teoria", produce<{ [p: string]: DatosGrupo }>((x) => {
const grupoActualSeleccionado = x[grupoStr].seleccionado
const grupoActualSeleccionado = x[grupoStr].seleccionado;
if (grupoActualSeleccionado) {
x[grupoStr].seleccionado = false
x[grupoStr].seleccionado = false;
} else {
for (const xKey in x) {
x[xKey].seleccionado = xKey === grupoStr
x[xKey].seleccionado = xKey === grupoStr;
}
}
}))
}));
},
})
});
}
}
for (const [grupoStr, grupo] of Object.entries(curso.Laboratorio ?? {})) {
for (const hora of grupo.Horas) {
const dia = hora.substring(0, 2)
const horas = hora.substring(2, 4)
const minutos = hora.substr(4)
const dia = hora.substring(0, 2);
const horas = hora.substring(2, 4);
const minutos = hora.substr(4);
const horaCompleta = `${horas}:${minutos}`
const horaCompleta = `${horas}:${minutos}`;
const id = `${version}_${anio}_${nombreAbreviado}_L_${grupoStr}`
const id = `${version}_${anio}_${nombreAbreviado}_L_${grupoStr}`;
if (!(horaCompleta in obj)) {
obj[horaCompleta] = {}
obj[horaCompleta] = {};
}
if (!(dia in obj[horaCompleta])) {
obj[horaCompleta][dia] = []
obj[horaCompleta][dia] = [];
}
obj[horaCompleta][dia].push({
@ -162,25 +162,25 @@ const procesarAnio = (data: Cursos, anio: string, version: number, setCursosUsua
Number(indiceCurso),
"Laboratorio",
produce<{ [p: string]: DatosGrupo }>((x) => {
const grupoActualSeleccionado = x[grupoStr].seleccionado
const grupoActualSeleccionado = x[grupoStr].seleccionado;
if (grupoActualSeleccionado) {
x[grupoStr].seleccionado = false
x[grupoStr].seleccionado = false;
} else {
for (const xKey in x) {
x[xKey].seleccionado = xKey === grupoStr
x[xKey].seleccionado = xKey === grupoStr;
}
}
}),
)
);
},
})
});
}
}
}
return obj
}
return obj;
};
interface Props {
data: Cursos,
@ -191,12 +191,12 @@ interface Props {
}
export function Tabla(props: Props) {
const anio = () => props.anio.substring(0, props.anio.indexOf(" "))
const data = createMemo(() => procesarAnio(props.data, anio(), props.version, props.setCursosUsuarios))
const anio = () => props.anio.substring(0, props.anio.indexOf(" "));
const data = createMemo(() => procesarAnio(props.data, anio(), props.version, props.setCursosUsuarios));
const celdas = createMemo(() => {
// Hace reaccionar a la reactividad de Solid
const d = data()
const d = data();
return (
<For each={horas}>
{(hora) => (
@ -207,8 +207,8 @@ export function Tabla(props: Props) {
/>
)}
</For>
)
})
);
});
return (
<div>
@ -223,5 +223,5 @@ export function Tabla(props: Props) {
</div>
{celdas()}
</div>
)
);
}

View File

@ -1,9 +1,9 @@
import { StyleSheet, css } from "aphrodite"
import { estilosGlobales } from "../../Estilos"
import { For, createSignal, createMemo, createEffect, onCleanup } from "solid-js"
import { Dia } from "../../Store"
import { DatosGrupo } from "../../types/DatosHorario"
import { TablaObserver } from "../TablaObserver"
import { StyleSheet, css } from "aphrodite";
import { estilosGlobales } from "../../Estilos";
import { For, createSignal, createMemo, createEffect, onCleanup } from "solid-js";
import { Dia } from "../../Store";
import { DatosGrupo } from "../../types/DatosHorario";
import { TablaObserver } from "../TablaObserver";
const e = StyleSheet.create({
celdaComun: {
@ -42,7 +42,7 @@ const e = StyleSheet.create({
celdaResaltadoSeleccionado: {
textDecoration: "underline",
},
})
});
const eColores = StyleSheet.create({
lunes: {
@ -62,7 +62,7 @@ const eColores = StyleSheet.create({
viernes: {
backgroundColor: "rgba(244,67,54,1)",
},
})
});
const clasesColores = {
Lunes: css(eColores.lunes),
@ -70,7 +70,7 @@ const clasesColores = {
Miercoles: css(eColores.miercoles),
Jueves: css(eColores.jueves),
Viernes: css(eColores.viernes),
}
};
interface DatosProps {
id: string,
@ -97,108 +97,108 @@ interface Props {
tablaObserver: TablaObserver,
}
const claseSeldaSeleccionada = css(e.celdaSeleccionado)
const claseSeldaSeleccionada = css(e.celdaSeleccionado);
function RenderFila(datos: DatosProps, props: Props) {
const id = datos.id
const txt = datos.txt
const esLab = datos.esLab
const fnSeleccionar = datos.fnSeleccionar
const id = datos.id;
const txt = datos.txt;
const esLab = datos.esLab;
const fnSeleccionar = datos.fnSeleccionar;
const estadoCeldaMemo = props.tablaObserver.registrarConId(id, datos.datosGrupo)
const estadoCeldaMemo = props.tablaObserver.registrarConId(id, datos.datosGrupo);
const [estabaResaltado, setEstabaResaltado] = createSignal(false)
const [estabaResaltado, setEstabaResaltado] = createSignal(false);
// Limpiar los memos, porque cuando se desmonta la celda esos memos quedan sin efecto
onCleanup(() => {
props.tablaObserver.limpiar(id)
})
props.tablaObserver.limpiar(id);
});
const clases = createMemo(
() => {
const clases = [
e.celdaCurso,
esLab ? e.celdaCursoLab : e.celdaCursoTeoria,
]
let adicional = ""
];
let adicional = "";
const estadoCelda = estadoCeldaMemo()
const estadoCelda = estadoCeldaMemo();
switch (estadoCelda) {
case "Normal": {
if (estabaResaltado()) {
props.fnDesresaltarFila()
setEstabaResaltado(false)
props.fnDesresaltarFila();
setEstabaResaltado(false);
}
break
break;
}
case "Oculto": {
if (estabaResaltado()) {
props.fnDesresaltarFila()
setEstabaResaltado(false)
props.fnDesresaltarFila();
setEstabaResaltado(false);
}
clases.push(e.celdaOculto)
break
clases.push(e.celdaOculto);
break;
}
case "Resaltado": {
props.fnResaltarFila()
setEstabaResaltado(true)
clases.push(e.celdaResaltado)
adicional = clasesColores[props.dia]
break
props.fnResaltarFila();
setEstabaResaltado(true);
clases.push(e.celdaResaltado);
adicional = clasesColores[props.dia];
break;
}
case "Seleccionado": {
if (estabaResaltado()) {
props.fnDesresaltarFila()
setEstabaResaltado(false)
props.fnDesresaltarFila();
setEstabaResaltado(false);
}
clases.push(e.celdaSeleccionado)
break
clases.push(e.celdaSeleccionado);
break;
}
case "ResaltadoOculto": {
props.fnResaltarFila()
setEstabaResaltado(true)
props.fnResaltarFila();
setEstabaResaltado(true);
clases.push(e.celdaResaltadoOculto)
adicional = clasesColores[props.dia]
break
clases.push(e.celdaResaltadoOculto);
adicional = clasesColores[props.dia];
break;
}
case "ResaltadoSeleccionado": {
props.fnResaltarFila()
setEstabaResaltado(true)
props.fnResaltarFila();
setEstabaResaltado(true);
clases.push(e.celdaResaltadoSeleccionado)
adicional = clasesColores[props.dia]
break
clases.push(e.celdaResaltadoSeleccionado);
adicional = clasesColores[props.dia];
break;
}
}
return `${css(...clases)} ${adicional}`
return `${css(...clases)} ${adicional}`;
},
undefined,
(x, y) => x === y,
)
);
return (
<button className={clases()}
onMouseEnter={() => {
props.tablaObserver.resaltar(id)
props.tablaObserver.resaltar(id);
}}
onMouseLeave={() => {
props.tablaObserver.quitarResaltado()
props.tablaObserver.quitarResaltado();
}}
onClick={fnSeleccionar}
>
{txt}
</button>
)
);
}
export function CeldaFila(props: Props) {
const datos = props.datos
const datos = props.datos;
return (
<div className={css(e.celdaComun, estilosGlobales.inlineBlock)}>
@ -206,5 +206,5 @@ export function CeldaFila(props: Props) {
{(datos) => RenderFila(datos, props)}
</For>
</div>
)
);
}

View File

@ -1,4 +1,4 @@
import { StyleSheet } from "aphrodite"
import { StyleSheet } from "aphrodite";
export const estilosGlobales = StyleSheet.create({
contenedor: {
@ -58,4 +58,4 @@ export const estilosGlobales = StyleSheet.create({
padding: "0.25rem 0.35rem",
borderRadius: "5px",
},
})
});

View File

@ -1,31 +1,31 @@
import { createSignal, JSX } from "solid-js"
import { createSignal, JSX } from "solid-js";
export const useRouter = (): () => string => {
let rutaPrevia = window.location.hash
let rutaPrevia = window.location.hash;
if (rutaPrevia === "") {
window.history.pushState({}, "Horarios UNSA", "#/")
rutaPrevia = "/"
window.history.pushState({}, "Horarios UNSA", "#/");
rutaPrevia = "/";
} else {
rutaPrevia = rutaPrevia.substr(1)
rutaPrevia = rutaPrevia.substr(1);
}
const [rutaActual, setRutaActual] = createSignal(rutaPrevia)
const [rutaActual, setRutaActual] = createSignal(rutaPrevia);
const fnEffect = () => {
const nuevaRuta = window.location.hash.substr(1)
setRutaActual(nuevaRuta)
}
const nuevaRuta = window.location.hash.substr(1);
setRutaActual(nuevaRuta);
};
window.addEventListener("hashchange", fnEffect)
window.addEventListener("hashchange", fnEffect);
return rutaActual
}
return rutaActual;
};
export function RouterLink(props: { to: string, className?: string, children: JSX.Element }) {
return (
<a className={props.className} href={`/#${props.to}`}>
{props.children}
</a>
)
);
}

View File

@ -55,6 +55,7 @@ function MobileIndex() {
const login = () => {
console.log((inputElement as HTMLInputElement).value);
window.location.href = "#/sistemas-movil/";
};
return (

View File

@ -1,22 +1,22 @@
import { BarraSuperior } from "../BarraSuperior"
import { ContenedorHorarios } from "../ContenedorHorarios/ContenedorHorarios"
import { Show, createSignal } from "solid-js"
import { css } from "aphrodite"
import { estilosGlobales } from "../Estilos"
import { Creditos } from "../Creditos"
import { Separador } from "../Separador"
import { BarraSuperior } from "../BarraSuperior";
import { ContenedorHorarios } from "../ContenedorHorarios/ContenedorHorarios";
import { Show, createSignal } from "solid-js";
import { css } from "aphrodite";
import { estilosGlobales } from "../Estilos";
import { Creditos } from "../Creditos";
import { Separador } from "../Separador";
export function Main() {
/// @ts-ignore
const soportaBackdropFilter = document.body.style.backdropFilter !== undefined
const mostrarMensajeBackdropFilterRaw = !localStorage.getItem("mensaje-backdrop-filter-oculto")
const soportaBackdropFilter = document.body.style.backdropFilter !== undefined;
const mostrarMensajeBackdropFilterRaw = !localStorage.getItem("mensaje-backdrop-filter-oculto");
const [mostrarMensajeBackdropFilter, setMostrarMensaje] = createSignal(mostrarMensajeBackdropFilterRaw)
const [mostrarMensajeBackdropFilter, setMostrarMensaje] = createSignal(mostrarMensajeBackdropFilterRaw);
const ocultarMensajeBackdropFilter = () => {
setMostrarMensaje(false)
localStorage.setItem("mensaje-backdrop-filter-oculto", "true")
}
setMostrarMensaje(false);
localStorage.setItem("mensaje-backdrop-filter-oculto", "true");
};
return (
<div>
@ -45,5 +45,5 @@ export function Main() {
<ContenedorHorarios />
<Creditos />
</div>
)
);
}

View File

@ -0,0 +1,11 @@
import { TopBar } from "./SistemasMovil/TopBar";
import { Table } from "./SistemasMovil/Table";
export function SistemasMovil() {
return (
<div>
<TopBar />
<Table />
</div>
);
}

View File

@ -0,0 +1,90 @@
import {StyleSheet, css} from "aphrodite/no-important";
import { createSignal, JSX } from "solid-js";
const s = StyleSheet.create({
container: {
display: "grid",
gridTemplateColumns: "3.5rem 1fr 1fr",
textAlign: "center",
fontSize: "0.9rem",
},
tableIndex: {
backgroundColor: "rgba(83,25,37,0.8)",
color: "white",
padding: "0.5rem 0.25rem",
textAlign: "center",
},
columna: {
textAlign: "left",
borderRight: "solid 2px var(--color-borde)",
},
});
type DayIndex = 0 | 1 | 2 | 3;
const days = ["Lunes", "Martes", "Miercoles", "Jueves", "Viernes"];
function Grupo(props: {curso: string, grupo: string}) {
const ss = StyleSheet.create({
button: {
display: "inline-block",
padding: "0.25rem 0.35rem",
textAlign: "left",
borderRadius: "10px",
border: "solid 2px red",
flexGrow: 1,
margin: "1px",
},
});
return (
<button className={css(ss.button)}>
{props.curso}
<br />
{props.grupo}
</button>
);
}
export function Celda(props: {children: JSX.Element}) {
const ss = StyleSheet.create({
celda: {
padding: "0 0.25rem",
display: "flex",
},
});
return (
<div className={css(ss.celda)}>
{props.children}
</div>
);
}
export function Table() {
const [currentDay, setCurrentDay] = createSignal<DayIndex>(0);
return (
<div className={css(s.container)}>
<div className={css(s.columna)}>
<div className={css(s.tableIndex)} style="border: none">&nbsp;</div>
</div>
<div className={css(s.columna)}>
<div className={css(s.tableIndex)}>{days[currentDay()]}</div>
<Celda>
<Grupo curso="TAIS 2" grupo="LA" />
<Grupo curso="ST2" grupo="LB" />
</Celda>
<Celda>
<Grupo curso="TAIS 2" grupo="LB" />
</Celda>
<Celda>
<Grupo curso="TAIS" grupo="LC" />
<Grupo curso="PIS 2" grupo="LB" />
<Grupo curso="PPP" grupo="B" />
</Celda>
</div>
<div className={css(s.columna)}>
<div className={css(s.tableIndex)}>{days[currentDay() + 1]}</div>
<div style="padding: 0 0.25rem" />
</div>
</div>
);
}

View File

@ -0,0 +1,40 @@
import { StyleSheet, css } from "aphrodite/no-important";
const s = StyleSheet.create({
bar: {
backgroundColor: "var(--color-primario)",
color: "white",
height: "3.5rem",
display: "flex",
alignItems: "center",
},
icon: {
display: "inline-block",
color: "white",
fontSize: "1.5rem",
verticalAlign: "bottom",
cursor: "pointer",
height: "1.5rem",
padding: "0 0.5rem",
},
barLabel: {
color: "white",
padding: "0 1rem",
fontWeight: 500,
fontSize: "1.25rem",
},
});
export function TopBar() {
return (
<nav className={css(s.bar)}>
<button>
<i
className={`ph-list ${css(s.icon)}`}
title={"Cambiar imagen de fondo"}
/>
</button>
<p className={css(s.barLabel)}>Mi horario</p>
</nav>
);
}

View File

@ -1,6 +1,7 @@
:root {
--color-texto: white;
--color-primario: #531925;
--color-borde: rgba(83, 25, 37, 0.49);
}
body {