Crear TablaObserver para reemplazar la forma en la que se resaltan celdas en la tabla

master
Araozu 2021-03-26 19:19:09 -05:00
parent f325f08fc8
commit cc36f070b7
10 changed files with 199 additions and 100 deletions

View File

@ -10,6 +10,9 @@
<link rel="stylesheet" href="css/phosphor.min.css">
<link rel="stylesheet" href="css/global.css">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -9,7 +9,7 @@ import { Creditos } from "./Creditos"
function App() {
/// @ts-ignore
const soportaBackdropFilter = document.body.style.backdropFilter !== undefined
const mostrarMensajeBackdropFilterRaw = localStorage.getItem("mensaje-backdrop-filter-oculto") !== undefined
const mostrarMensajeBackdropFilterRaw = localStorage.getItem("mensaje-backdrop-filter-oculto") === undefined
const [mostrarMensajeBackdropFilter, setMostrarMensaje] = createSignal(mostrarMensajeBackdropFilterRaw)

View File

@ -2,7 +2,7 @@ import { estilosGlobales } from "./Estilos"
import { StyleSheet, css } from "aphrodite"
import { numWallpaper, setNumWallpaper } from "./Store"
const totalWallpapers = 5
const ultimoIndiceWallpaper = 5
const e = StyleSheet.create({
contCambiador: {
@ -36,14 +36,14 @@ const retrocederWallpaper = () => {
setNumWallpaper(num - 1)
localStorage.setItem("num-img", (num - 1).toString())
} else {
setNumWallpaper(totalWallpapers)
localStorage.setItem("num-img", (totalWallpapers).toString())
setNumWallpaper(ultimoIndiceWallpaper)
localStorage.setItem("num-img", (ultimoIndiceWallpaper).toString())
}
}
const avanzarWallpaper = () => {
const num = numWallpaper()
if (num < totalWallpapers) {
if (num < ultimoIndiceWallpaper) {
setNumWallpaper(num + 1)
localStorage.setItem("num-img", (num + 1).toString())
} else {

View File

@ -1,7 +1,8 @@
import { Cursos, CursoRaw, DatosGrupo, ListaCursosUsuario, Curso } from "../types/DatosHorario"
import { createEffect, createMemo, For, SetStateFunction } from "solid-js"
import { Cursos, DatosGrupo, ListaCursosUsuario, Curso } from "../types/DatosHorario"
import { createMemo, For, SetStateFunction } from "solid-js"
import { StyleSheet, css } from "aphrodite"
import { estilosGlobales } from "../Estilos"
import { TablaObserver } from "./TablaObserver"
const e = StyleSheet.create({
inline: {
@ -29,14 +30,14 @@ const e = StyleSheet.create({
})
interface Props {
version: number,
dataAnio: Cursos,
anioActual: () => string,
fnAgregarCurso: (c: Curso) => void,
listaCursosUsuario: ListaCursosUsuario,
idHover: () => string,
setIdHover: (v: string) => string,
esCursoMiHorario: boolean,
setCursosUsuarios: SetStateFunction<ListaCursosUsuario>
setCursosUsuarios: SetStateFunction<ListaCursosUsuario>,
tablaObserver: TablaObserver,
}
type FnSetCursosUsuarios = SetStateFunction<ListaCursosUsuario>;
@ -45,7 +46,7 @@ interface PropsIndicadorGrupo {
nombre: string,
esLab: boolean,
idParcial: string,
setIdHover: (v: string) => string,
tablaObserver: TablaObserver,
onClick: () => void
}
@ -54,8 +55,8 @@ function IndicadorGrupo(props: PropsIndicadorGrupo) {
return (
<span className={css(e.botonTexto, estilosGlobales.contenedorCursor, estilosGlobales.contenedorCursorSoft)}
style={props.esLab ? {"font-style": "italic"} : {"font-weight": "bold"}}
onMouseEnter={() => props.setIdHover(id)}
onMouseLeave={() => props.setIdHover("")}
onMouseEnter={() => props.tablaObserver.resaltar(id)}
onMouseLeave={() => props.tablaObserver.quitarResaltado()}
onClick={props.onClick}
>
{props.esLab ? "L" : ""}{props.nombre}
@ -114,7 +115,7 @@ export function CursosElem(props: Props) {
<For each={Object.entries(props.dataAnio)}>
{([indiceCurso, datosCurso]) => {
const idCurso = `${anio()}_${datosCurso.abreviado}`
const idCurso = `${props.version}_${anio()}_${datosCurso.abreviado}`
const cursoAgregadoMemo = createMemo(
() => props.listaCursosUsuario.cursos.find((x) => x.nombre === datosCurso.nombre && !x.oculto) !== undefined,
@ -152,8 +153,8 @@ export function CursosElem(props: Props) {
<div className={claseMemo()}>
<div
className={css(e.inline, e.lineaTexto, e.botonTexto, estilosGlobales.contenedorCursor, estilosGlobales.contenedorCursorSoft)}
onMouseEnter={() => props.setIdHover(idCurso)}
onMouseLeave={() => props.setIdHover("")}
onMouseEnter={() => props.tablaObserver.resaltar(idCurso)}
onMouseLeave={() => props.tablaObserver.quitarResaltado()}
>
{datosCurso.abreviado} - {datosCurso.nombre}
</div>
@ -168,10 +169,11 @@ export function CursosElem(props: Props) {
</span>
<For each={grupos}>
{([x, fnOnClick]) => (
<IndicadorGrupo nombre={x}
<IndicadorGrupo
nombre={x}
esLab={false}
idParcial={idCurso}
setIdHover={props.setIdHover}
tablaObserver={props.tablaObserver}
onClick={fnOnClick}
/>
)
@ -190,10 +192,11 @@ export function CursosElem(props: Props) {
</span>
<For each={grupos}>
{([x, fnOnClick]) => (
<IndicadorGrupo nombre={x}
<IndicadorGrupo
nombre={x}
esLab
idParcial={idCurso}
setIdHover={props.setIdHover}
tablaObserver={props.tablaObserver}
onClick={fnOnClick}
/>
)

View File

@ -7,6 +7,7 @@ import { CursosElem } from "./CursosElem"
import { EstadoLayout } from "./ContenedorHorarios"
import { BotonMaxMin } from "./BotonMaxMin"
import { useListaCursos } from "./useListaCursos"
import { TablaObserver } from "./TablaObserver"
interface HorariosProps {
data: DatosHorario,
@ -25,8 +26,8 @@ const {
export function Horarios(props: HorariosProps) {
const [anioActual, setAnioActual] = createSignal("1er año")
// ID que indica cuales celdas resaltar.
const [idHover, setIdHover] = createSignal("")
const tablaObserver = new TablaObserver()
const elAnios = (
<For each={Object.entries(props.data.años)}>
@ -102,21 +103,20 @@ export function Horarios(props: HorariosProps) {
data={dataTabla()}
version={props.data.version}
anio={anioActual()}
idHover={idHover}
setIdHover={setIdHover}
setCursosUsuarios={setListaCursos}
tablaObserver={tablaObserver}
/>
</div>
<div>
<CursosElem
version={props.data.version}
dataAnio={dataTabla()}
anioActual={anioActual}
fnAgregarCurso={props.fnAgregarCurso}
listaCursosUsuario={props.listaCursosUsuario}
idHover={idHover}
setIdHover={setIdHover}
esCursoMiHorario={false}
setCursosUsuarios={setListaCursos}
tablaObserver={tablaObserver}
/>
</div>
</Match>

View File

@ -1,13 +1,13 @@
import { estilosGlobales } from "../Estilos"
import { StyleSheet, css } from "aphrodite"
import { Tabla } from "./Tabla"
import { mostrarDescansos } from "../Store"
import { EstadoLayout } from "./ContenedorHorarios"
import { Switch, Match, For, createMemo, createSignal, SetStateFunction } from "solid-js"
import { Switch, Match, createMemo, SetStateFunction } from "solid-js"
import { BotonMaxMin } from "./BotonMaxMin"
import { BotonIcono } from "./BotonIcono"
import { Curso, Cursos, ListaCursosUsuario } from "../types/DatosHorario"
import { CursosElem } from "./CursosElem"
import { TablaObserver } from "./TablaObserver"
interface MiHorarioProps {
estadoLayout: EstadoLayout,
@ -31,7 +31,7 @@ const e = StyleSheet.create({
})
export function MiHorario(props: MiHorarioProps) {
const [idHover, setIdHover] = createSignal("")
const tablaObserver = new TablaObserver()
const datosMiHorario = createMemo(() => {
const obj: Cursos = {}
@ -48,7 +48,7 @@ export function MiHorario(props: MiHorarioProps) {
/* TODO: En barra superior colocar todos los horarios. En barra inferior el horario
actual.
Al hacer click en un horario de la barra superior, llevarlo al inicio de la lista.
*/
*/
return (
<div>
<Switch>
@ -110,21 +110,20 @@ export function MiHorario(props: MiHorarioProps) {
data={datosMiHorario()}
anio={"Mi horario"}
version={1}
idHover={idHover}
setIdHover={setIdHover}
setCursosUsuarios={props.setCursosUsuarios}
tablaObserver={tablaObserver}
/>
</div>
<CursosElem
version={Number(Math.random() * 1_000_000)}
anioActual={() => "Mi horario"}
dataAnio={datosMiHorario()}
fnAgregarCurso={props.fnAgregarCurso}
listaCursosUsuario={props.cursosUsuario}
idHover={idHover}
setIdHover={setIdHover}
esCursoMiHorario
setCursosUsuarios={props.setCursosUsuarios}
tablaObserver={tablaObserver}
/>
</Match>
<Match when={props.estadoLayout === "MaxHorarios"}>

View File

@ -4,6 +4,7 @@ import { estilosGlobales } from "../Estilos"
import { Cursos, ListaCursosUsuario, DataProcesada } 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)",
@ -160,16 +161,13 @@ interface Props {
data: Cursos,
anio: string,
version: number,
idHover: () => string,
setIdHover: (v: string) => string,
setCursosUsuarios: SetStateFunction<ListaCursosUsuario>
setCursosUsuarios: SetStateFunction<ListaCursosUsuario>,
tablaObserver: TablaObserver,
}
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 idHover = props.idHover
const setIdHover = props.setIdHover
const celdas = createMemo(() => {
// Hace reaccionar a la reactividad de Solid
@ -180,8 +178,7 @@ export function Tabla(props: Props) {
<FilaTabla
data={data()}
hora={hora}
idHover={idHover}
setIdHover={setIdHover}
tablaObserver={props.tablaObserver}
/>
)}
</For>

View File

@ -1,8 +1,9 @@
import { StyleSheet, css } from "aphrodite"
import { estilosGlobales } from "../../Estilos"
import { For, createSignal, createMemo, createEffect, SetStateFunction } from "solid-js"
import { For, createSignal, createMemo } from "solid-js"
import { Dia } from "../../Store"
import { DatosGrupo, ListaCursosUsuario } from "../../types/DatosHorario"
import { DatosGrupo } from "../../types/DatosHorario"
import { TablaObserver } from "../TablaObserver"
const e = StyleSheet.create({
celdaComun: {
@ -19,7 +20,7 @@ const e = StyleSheet.create({
borderRadius: "5px",
// transition: "background-color 100ms, color 100ms"
},
celdaCursoActiva: {
celdaResaltado: {
// color: "#151515"
},
celdaCursoTeoria: {
@ -28,7 +29,7 @@ const e = StyleSheet.create({
celdaCursoLab: {
fontStyle: "italic",
},
celdaSeleccionada: {
celdaSeleccionado: {
textDecoration: "underline",
},
})
@ -78,22 +79,16 @@ interface Props {
datosGrupo: DatosGrupo,
fnSeleccionar: () => void
}[],
idHover: () => string,
setIdHover: (v: string) => string,
fnResaltarFila: () => void,
fnDesresaltarFila: () => void,
dia: Dia,
tablaObserver: TablaObserver,
}
const claseSeldaSeleccionada = css(e.celdaSeleccionada)
const claseSeldaSeleccionada = css(e.celdaSeleccionado)
export function CeldaFila(props: Props) {
const datos = props.datos
const idHover = props.idHover
const setIdHover = props.setIdHover
const fnOnMouseEnter = (id: string) => setIdHover(id)
const fnOnMouseLeave = () => setIdHover("")
return (
<div className={css(e.celdaComun, estilosGlobales.inlineBlock)}>
@ -104,6 +99,8 @@ export function CeldaFila(props: Props) {
const esLab = datos.esLab
const fnSeleccionar = datos.fnSeleccionar
const estadoCeldaMemo = props.tablaObserver.registrarConId(id)
const [estabaResaltado, setEstabaResaltado] = createSignal(false)
const estaSeleccionado = createMemo(() => datos.datosGrupo.seleccionado)
@ -113,20 +110,59 @@ export function CeldaFila(props: Props) {
const clases = [
e.celdaCurso,
esLab ? e.celdaCursoLab : e.celdaCursoTeoria,
estaSeleccionado() && e.celdaSeleccionada,
estaSeleccionado() && e.celdaSeleccionado,
]
let adicional = ""
const idHoverS = idHover()
if (idHoverS !== "" && id.search(idHoverS) !== -1) {
props.fnResaltarFila()
clases.push(e.celdaCursoActiva)
adicional = clasesColores[props.dia]
setEstabaResaltado(true)
} else if (estabaResaltado()) {
props.fnDesresaltarFila()
setEstabaResaltado(false)
const estadoCelda = estadoCeldaMemo()
switch (estadoCelda) {
case "Normal": {
if (estabaResaltado()) {
props.fnDesresaltarFila()
setEstabaResaltado(false)
}
break
}
case "Oculto": {
if (estabaResaltado()) {
props.fnDesresaltarFila()
setEstabaResaltado(false)
}
// TODO
break
}
case "Resaltado": {
props.fnResaltarFila()
setEstabaResaltado(true)
clases.push(e.celdaResaltado)
adicional = clasesColores[props.dia]
break
}
case "Seleccionado": {
if (estabaResaltado()) {
props.fnDesresaltarFila()
setEstabaResaltado(false)
}
break
}
case "ResaltadoOculto": {
props.fnResaltarFila()
setEstabaResaltado(true)
break
}
case "ResaltadoSeleccionado": {
props.fnResaltarFila()
setEstabaResaltado(true)
break
}
}
return `${css(...clases)} ${adicional}`
},
undefined,
@ -135,9 +171,13 @@ export function CeldaFila(props: Props) {
return (
<span className={clases()}
onMouseEnter={() => fnOnMouseEnter(id)}
onMouseLeave={fnOnMouseLeave}
onClick={fnSeleccionar}
onMouseEnter={() => {
props.tablaObserver.resaltar(id)
}}
onMouseLeave={() => {
props.tablaObserver.quitarResaltado()
}}
onClick={fnSeleccionar}
>
{txt}
</span>

View File

@ -1,10 +1,11 @@
import { StyleSheet, css } from "aphrodite"
import { estilosGlobales } from "../../Estilos"
import { For, createSignal, createMemo, createState, createEffect, State, SetStateFunction } from "solid-js"
import { For, createMemo, createState, State } from "solid-js"
import { Dia, dias } from "../../Store"
import { CeldaFila } from "./CeldaFila"
import { DataProcesada, ListaCursosUsuario } from "../../types/DatosHorario"
import { DataProcesada } from "../../types/DatosHorario"
import { coloresBorde, diaANum } from "../Tabla"
import { TablaObserver } from "../TablaObserver"
const e = StyleSheet.create({
celdaHora: {
@ -61,8 +62,7 @@ const [diasResaltados, setDiasResaltados] = createState({
interface Props {
hora: string,
data: DataProcesada,
idHover: () => string,
setIdHover: (v: string) => string
tablaObserver: TablaObserver,
}
const diasFilter = createMemo(() => Object.entries(diasResaltados)
@ -140,16 +140,15 @@ export function FilaTabla(props: Props) {
return (
<CeldaFila
datos={datos}
idHover={props.idHover}
setIdHover={props.setIdHover}
fnResaltarFila={() => fnResaltar(dia)}
fnDesresaltarFila={() => fnDesresaltar(dia)}
dia={dia}
tablaObserver={props.tablaObserver}
/>
)
}}
</For>
<div className={css(e.filaBorde)} />
<div className={css(e.filaBorde)}/>
</div>
</div>
)

View File

@ -1,18 +1,18 @@
import { createMemo, createState, SetStateFunction, State } from "solid-js"
/**
* - Normal
* - Oculto - Otro grupo seleccionado
* - Seleccionado - Cursor ensima
* - Resaltado - Grupo seleccionado
* - ResaltadoSeleccionado - Grupo seleccionado y cursor encima
* - ResaltadoOculto - Otro grupo seleccionado y cursor encima
* - Resaltado - Cursor encima
* - Seleccionado - Grupo escogido
* - ResaltadoSeleccionado - Grupo escogido y cursor encima
* - ResaltadoOculto - Otro grupo escogido y cursor encima
*/
import { createMemo, createState, SetStateFunction, State } from "solid-js";
type EstadoCelda =
| "Normal"
| "Oculto"
| "Seleccionado"
| "Resaltado"
| "Seleccionado"
| "ResaltadoSeleccionado"
| "ResaltadoOculto"
@ -20,7 +20,7 @@ interface Datos {
anio?: string,
curso?: string,
esLab?: boolean,
grupo?: string
grupo?: string,
}
export class TablaObserver {
@ -28,35 +28,93 @@ export class TablaObserver {
private readonly resaltado: State<Datos>
private readonly setResaltado: SetStateFunction<Datos>
private gruposSeleccionados = {}
private memos: {[id: string]: () => EstadoCelda} = {};
constructor() {
const [resaltado, setResaltado] = createState<Datos>({
anio: undefined,
curso: undefined,
esLab: undefined,
grupo: undefined
});
this.resaltado = resaltado;
this.setResaltado = setResaltado;
grupo: undefined,
})
this.resaltado = resaltado
this.setResaltado = setResaltado
}
// Cada celda se registra dando estos datos
// Devuelve un memo con un valor de EstadoCelda,
// el cual cada celda sabra como manejar
registrar(anio: string, cursoAbreviado: string, esLab: boolean, grupo: string) {
const fn = () => {
};
const memo = createMemo(
fn,
/**
* Crea un memo que indica el estado de la celda
* @param anio El año
* @param curso Curso abreviado
* @param esLab Si es laboratorio
* @param grupo Una única letra
*/
private registrar(anio: string, curso: string, esLab: boolean, grupo: string): () => EstadoCelda {
const resaltado = this.resaltado
const resaltadoMemo = createMemo(
() => {
if (resaltado.anio === anio && resaltado.curso === curso) {
if (resaltado.esLab === undefined) {
return true
} else if (resaltado.esLab !== esLab) {
return false
} else {
if (resaltado.grupo === undefined) {
return true
} else return resaltado.grupo === grupo;
}
} else {
return false
}
},
undefined,
(x, y) => x === y
);
(x, y) => x === y,
)
return createMemo(
(): EstadoCelda => {
if (resaltadoMemo()) {
return "Resaltado"
} else {
return "Normal"
}
},
undefined,
(x, y) => x === y,
)
}
resaltar(id: string) {
/**
* Crea un memo que indica el estado de la celda a partir de un id
* @param id Id a registrar - YYYYMMDD_Año_Curso[\_Lab[_Grupo]]
*/
registrarConId(id: string): () => EstadoCelda {
if (this.memos[id]) {
return this.memos[id]
}
const [, anio, curso, lab, grupo] = id.split("_")
const memo = this.registrar(anio, curso, lab === "L", grupo)
this.memos[id] = memo;
return memo;
}
/**
* Parsea el id y hace que las celdas registradas se actualizen adecuadamente
* @param id Id a resaltar - YYYYMMDD_Año_Curso[\_Lab[_Grupo]]
*/
resaltar(id: string) {
const [, anio, curso, lab, grupo] = id.split("_")
if (anio === undefined || curso === undefined) {
console.error("Error al intentar resaltar celda: anio o curso son undefined:", anio, curso)
return
}
this.setResaltado({
anio,
curso,
esLab: lab === "L",
grupo,
})
}
quitarResaltado() {
@ -64,7 +122,7 @@ export class TablaObserver {
anio: undefined,
curso: undefined,
esLab: undefined,
grupo: undefined
});
grupo: undefined,
})
}
}