diff --git a/.claude/plans/concurrent-splashing-nest.md b/.claude/plans/concurrent-splashing-nest.md
new file mode 100644
index 0000000..8d2fc3c
--- /dev/null
+++ b/.claude/plans/concurrent-splashing-nest.md
@@ -0,0 +1,45 @@
+# Plan: Simplificar layout EvaluacionEntrevistaPage — quitar columna competencia
+
+## Context
+
+La EvaluacionEntrevistaPage ya tiene la grilla alumnos×competencias con selects de profesor, periodo, nivel y retroalimentación. El usuario quiere simplificar el layout:
+- **Quitar** la columna izquierda del row header (periodo badge, nombre competencia, nivel, descripción, descriptores de nivel) — ocupa demasiado espacio
+- **Agregar** solo el nombre de la competencia como título dentro de cada card/celda, para saber cuál se evalúa
+
+## Archivos a modificar
+
+| Archivo | Cambio |
+|---------|--------|
+| `EvaluacionEntrevistaPage.tsx` | Quitar `
` row header + `
Competencia
`, agregar nombre competencia dentro del `.cell` |
+| `EvaluacionEntrevistaPage.module.css` | Quitar estilos de row header (`compLabel`, `compNivel`, `compDescripcion`, `levelDescriptors`, `periodoBadge`, sticky first-child), agregar `.cellCompName` |
+
+## Cambios en TSX
+
+1. **Eliminar** el `
Competencia
` del ``
+2. **Eliminar** todo el primer `
` del `
` de cada competencia (el que contiene periodoMap badge, compLabel, compNivel, compDescripcion, details/levelDescriptors)
+3. **Agregar** dentro de cada `.cell` (antes del label "Profesor") el nombre de la competencia como título:
+ ```tsx
+
{comp.competencia}
+ ```
+
+## Cambios en CSS
+
+1. **Quitar** las reglas de sticky en `.grid th:first-child` y `.grid td:first-child` (ya no hay columna fija)
+2. **Quitar** `.compLabel`, `.compNivel`, `.compDescripcion`, `.levelDescriptors` y sub-reglas, `.periodoBadge` — ya no se usan
+3. **Agregar** `.cellCompName`:
+ ```css
+ .cellCompName {
+ font-size: 0.8125rem;
+ font-weight: 600;
+ color: var(--color-text, #333);
+ margin-bottom: 0.5rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid var(--color-border, #eee);
+ }
+ ```
+
+## Verificación
+
+1. `cd packages/web && npx tsc --noEmit` — sin errores
+2. Cada card muestra: nombre competencia (título) → profesor → periodo → nivel → retroalimentación → guardar
+3. No hay columna izquierda redundante
diff --git a/.gitignore b/.gitignore
index b24d71e..7fba776 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,50 +1,40 @@
-# These are some examples of commonly ignored file patterns.
-# You should customize this list as applicable to your project.
-# Learn more about .gitignore:
-# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
-
-# Node artifact files
+# Dependencies
node_modules/
+
+# Build outputs
dist/
+packages/web/dist/
+packages/docusaurus/build/
+packages/docusaurus/.docusaurus/
+packages/api/dist/
-# Compiled Java class files
-*.class
+# TypeScript
+*.tsbuildinfo
-# Compiled Python bytecode
-*.py[cod]
+# Vite
+*.local
-# Log files
+# Logs
*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
-# Package files
-*.jar
-
-# Maven
-target/
-dist/
-
-# JetBrains IDE
+# Editor / IDE
.idea/
+.vscode/
+*.swp
+*.swo
+*~
-# Unit test reports
-TEST*.xml
-
-# Generated by MacOS
+# OS generated
.DS_Store
-
-# Generated by Windows
Thumbs.db
-# Applications
-*.app
-*.exe
-*.war
-
-# Large media files
-*.mp4
-*.tiff
-*.avi
-*.flv
-*.mov
-*.wmv
+# Environment variables
+.env
+.env.local
+.env.*.local
+# Deprecated backup directory
+deprecated/backup/
diff --git a/CLAUDE.md b/CLAUDE.md
index 83805e9..4f7b782 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4,38 +4,57 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
-Static course website for **TC2005B - Construcción de Software y Toma de Decisiones** at Tecnológico de Monterrey, Campus Querétaro. Hosted via GitHub Pages.
-
-## Commands
-
-- **Run tests:** `npm test` (uses Vitest with `--globals` flag)
-- **Serve locally:** Open `index.html` in a browser or use any static file server (e.g., `npx serve .`)
+Course website for **TC2005B - Construcción de Software y Toma de Decisiones** at Tecnológico de Monterrey, Campus Querétaro. Hosted via GitHub Pages.
## Architecture
-- **Static HTML site** — no build step, no static site generator. Pages are plain `.html` files.
-- **UI Framework:** Materialize CSS (`css/materialize.min.css`, `js/materialize.min.js`) with Google Material Icons.
-- **Custom styles:** `css/daw.css`
-- **Language:** Spanish (es-mx). All page content, labels, and calendar entries are in Spanish.
+**Monorepo** with npm workspaces containing two packages:
-### Key Pages
+- `packages/web/` — React 19 + Vite 6 + TypeScript SPA (main site: calendar, labs, avances, policies)
+- `packages/docusaurus/` — Docusaurus 3.9.2 documentation site (served under `/docs/`)
-- `index.html` — Landing page with group selection links
-- `grupo1.html`, `grupo2.html` — Class schedule/calendar per group (table-based calendars)
-- `code_reviews.html` — Team work policies
+Legacy static HTML content (ejercicios, laboratorios, lecturas, documentos) lives in `static-legacy/` and is copied to the build output.
-### Content Directories
-
-- `labs/` — Web development labs (HTML, CSS, JS, Node, Express, MVC, sessions, auth, AJAX, REST, deployment)
-- `laboratorios/` — Database labs (SQL, stored procedures, transactions, optimization)
-- `ejercicios/` — Database exercises (ER models, relational algebra, SQL, normalization)
-- `lecturas/` — Reading materials on databases, SQL, usability, application design
-- `avances/` — Project milestone/deliverable descriptions
-- `documentos/` — Course documents, presentations (.pptx), and supplementary files
-- `archived/` — Deprecated content from prior semesters
-
-### Conventions
+## Commands
-- All HTML pages use the same Materialize boilerplate: navbar, main container, footer with important links.
-- Calendar pages (`grupo*.html`) use `
` with weekly rows.
-- Links to labs/exercises use relative paths from the root.
+- **Dev servers:** `npm run dev` — starts Vite (port 5173) and Docusaurus (port 3001) concurrently
+- **Build:** `npm run build` — builds web + docs + merges into `dist/`
+- **Preview:** `npm run preview` — serves the built `dist/` directory
+- **Run tests:** `npm test` (uses Vitest with `--globals` flag)
+- **Type check:** `cd packages/web && npx tsc --noEmit`
+
+## Key Directories
+
+### `packages/web/src/`
+- `components/` — React components organized by feature (layout, home, calendar, labs, avances, policies)
+- `data/` — Migrated data files as typed ES modules (labs, avances, calendario)
+- `types/` — TypeScript interfaces (Lab, Avance, Calendario, Actividad, etc.)
+- `hooks/` — Custom hooks (useCalendarFilter, useWeekNavigation)
+- `styles/` — CSS variables and global styles
+
+### Routing (React Router 7)
+| Route | Component |
+|-------|-----------|
+| `/` | HomePage |
+| `/calendario/:grupoId` | CalendarPage |
+| `/labs/:labId` | LabPage |
+| `/avances/:avanceId` | AvancePage |
+| `/politicas` | CodeReviewsPage |
+
+### Data Sources
+- `data-source/labs/` — Original JS lab data files (34 files, source of truth)
+- `data-source/avances/` — Original JS avance data files (6 files)
+- `data-source/calendario/` — Original JS calendario data file
+- Migrated to TypeScript via `scripts/migrate-data.mjs`
+
+### Legacy Content
+- `static-legacy/` — ejercicios, laboratorios, lecturas, documentos, imagenes (copied to build)
+- `deprecated/` — All legacy HTML pages, viewers, assets, and content duplicates (not used at runtime)
+
+## Conventions
+
+- **Language:** Spanish (es-mx). All content, labels, and calendar entries are in Spanish.
+- **Styling:** CSS Modules with design tokens in `variables.css`. No Tailwind.
+- **Fonts:** Inter (Google Fonts) + Material Icons.
+- **Data pattern:** Each lab/avance is a separate TS file with typed `export default`. Barrel exports use dynamic imports for code splitting.
+- **Links:** Internal links use React Router paths (`/labs/lab1`, `/avances/av1`). External links use full URLs.
diff --git a/backup/Proyecto1AplicacionesWeb.html b/backup/Proyecto1AplicacionesWeb.html
deleted file mode 100644
index 6401ae8..0000000
--- a/backup/Proyecto1AplicacionesWeb.html
+++ /dev/null
@@ -1,209 +0,0 @@
-
-
-
-
- Actividad
-
-
-
-
-
-
-
-
-
-
-
Actividad: Proyecto 1: Consultora de
- aplicaciones web
- Mdulo: Aplicaciones cliente-servidor
-
-
-
Descripcin
-
-
-
-
-
-
El espritu emprendedor por fin rindi frutos y ahora eres
- el CEO de tu propia empresa. Tu gran idea de un nuevo producto
- de software ya la ests desarrollando, pero necesitas
- capitalizar un poco ms tu empresa para reunir los recursos
- necesarios para terminar tu gran proyecto. Para capitalizarte,
- haz decidido prestar servicios de consultora de aplicaciones
- web para adems contribuir con la mejora de procesos de las
- PYMES de la regin.
-
-
-
-
-
Modalidad
-
-
-
-
-
-
Colaborativa.
-
-
-
-
-
Objetivos de la
- actividad
-
-
-
-
-
-
Que conozcas a profundidad aplicaciones web que puedan ser
- utilizadas por organizaciones para que mejoren sus procesos
-
Que te enfrentes con los retos de instalar y configurar una
- aplicacin web open source
-
Desarrollar tu competencias de anlisis de la informacin
-
Desarrollar tus competencias de trabajo colaborativo
-
Desarrollar tu competencia de administracin del tiempo
-
Desarrollar tus competencias de comunicacin oral y escrita
-
Desarrollar tu competencia para proponer soluciones
- tecnolgicas para mejorar los procesos de negocio de una
- organizacin
-
Desarrollar tu competencia de aprendizaje por cuenta propia
- de nuevas tecnologas y herramientas
-
Desarrollar tu competencia de de anlisis de impacto de las
- herramientas tecnolgicas en los individuos, organizaciones, y
- la sociedad para prestar servicios profesionales de manera
- tica y responsable
-
-
-
-
-
Instrucciones
-
-
-
-
-
-
Dedicaremos 10 minutos a buscar aplicaciones web que con su
- uso puedan resolver problemas de organizaciones y contribuyan
- a la mejora de sus procesos. Las aplicaciones web deben ser
- open source o deben poderse utilizar como servicio.
-
Dedicaremos algunos minutos de la sesin para elaborar un
- lista de las aplicaciones encontradas, y las agruparemos por
- la problemtica que abordan.
-
Seleccionaremos la problemtica de mayor inters para el
- grupo.
-
Elaboraremos los equipos de trabajo para el desarrollo del
- proyecto.
-
Con tu equipo, selecciona 3 aplicaciones que abroden la
- problemtica seleccionada. Al menos 1 aplicacin debe ser open
- source.
-
Definan los criterios de evaluacin de sus aplicaciones.
- Presenten argumentos slidos sobre las razones por las cuales
- escogieron o definieron esos criterios. Algunos lineamientos
- pueden ser: facilidad de uso, facilidad de
- instalacin/configuracin, relevancia del problema que
- resuelven, soporte, comunidad de usuarios, etc.
-
Descarguen la aplicacin open source, y lleven a cabo su
- instalacin y configuracin, incluyendo el servidor que
- requiere. Documenten este proceso.
-
Familiarcense con las 3 aplicaciones.
-
Hagan una revisin sobre los criterios de evaluacin.
- Documenten los cambios y presenten argumentos.
-
Evalen las 3 aplicaciones.
-
Emitan recomendaciones sobre cada una de las aplicaciones
- analizadas.
-
Presenten los resultados (y todo lo anterior que se les
- pide) a travs de un sitio HTML5. El sitio deben presentarlo
- al saln de clases y deben llegar preparados para ello (no
- llegar a prepararse). La presentacin debe iniciar y terminar
- a tiempo, y posteriormente habr un espacio para preguntas y
- respuestas.
-
Parte de la evaluacin la realizar el profesor, otra parte
- la realizar toda la clase.
-
-
Criterios de evaluacin
-
-
Evaluacin del profesor:
-
-
Instalacin y configuracin de la aplicacin open source
- (20%)
Despus de haber sido consultor de aplicaciones web, te haz
- dado cuenta que si bien hay una gran variedad, no hay una que
- resuelva exactamente las necesidades de las empresas de la
- regin. Es por ello que ahora haz decidido desarrollar tu
- propia aplicacin web.
-
-
-
-
-
Modalidad
-
-
-
-
-
-
Colaborativa.
-
-
-
-
-
Objetivos de la
- actividad
-
-
-
-
-
-
Que desarrolles tu competencia de programacin en un
- apradigma cliente-servidor
-
Que desarrolles tu comepetencia de programacin en un
- paradigma orientado a eventos
-
Que desarrolles tus competencias para desarrollar
- aplicaciones bajo una arquitectura modelo, vista, controlador
-
Que desarrolles tus competencias de diseo de bases de datos
-
Que desarrolles tus competencias para disear y desarrollar
- servicios web
-
Que desarrolles tus competencias para disear aplicaciones
- amigables para el usuario, y que seas capaz de evaluar de
- manera formal la usabilidad de una aplicacin por medio de heursticas
-
Que te enfrentes con el reto de desplegar una aplicacin web
- php
-
Desarrollar tus competencias de trabajo colaborativo
-
Desarrollar tu competencia de administracin del tiempo
-
Desarrollar tu competencia de comunicacin oral
-
Desarrollar tu competencia para proponer soluciones
- tecnolgicas para mejorar los procesos de negocio, sociales,
- ambientales y educativos
-
Desarrollar tu competencia de aprendizaje por cuenta propia
- de nuevas tecnologas y herramientas
-
Desarrollar tu competencia de de anlisis de impacto de las
- herramientas tecnolgicas en los individuos, organizaciones, y
- la sociedad para prestar servicios profesionales de manera
- tica y responsable
-
-
-
-
-
Instrucciones
-
-
-
-
-
-
En equipo desarrolla una aplicacin web utilizando php como
- lenguaje de programacin del lado del servidor. La aplicacin
- debe resolver una problemtica propia de las ciencias
- computacionales, o bien debe tener un impacto social,
- ambiental, o educativo.
-
-
La funcionalidad de la aplicacin debe incluir al menos
- una consulta para listar elementos de una base datos, una
- insercin de un registro (a travs de una forma html), una
- modificacin de un registro, y una eliminacin de un
- registro.
-
Una vez que tengas la idea de tu proyecto, durante las
- sesiones de clase, disctela con tu profesor. Posteriormente
- por medio de diagramas de casos de uso, acuerda el alcance
- de la aplicacin con tu profesor. Para la entrega del
- proyecto, incluye los diagramas de casos de uso dentro de
- una pgina de tu aplicacin.
-
Cada miembro del equipo debe implementar al menos 1
- componente AJAX. Pueden hacer uso de alguna librera
- predefinada como jQuery, Dojo, entre otros.
-
Disea un servicio web que provea una parte de la
- funcionalidad de tu aplicacin. Cada miembro del equipo debe
- implementar al menos una operacin del servicio web. El
- servicio web puede ser basado en REST o SOAP. Consume el
- servicio dentro de tu aplicacin.
-
Del lado del cliente, debes utilizar tecnologas HTML5,
- CSS, JavaScript, y AJAX. Asegrate que tu aplicacin sea
- fcil de usar y agradable a la vista. Toma en cuenta la
- evaluacin heurstica de tu aplicacin realizada por tus
- compaeros. Implementa al menos una de las recomendaciones.
- Durante la presentacin, debes presentar el estado previo a
- la evaluacin heurstica de tu aplicacin y compararlo con
- el estado actual. Presenta tambin las recomendaciones que
- se hicieron que no se implementaron.
-
La aplicacin debe estar disponible en un servidor
- accesible a travs de la www
Desarrollar tus competencias de trabajo COLABORATIVO
-
Que desarrolles tu competencia de programaci�n en un
- paradigma cliente-servidor
-
Que desarrolles tu comepetencia de programaci�n en un
- paradigma orientado a eventos
-
Que desarrolles tus competencias para desarrollar
- aplicaciones bajo una arquitectura modelo, vista, controlador
-
Que desarrolles tus competencias de dise�o de bases de datos
-
Que desarrolles tus competencias para dise�ar y desarrollar
- servicios web
-
Que desarrolles tus competencias para integrar servicios web
- en aplicaciones
-
Desarrollar tu competencia de administraci�n del tiempo
-
Desarrollar tu competencia de comunicaci�n oral
-
Desarrollar tu competencia para proponer soluciones
- tecnol�gicas para mejorar los procesos de negocio, sociales,
- ambientales y educativos
-
Desarrollar tu competencia de aprendizaje por cuenta propia
- de nuevas tecnolog�as y herramientas
-
Desarrollar tu competencia de de an�lisis de impacto de las
- herramientas tecnol�gicas en los individuos, organizaciones, y
- la sociedad para prestar servicios profesionales de manera
- �tica y responsable
-
-
-
-
-
Instrucciones
-
-
-
-
-
-
En equipo desarrolla una aplicaci�n web que resuelva una
- problem�tica propia de las ciencias computacionales, o bien
- que tenga un impacto social, ambiental, o educativo.
-
Sobre la plataforma tecnol�gica tienes las siguientes
- opciones:
-
-
Desarrolla una aplicaci�n web JEE
-
Desarrolla un servicio web con tecnolog�a JEE y desarrolla
- una aplicaci�n php que consuma el servicio
-
Desarrolla una aplicaci�n web con php que integre al menos
- 2 servicios web desarrollados por terceros (facebook,
- twitter, google calendar, google maps, entre muchos otros)
-
Desarrolla una aplicaci�n web con alguna tecnolog�a no
- vista en clase (python,
- ruby,
- perl,
- .net,
- entre otras)
La funcionalidad de la aplicaci�n debe incluir al menos una
- consulta para listar elementos de una base datos, y una
- inserci�n de un registro a trav�s de una forma.
-
Una vez que tengas la idea de tu proyecto, durante las
- sesiones de clase, disc�tela con tu profesor. Posteriormente
- por medio de diagramas de casos de uso, acuerda el alcance de
- la aplicaci�n con tu profesor. Para la entrega del proyecto,
- incluye los diagramas de casos de uso dentro de una p�gina de
- tu aplicaci�n.
-
Para el desarrollo debes utilizar un control de versiones y
- mostrar evidencia de ello.
-
Aplica las heur�sticas de usabilidad en tu dise�o.
-
Tu aplicaci�n debe estar construida bajo una arquitectura
- MVC, y debes mostrar evidencia de ello.
-
Cada miembro del equipo debe implementar al menos 1
- componente AJAX. Pueden hacer uso de alguna librer�a
- predefinada como jQuery, Dojo, entre otros.
Actividad: Proyecto 4: Servicios web
- Mdulo: Servicios web
-
-
-
Descripcin
-
-
-
-
-
-
Ahora que haz desarrollado tus competencias en el desarrollo
- de aplicaciones web con diferentes tecnologas como HTML5,
- CSS, JS, PHP, JEE, XML, haz notado una buena oportunidad de
- negocio y desarrollo al adentrarte en el mundo de los
- servicios web, por lo que tu siguiente proyecto consiste en
- comenzar a implementar una infraestructura bajo una
- arquitectura orientada a servicios (SOA).
-
-
-
-
-
Modalidad
-
-
-
-
-
-
Colaborativa.
-
-
-
-
-
Objetivos de la
- actividad
-
-
-
-
-
-
Desarrollar tus competencias de trabajo COLABORATIVO
-
Que desarrolles tu competencia de programacin en un
- paradigma cliente-servidor
-
Que desarrolles tu comepetencia de programacin en un
- paradigma orientado a eventos
-
Que desarrolles tus competencias para desarrollar
- aplicaciones bajo una arquitectura modelo, vista, controlador
-
Que desarrolles tus competencias para disear y desarrollar
- servicios web
-
Que desarrolles tus competencias de diseo de bases de datos
-
Desarrollar tu competencia de administracin del tiempo
-
Desarrollar tu competencia de comunicacin oral
-
Desarrollar tu competencia para proponer soluciones
- tecnolgicas para mejorar los procesos de negocio, sociales,
- ambientales y educativos
-
Desarrollar tu competencia de aprendizaje por cuenta propia
- de nuevas tecnologas y herramientas
-
Desarrollar tu competencia de anlisis de impacto de las
- herramientas tecnolgicas en los individuos, organizaciones, y
- la sociedad para prestar servicios profesionales de manera
- tica y responsable
Disea un prototipo de aplicacin que resuelva una
- problemtica propia de las ciencias computacionales, o bien
- que tenga un impacto social, ambiental, o educativo. Puedes
- tomar como base, algn laboratorio o proyecto anterior,
- siempre y cuando aborde una problemtica aceptable.
-
Disea y desarrolla un servicio web que le de soporte a la
- aplicacin. El servicio web puede ser basado en REST o SOAP, y
- debe tener al menos 3 operaciones significativas para la
- aplicacin.
-
La aplicacin web puede estar implementada en cualquier
- tecnologa (php, JEE, o cualquier otra).
-
-
-
La funcionalidad del prototipo debe incluir al menos una
- consulta para listar elementos de una base datos, una
- insercin de un registro (a travs de una forma html), una
- modificacin de un registro, y una eliminacin de un registro.
-
-
-
El prototipo debe seguir una arquitectura MVC
-
-
-
El prototipo debe incluir al menos 1 componente AJAX. Puedes
- hacer uso de alguna librera predefinada.
-
-
-
El alcance del prototipo debe abarcar una cantidad de casos
- de uso equivalente al doble de los integranets del equipo. Se
- deben definir los casos de uso y presentar como una pgina web
- dentro de la aplicacin. Los casos de uso deben ser
- significativos (cerrar sesin no cuenta como un caso de uso
- significativo).
-
-
-
Del lado del cliente, debes utilizar tecnologas HTML5, CSS,
- JavaScript, y AJAX. Asegrate que tu aplicacin sea fcil de
- usar y agradable a la vista.
-
-
-
-
Criterios de evaluacin
-
-
Evaluacin del profesor:
-
Evaluacin del grupo:
-
Servicio web (40%)
-
-
Implementacin del servicio web con al menos 3 operaciones
- significativas (30%)
-
Diseo del servicio web (10%)
-
-
Aplicacin web (40%)
-
-
Arquitectura MVC (10%)
-
Casos de uso, utilidad y creatividad del proyecto (10%)
-
Consumo del servicio web (20%)
-
-
Cliente (20%)
-
-
Documentos HTML5, CSS siguiendo buenas prcticas,
- componente AJAX (10%)
-
Usabilidad general del sitio (10%)
-
-
Impacto social, ambiental, educativo (+5%)
-
-
-
-
-
-
-
-
-
Recursos
-
-
-
-
-
-
-
-
-
-
-
-
Especificaciones de
- entrega
-
-
-
-
-
-
Presentacin en la sesin de clase y a travs de Blackboard
-
-
-
-
-
-
-
-
DR Tecnolgico de Monterrey Campus Quertaro|
- Departamento de Desarrollo Acadmico| Mxico, 2014
A lo largo del curso desarrollarás un proyecto para potenciar tu aprendizaje. Si estás llevando la materia de Bases de Datos (BD), el proyecto será el mismo para ambas materias. El proyecto deberá ser un sistema web que
- proponga una solución para una problemática social, ambiental, educativa, o bien para un problema de las ciencias computacionales. Si como parte de tu servicio social vas a desarrollar una aplicación web, tienes la opción de alinear ese proyecto con el curso.
-
Los proyectos se trabajarán en equipos base de 3 personas que estén tomando esta clase. Si estás llevando BD todos los integrantes de tu equipo deberán estar llevando la materia también y ambos profesores deben aceptar la propuesta. Si no estás cursando BD, tus compañeros de equipo tampoco deberán hacerlo.
-
-
-
-
-
-
- Modalidad
-
-
- Colaborativa.
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Que desarrolles tu competencia de programación en un
- paradigma cliente-servidor
-
Que desarrolles tu comepetencia de programación en un
- paradigma orientado a eventos
-
Que desarrolles tus competencias para desarrollar
- aplicaciones bajo una arquitectura modelo, vista, controlador
-
Que desarrolles tus competencias de diseño de bases de datos
-
Que desarrolles tus competencias para diseñar y desarrollar
- servicios web
-
Que desarrolles tus competencias para diseñar aplicaciones
- amigables para el usuario, y que seas capaz de evaluar de
- manera formal la usabilidad de una aplicación por medio de heurísticas
-
Que te enfrentes con el reto de desplegar una aplicación web
-
Desarrollar tus competencias de trabajo colaborativo
-
Desarrollar tu competencia de administración del tiempo
-
Desarrollar tu competencia de comunicación oral
-
Desarrollar tu competencia para proponer soluciones
- tecnológicas para mejorar los procesos de negocio, sociales,
- ambientales y educativos
-
Desarrollar tu competencia de aprendizaje por cuenta propia
- de nuevas tecnologías y herramientas
-
-
-
-
-
-
-
- Instrucciones
-
-
- En equipo desarrolla una aplicación web. La aplicación
- debe resolver una problemática propia de las ciencias
- computacionales, o bien debe tener un impacto social,
- ambiental, o educativo.
-
-
Una vez que tengas la idea de tu proyecto, durante las
- sesiones de clase, discútela con tu profesor hasta que lleguen a
- un acuerdo sobre el proyecto.
-
Elabora un breve documento sobre la visión del proyecto y
- sus características.
-
Por medio de un diagramas de casos de uso, acuerden el
- alcance de la aplicación con tu profesor.
-
Elaboren un mapa del sitio atienda los aspectos establecidos
- en la visión, características y alcance del proyecto.
-
Para la primera entrega deberás entregar una primera versión
- de la interface del usuario. Para ello utiliza tecnologías
- HTML5, CSS y JavaScript. Se sugiere que te apoyes de un
- framework de front-end.
-
Tu interface debe procurar ser agradable a la vista, y debe
- fomentar la facilidad de uso de tu aplicación.
-
Incluye la visión del proyecto y sus características, así
- como todos los artefactos de ingeniería de software que
- estés utilizando dentro de una sección de tu sitio.
-
Antes de presentar, entreguen al profesor un documento
- firmado por todos los integrantes del equipo, con los
- porcentajes de trabajo de cada uno.
Presentación en la sesión de clase y a por medio de un respositorio en Bitbucket
-
-
-
-
-
-
-
- Preguntas
-
-
-
¿Por qué es importante la accesibilidad?
-
¿Cuáles son los lineamientos de accesibilidad que consideras más importantes? ¿Por qué?
-
Describe 3 cambios que harás en tu proyecto para hacerlo más accesible.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/av1.html b/backup/av1.html
deleted file mode 100644
index 603f417..0000000
--- a/backup/av1.html
+++ /dev/null
@@ -1,193 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Avance del proyecto 1: Propuesta de proyecto
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Avance de proyecto 1: Propuesta de proyecto
-
-
-
-
Avance de proyecto 1: Propuesta de proyecto
-
En este avance de proyecto formarán su equipo de trabajo, crearán una identidad para ustedes como despacho y realizarán la propuesta formal de su proyecto.
-
-
-
-
-
-
- Modalidad
-
-
- Colaborativa.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Formalizar el equipo de trabajo y establecer las bases para desarrollar el proyecto de la Unidad de Formación
-
-
-
-
-
-
Instrucciones
-
-
-
Como respuesta a esta asignación, deberán elaborar un documento para establecer su despacho de consultoría en Construcción de software, y otro(s) documento(s), en el que presentarán la propuesta formal del proyecto.
-
El documento del despacho debe contener al menos lo siguiente (1 punto) :
-
-
- Conformación del equipo:
-
-
-
Indicar para cada uno de los integrantes del despacho su nombre, matrícula y datos de contacto. Además deben identificar las fortalezas y áreas de oportunidad de cada uno de ustedes. Así como las expectativas que tienen del curso.
-
Posteriormente elaborar un breve listado de lo que esperan lograr y obtener como equipo en el presente curso, así como sus compromisos para lograrlo.
-
-
-
- Identidad corporativa:
-
-
Razón social y logotipo, con los que deberán identificar todos los documentos que se generen a lo largo del desarrollo del proyecto.
-
-
Principios y valores, así como los objetivos de su despacho de consultoría en desarrollo de sistemas y bases de datos.
-
-
¿Qué ofrece para sus clientes?, ¿Qué compromiso tiene con la sociedad?
-
-
-
-
-
-
-
- La propuesta del proyecto debe considerar (3 puntos):
-
-
- Alcance organizacional:
-
- Deberás describir brevemente la institución o empresa para la que se desarrolla el proyecto, indicando si se trata de un área o departamento específico, así como su misión o propósito general.
-
-
-
- Descripción del problema o situación actual de la empresa:
-
- Deberás describir a detalle la situación actual de la empresa. Por ejemplo su forma de operar actualmente y los inconvenientes de hacerlo así. En esta sección NO se espera que se incluya una propuesta de solución.
-
-
-
-
-
- Requerimientos de información:
-
- Identificarás de manera general los catálogos que le darán soporte al sistema de información. Al respecto se espera que identifiques de manera completa campos y posibles relaciones entre los catálogos.
-
-
-
-
- Plan de trabajo y aprendizaje adquirido (1 punto):
-
- En TODAS sus presentaciones deben incluir el plan de trabajo actualizado y el aprendizaje adquirido como equipo. El plan de trabajo debe incluir al menos:
-
-
1) Las actividades pendientes del proyecto y el periodo de tiempo en el que se realizarán.
-
2) Para las actividades del siguiente avance los responsables de llevarlas a cabo, la fecha en la que las realizarán y el intervalo de esfuerzo estimado.
-
3) Para las actividades que se llevaron a cabo en este avance el tiempo que les tomó realizarlas y la diferencia con su estimación.
-
-
-
- Canal de comunicación:
-
-
1) Enviar al profesor Alejandro la lista de usuarios de Discord de su equipo para que les prepare su canal personalizado. Este canal será utilizado para la comunicación entre el equipo con los profesores. Adicionalmente, si gustan pueden crear un canal exclusivo para la comunicación entre el equipo.
-
2) Todos los miembros del equipo deben estar en el canal.
-
3) Sube el documento generado.
-
-
-
-
-
-
- Recuerda que toda la documentación que generen en torno al proyecto deberá mostrar la imagen corporativa de tu equipo y mantener consistencia gráfica en aspectos como fuentes tipográficas, colores o sombreados, imágenes, márgenes y alineación.
-
-
-
-
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
Por medio de Discord en el canal del equipo con los profesores.
Avance del proyecto 2: Propuesta del proyecto e identificación de requisitos
-
-
-
-
Avance del proyecto 2: Propuesta del proyecto e identificación de requisitos
-
En este avance de proyecto realizarán la propuesta formal de su proyecto.
-
-
-
-
-
-
- Modalidad
-
-
- Colaborativa.
-
-
-
-
-
-
Objetivos de aprendizaje
-
-
Desarrollar tus habilidades de trabajo colaborativo
-
Desarrollar tu capacidad de administración del tiempo
-
Que apliques lo visto en el curso de Fundamentos de Ingeniería de Software para continuar desarrollando tus habilidades como arquitecto de software.
-
-
-
-
-
-
Instrucciones
-
-
- Redacten un documento que incluya los siguientes segmentos:
-
-
- Alcance organizacional:
-
- Deberás describir brevemente la institución o empresa para la que se desarrolla el proyecto, indicando si se trata de un área o departamento específico, así como su misión o propósito general. (10 puntos)
-
-
-
- Descripción del problema o situación actual de la empresa:
-
- Deberás describir a detalle la situación actual de la empresa. Por ejemplo su forma de operar actualmente y los inconvenientes de hacerlo así. En esta sección NO se espera que se incluya una propuesta de solución. (20 puntos)
-
-
-
- Requerimientos funcionales:
-
- Describirás de manera general los principales requerimientos de la organización que pretenden cubrirse con el sistema. Se espera que se elabore un diagrama de casos de uso que cumpla con los lineamientos descritos en el curso de Fundamentos de Ingeniería de Software. (30 puntos)
-
-
-
- Requerimientos de información:
-
- Identificarás de manera general los catálogos que le darán soporte al sistema de información. Al respecto se espera que identifiques de manera completa campos y posibles relaciones entre los catálogos. (20 puntos)
-
-
-
- Bosquejo de la aplicación:
-
- Diseñarán una propuesta de interfaz gráfica (puede ser en papel, usando el editor de ventanas de MS VISIO o si lo prefieren utilizar el editor de formas de .NET, o cualquier otro editor web como Cacoo) que atenderá los aspectos funcionales identificados. Se espera que se muestre la evolución del bosquejo o prototipo con base a las revisiones hechas por el cliente. Podrán crear un proyecto por cada versión y numerarlas consecutivamente. (20 puntos)
-
-
-
-
- Recuerda que toda la documentación que generen en torno al proyecto deberá mostrar la imagen corporativa de tu equipo y mantener consistencia gráfica en aspectos como fuentes tipográficas, colores o sombreados, imágenes, márgenes y alineación.
-
-
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
Por medio de su repositorio de equipo en bitbucket. El commit debe contener el mensaje "Avance de proyecto 2".
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/av3.html b/backup/av3.html
deleted file mode 100644
index aac5949..0000000
--- a/backup/av3.html
+++ /dev/null
@@ -1,190 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Avance del proyecto 2: Análisis y diseño de la solución.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Avance del proyecto 2: Análisis y diseño de la solución.
-
-
-
-
Avance del proyecto 2: Análisis y diseño de la solución.
-
En este avance de proyecto comenzarán con el diseño de la base de datos y de la aplicación web. Identificarás los requisitos funcionales, los requisitos no funcionales, y los requisitos de información; así como el diseño de la interacción y primera versión de la interfaz. Posteriormente detallarás los casos de uso y casos de prueba de los requisitos de mayor prioridad.
-
-
-
-
-
-
- Modalidad
-
-
- Colaborativa.
-
-
-
-
-
-
Objetivos de aprendizaje
-
-
Desarrollar tus habilidades de trabajo colaborativo
-
Desarrollar tu capacidad de administración del tiempo
-
Desarrollar tu habilidad de diseño de bases de datos.
-
Desarrollar tu habilidad de diseño de la información.
-
Desarrollar tu habilidad de diseño de la interacción.
-
Desarrollar tu habilidad de diseño e implementación de interfaces HTML5.
-
-
-
-
-
-
Instrucciones
-
-
- Redacten un documento (o varios) que incluya(n) los siguientes segmentos:
-
-
- Diagrama de contexto (2 puntos):
-
- Haciendo uso formal de la nomenclatura descrita en el curso para la elaboración de este diagrama, elaborar el diagrama de contexto de nivel 0 para identificar a todos los interesados en el sistema de información que dará solución a la problemática descrita anteriormente. Recuerda detallar los flujos de datos entre interesados y sistema a un nivel de campos, ya que este será el punto de entrada para diseñar el modelo de persistencia.
-
-
-
- Requisitos funcionales (4 puntos):
-
Identificación completa de los requerimientos de la organización que pretenden cubrirse con el sistema. Se espera que se elabore un diagrama de casos de uso que cumpla con los lineamientos descritos en la Unidad de Formación Análisis de Requerimientos de Software.
-
- Tabla de priorización de requisitos que considere el riesgo, valor, complejidad y estabilidad.
-
-
- Detalle de los casos de uso que identificaste en la tabla de alta prioridad, la plantilla a utilizar queda a su criterio pero debe incluir el diagrama de actividad de cada uno. Posteriormente se recomienda que los validen con el cliente y a partir de este momento se procede a definir el modelo de datos del proyecto.
-
-
-
- Reglas de negocio (1 punto):
-
- Identificar y describir las reglas de negocio definidas por la organización que deben ser atendidas por el sistema.
-
-
-
- Modelo Entidad-Relación (3 puntos):
-
- Diagrama con entidades, asociaciones y las extensiones necesarias (ISA's, entidades débiles, roles, en caso de ser necesarias) con la cardinalidad explícita, incluyendo posibles cotas de cardinalidad con base en las lecturas sobre la notación básica y/o las extensiones al modelo. Para mayor legibilidad del diagrama, omite en éste los atributos que se detallarán en el siguiente punto. Se espera que el modelo presentado sea "validado" en la medida de lo posible por el cliente, al menos a nivel de atributos.
-
- Diccionario de datos:
-
- Tablas detallando los atributos de cada elemento del modelo, tal como se realizó en el Ejercicio de Modelo Entidad-Relación completo.
-
- Documentación de restricciones adicionales:
-
- Documentarás las restricciones adicionales que deben obedecer los datos de tu modelo.
-
- Establecerás las tablas con las que tu modelo se instrumentará en una base de datos relacional, aplicando las reglas de traslado.
-
-
-
- Requisitos no funcionales (1 punto):
-
- Identificar y describir cada uno de los requisitos no funcionales de tu proyecto, recuerden redactarlos de una forma en la que al final puedan medir su logro.
-
-
-
- Mapa del sitio (1 punto):
-
- El formato del diagrama es libre. Debe permitir identificar la navegación que tiene que realizar el usuario para lograr sus objetivos.
-
-
-
- Bosquejo de la aplicación (1 punto):
-
- Diseñarán una propuesta de interfaz gráfica por medio de wireframes (puede ser en papel, usando el editor de ventanas de MS VISIO o cualquier otro editor web como Cacoo, Lucid Chart, entre otros) que atenderá los aspectos funcionales identificados. Se espera que se muestre la evolución del bosquejo o prototipo con base a las revisiones hechas por el cliente. Podrán crear un proyecto por cada versión y numerarlas consecutivamente.
-
-
-
- Plan de comunicación (1 punto):
-
- Esbozo general del plan de comunicación donde determinen la forma en la que se van a comunicar con cada uno de los interesados del proyecto.
-
-
-
- Plan de trabajo actualizado y aprendizaje adquirido (1 punto):
-
-
-
- Recuerda que toda la documentación que generen en torno al proyecto deberá mostrar la imagen corporativa de tu equipo y mantener consistencia gráfica en aspectos como fuentes tipográficas, colores o sombreados, imágenes, márgenes y alineación.
-
-
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
Por medio de su repositorio de equipo. Al último commit del avance, asígnenle el tag avance2.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/backup/av4.html b/backup/av4.html
deleted file mode 100644
index 7a281a9..0000000
--- a/backup/av4.html
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Avance de Proyecto 3: Creación de la Base de Datos
-
-
-
-
-
-
-
-
-
-
-
Avance de Proyecto 3: Creación de la Base de Datos
-
-
-
-
-
-
-
- Modalidad
-
-
-
- Colaborativa.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
-
Aplicar tus conocimientos sobre bases de datos y desarrollo web.
-
-
-
-
-
-
-
-
- Instrucciones
-
-
-
- Uno de los integrantes del equipo deberá entregar el trabajo indicando las matrículas y nombres de los integrantes.
-
- Este documento debe incluir los siguientes elementos:
-
-
- Tablas Correspondientes (Modelo Relacional Revisado) (1 punto): Establecerás las tablas con las que tu modelo se instrumentará en una base de datos relacional. Como te habrás dado cuenta en este caso solo son mejoras o refinamientos a tu avance anterior. Se espera que el modelo entregado haya sido revisado en compañía del profesor y nuevamente validado por el cliente. (nombre del Mer_Revisado.doc)
-
-
- Tablas del proyecto (2 puntos): En base al modelo relacional que definiste en la entrega anterior, debes definir el script para crear las tablas, incluyendo los constraints de llaves primarias y foráneas. (nombre del script estructura.sql)
-
-
- Prototipo de navegación de la aplicación web (2 puntos): Debe presentarse un prototipo completamente navegable que cumpla con las expectativas del cliente relacionadas con la interface, como colores institucionales, secuencia de navegación, mensajes de retroalimentación, layout de reportes.
-
-
- Carga de datos y su script correspondiente(4 puntos): Las tablas deben contener una muestra representativa de datos que permita consultarlas. Una muestra representativa de datos es aquella que hace referencia a datos que pudieran ser equivalente en extensión, formato y valor a los reales. Por ejemplo, para el registro de un nombre completo, sería un dato representativo: "Juan Manuel González de Cossío". No son datos representativos: "Prueba Prueba Ejemplo Ejemplo2", "Nombre Nombre2 Apellido_paterno, Apellido_materno".
-
- 1.- En el caso de tablas que representan clasificaciones (Ejemplos: Tipos de producto, Habilidades, Nacionalidad) deben aparecer todos los registros si son menos de 10 o un mínimo de 10.
-
- 2.- Para tablas que representen entidades con información permanente o de largo plazo (Ejemplos: Clientes, Proveedores, Productos, Alumnos, Empleados) cargar un mínimo de 20 registros.
-
- 3.- Para tablas que representen asociaciones o entidades con información transaccional (Ejemplos: Préstamos, Compras, Ventas, Unidades producidas.) cargar un mínimo de 50 registros por tabla. Es necesario que el código para la carga de datos, sea también parte del script.
-
-
-
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
-
- Importante: Toda la documentación que generen en torno al proyecto deberá mostrar la imagen corporativa de tu equipo y mantener consistencia gráfica en aspectos como fuentes tipográficas, colores o sombreados, imágenes. márgenes y alineación.
-
- Entrega por medio del repositorio de equipo en Bitbucket o GitHub.
-
- Este avance debe incluir:
-
- Avance del 30% de la implementación de requisitos funcionales: Para la entrega de esta sección se requiere que envíes el código y los scripts de la base de datos. Se sugiere que se implementen los requisitos de mayor valor y riesgo, considerando el dominio técnico que tienes hasta el momento. (6 puntos)
-
- Diseño y ejecución de pruebas: Haciendo uso de la metodología para traducir casos de uso a casos de prueba, se espera que tengan diseñadas el 30% de las pruebas de tu aplicación, las hayan ejecutado, y hayan realizado las correcciones pertinentes. (6 puntos)
-
- Usabilidad: La interfaz ofrecida por la aplicación, debe mostrar evidencia de haber sido diseñada con base en los lineamientos descritos en la lectura de las "8 reglas de oro", además de reflejar los ajustes que el cliente haya sugerido (por lo que es necesario presentarle un prototipo funcional con anticipación). La interfaz debe mostrar evidencia de al menos 3 mejoras resultantes de la evaluación heurística realizada por tus compañeros, para ello deben mostrar los reportes de usabilidad y la nueva interfaz. (4 puntos)
-
- Reportes: Se espera que tengan identificado el detalle del contenido de cada uno de ellos, es decir, encabezados, detalle, pie de página, gráficos esperados, etc. El detalle debe ser a nivel de campo y basta que "Anexe" una imagen a una forma para verlo desde el prototipo. (2 puntos)
-
-
- Identificar el ambiente de producción: Se espera una breve descripción donde se identifique de manera breve y precisa el proveedor de alojamiento en el cual quedará desplegado el sistema. Se recomienda que esta información se comparta con el cliente para elaborar un acuerdo sobre la inversión. (2 puntos)
-
Para buscar un proveedor de alojamiento considera que debes asegurarte que soporte tu stack tecnológico (node+mysql). Además si tu aplicación maneja datos personales, para cumplir con la Ley Federal de Protección de Datos Personales es necesario que los datos se comuniquen por medio de un protocolo seguro como HTTPS. Para utilizar HTTPS es necesario contar con un certificado de seguridad SSL proporcionado por una Autoridad Certificadora. Muchos proveedores de alojamiento ofrecen este servicio con un pago extra; o bien puedes generarlo tu mismo si el proveedor de alojamiento ofrece acceso a consola (ssh) con letsencrypt como DigitalOcean, o si el proveedor soporta letsencrypt.
- Importante: Toda la documentación que generen en torno al proyecto deberá mostrar la imagen corporativa de tu equipo y mantener consistencia gráfica en aspectos como fuentes tipográficas, colores o sombreados, imágenes. márgenes y alineación.
-
- Recuerden incluir la actualización de su plan de trabajo.
-
- Por medio de su repositorio del proyecto con el tag avance4.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/av6.html b/backup/av6.html
deleted file mode 100644
index 4dda930..0000000
--- a/backup/av6.html
+++ /dev/null
@@ -1,128 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Avance de Proyecto 5: Versión Beta del Sistema
-
-
-
-
-
-
-
-
-
-
-
-
-
Avance de Proyecto 5: Versión Beta del Sistema
-
-
-
-
-
-
-
- Modalidad
-
-
-
- Colaborativa.
-
-
-
-
-
-
-
-
-
- Instrucciones
-
-
-
- Este avance debe incluir:
-
- Avance del 60% de la implementación de requisitos funcionales: Codificación de la aplicación versión Beta: La aplicación debe mostrar evidencia de que se han atendido la funionalidad que le da valor al proceso de negocio del cliente, no sólo mostrar que funciona la gestión de catálogos "ABC de la aplicación". (9 puntos)
-
- Diseño y ejecución de pruebas: Haciendo uso de la metodología para traducir casos de uso a casos de prueba, se espera que tengan diseñadas el 60% de las pruebas de tu aplicación, las hayan ejecutado, y hayan realizado las correcciones pertinentes. (9 puntos)
-
- Aceptación de requisitos funcionales por parte del cliente: Se espera una evidencia (un documeno firmado) donde el cliente acepte al menos el 30% de los casos de uso implementados y probados. (6 puntos)
-
- Descripción de la arquitectura de ejecución: Se espera que elaboren un diagrama de despliegue para describir con claridad el ambiente de ejecución en producción. (3 puntos)
-
- Es importante que esta versión refleje los ajustes y recomendaciones hechas por el cliente, así como las que hayan identificado a la largo del semestre. (3 puntos)
-
-
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
-
- Importante: Toda la documentación que generen en torno al proyecto deberá mostrar la imagen corporativa de tu equipo y mantener consistencia gráfica en aspectos como fuentes tipográficas, colores o sombreados, imágenes. márgenes y alineación.
-
- Por medio de su repositorio del proyecto con el tag avance5.
-
- La entrega final debe incluir:
-
- Versión 1.0 del sistema: El sistema debe estar implementado en su totalidad, probado, y se debe mostrar evidencia de la corrección de las fallas detectadas durante el desarrollo. (20 puntos)
-
- Despliegue del sistema: El sistema debe estar desplegado en el ambiente de producción, además debe existir evidencia de que el cliente aceptó la totalidad de los requisitos comprometidos. Es evidente que el producto funcionando cumple con el acuerdo definido al inicio del curso, por ejemplo, si se comprometió la entrega de un manual o capacitación existe un documento que lo avale. (15 puntos)
-
- Presentación final: La presentación final utiliza un lenguaje adecuado para la audiencia. La presentación cuenta con una secuencia y estructura clara, haciendo un uso adecuado del tiempo disponible, así como una adecuada participación de cada uno de los integrantes del equipo. La aplicación se muestra por medio de un video. Al final de la presentación se logra el objetivo de dar por concluido el proyecto, o en su defecto se especifican con claridad los compromisos por cada una de las partes. (15 puntos)
-
-
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
-
- Importante: Toda la documentación que generen en torno al proyecto deberá mostrar la imagen corporativa de tu equipo y mantener consistencia gráfica en aspectos como fuentes tipográficas, colores o sombreados, imágenes. márgenes y alineación.
-
- Por medio de su repositorio del proyecto, en la rama master o main con el tag 1.0
-
Entrega final del proyecto: Versión 1.0 y presenación final ante el cliente.
-
-
-
-
-
-
-
- Modalidad
-
-
-
- Colaborativa.
-
-
-
-
-
-
-
-
-
- Instrucciones
-
-
-
- La entrega final debe incluir:
-
- Versión 1.0 del sistema: El sistema debe estar implementado en su totalidad, probado, y se debe mostrar evidencia de la corrección de las fallas detectadas durante el desarrollo. (40 puntos)
-
- Despliegue del sistema: El sistema debe estar desplegado en el ambiente de producción, además debe existir evidencia de que el cliente aceptó la totalidad de los requisitos comprometidos. Es evidente que el producto funcionando cumple con el acuerdo definido al inicio del curso, por ejemplo, si se comprometió la entrega de un manual o capacitación existe un documento que lo avale. (30 puntos)
-
- Presentación final: La presentación final utiliza un lenguaje adecuado para la audiencia. La presentación cuenta con una secuencia y estructura clara, haciendo un uso adecuado del tiempo disponible, así como una adecuada participación de cada uno de los integrantes del equipo. La aplicación se muestra por medio de un video. Al final de la presentación se logra el objetivo de dar por concluido el proyecto, o en su defecto se especifican con claridad los compromisos por cada una de las partes. (30 puntos)
-
-
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
-
- Importante: Toda la documentación que generen en torno al proyecto deberá mostrar la imagen corporativa de tu equipo y mantener consistencia gráfica en aspectos como fuentes tipográficas, colores o sombreados, imágenes. márgenes y alineación.
-
- Por medio de su repositorio de equipo de bitbucket.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/code_reviews.html b/backup/code_reviews.html
deleted file mode 100644
index 03e7471..0000000
--- a/backup/code_reviews.html
+++ /dev/null
@@ -1,159 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Política de trabajo en equipo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Política de trabajo en equipo
-
-
-
-
-
-
-
-
- Descripción
-
-
Una política es un principio de gobierno, típicamente usado como la base para la definición de procedimientos dentro de una organización. En nuestro caso, la política de trabajo en equipo, tiene como propósito maximizar el desarrollo de habilidades y la efectividad de los miembros del equipo, además de mejorar la calidad del producto final por medio de la detección temprana de defectos.
-
Las revisiones de código y la programación en parejas son buenas prácticas de la industria para remover defectos en el software y al mismo tiempo promueven el desarrollo de habilidades. Es por ello que incorporaremos estas prácticas en nuestro trabajo diario.
-
-
-
-
-
-
- Modalidad
-
-
- En pares.
-
-
-
-
-
-
-
- Objetivos
-
-
-
Encontrar defectos en el código
-
Desarrollar tus habilidades aprendiendo de los demás
-
Transmitir tus habilidades
-
Brindar y recibir retroalimentación de manera oportuna
-
Crear una comunidad de aprendizaje activo
-
-
-
-
-
-
- Política de trabajo en equipo
-
-
-
-
Es una buena práctica que vayas creando commits cada vez que avances en tu trabajo, esto permite ver el trabajo realizado y gestionar las versiones de tu código para que puedas modificarlo con confianza.
-
-
-
En la medida de lo posible, procura que todo tu trabajo en el proyecto se realice con la práctica Pair Programming. Incluso, puedes aplicar esta práctica también en los laboratorios.
-
La practica de Pair Programming, básicamente consiste en que 2 personas trabajan en 1 tarea, compartiendo 1 sola pantalla y teclado. Esto permite que el observador guíe y encuentre defectos en el momento que se inyectan, además de que ambos mejoran sus habilidades tanto de observar, como de la retroalimentación que reciben.
-
Para que esta práctica sea efectiva, traten de trabajar sin distracciones en periodos de alrededor de 45min + 10min de descanso, y ajusten conformen se sienten más cómodos y productivos.
-
Es indispensable un ambiente de respecto, confianza y altamente constructivo. El trabajo no debe realizarse para juzgar al compañero, sino para que ambos mejoren sus habilidades y para que mejore la calidad del producto.
-
-
-
Para el trabajo que no se realice en parejas, es posible también transferir habilidades y mejorar su calidad por medio de las revisiones de código.
-
Idealmente, antes de integrar a la rama develop, es importante crear un Pull Request y asignar como revisores a 2 miembros de tu equipo.
-
Para los revisores, se espera que además de encontrar defectos de claridad en el código, documentación, seguridad, eficiencia, y buenas prácticas, apoyen a los autores en mejorar su práctica o aprendan algo de ellos. Por lo que se pide que en la revisión, hagan comentarios enfocados en lo que sus compañeros pueden mejorar, o en lo que aprendieron del código que revisaron.
-
La retroalimentación debe ser constructiva y significativa. Es decir, no se aceptarán comentarios ofensivos hacia la persona, ni que desprecien el trabajo. De ser necesario incluye material de apoyo como un artículo. Describe claramente lo que se puede mejorar y cómo realizarlo, así como lo que potencialmente podría causar problemas. O bien, específicamente las lecciones aprendidas del código que se revisó.
En esta actividad se analizarán las diferencias entre BD y DBMS.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Que conozcas la diferencia entre DB y DBMS
-
Que desarrolles tu comepetencia de comunicación escrita
-
Que profundices en el dominio de HTLM5 y CSS3
-
Que desarrolles tus competencias de búsqueda y análisis de información
-
Desarrollar tu competencia de aprendizaje por cuenta propia
-
-
-
-
-
-
-
- Instrucciones
-
-
Haciendo uso de la bibliografía propuesta para el curso, y accediendo a fuentes formales en línea, genera un documento HTML5 donde contestes las siguientes preguntas:
-
¿Qué es entonces una base de datos - DB?
-
¿En qué casos es conveniente usar bases de datos?
-
¿Qué es un sistema de gestión de base de datos - DBMS?
-
Enlista y explica las funciones/responsabilidades que tienen los DBMS.
-
Ejemplifica y justifica en que proyecto de los que has realizado hubiera sido conveniente utilizar una base de datos.
El cuestionario se presentará como una sección/página HTML5. La entrega se realizará por medio de bitbucket con fecha límite el día lunes.
-
Soporta tu trabajo utilizando libros, revistas o sitios WEB. En cualquier caso, cita las fuentes
- utilizadas. (utilizar al menos 2 fuentes de referencia distintos. Por ejemplo un libro y una sitio web). NO está permitido hacer un copiado de párrafos a menos que sea citadas adecuadamente.
-
- ·
-
- Citar con el siguiente formato:
-
Para revista:
- Autor, título del artículo, revista y año.
-
-
Para libro:
- Autor, título del libro, páginas y año.
-
Para el sitio web: URL
-
Ejemplo de cómo citar:
-
Recientemente se ha acordado una clasificación de los algoritmos con base a la cantidad de memoria que ocupa en la computadora, dicha propuesta fue realizada en el año 2006 por Juan Pérez [1]
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/jurassicpark.html b/backup/jurassicpark.html
deleted file mode 100644
index e12476e..0000000
--- a/backup/jurassicpark.html
+++ /dev/null
@@ -1,186 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Examen de segundo parcial: Welcome to Jurassic Park
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Examen de segundo parcial: Welcome to Jurassic Park
-
-
-
-
Código de ética
- "Apegándome al Código de Ética de los Estudiantes del Tecnológico de Monterrey, me comprometo a que mi actuación en esta actividad de evaluación esté regida por la honestidad académica. En congruencia con el mismo, realizaré esta actividad de forma honesta y personal, para reflejar a través de ella mis conocimientos y competencias."
-
El barco sale de la isla a las 12:55pm. Cuentas con 170 minutos.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Competencias a observar
-
-
2. Implementa sistemas de bases de datos que satisfacen requisitos de información y permiten la escalabilidad del sistema
-
4. Desarrolla aplicaciones web con interacción asíncrona con el servidor y que consumen servicios web
-
-
-
-
-
-
Instrucciones
-
-
- Para observar el nivel mínimo de desarrollo de competencias esperado hasta este punto:
-
Monitorear la seguridad del Parque Jurásico (aka Jurassic Park) debe ser una tarea 24/7 y debe poder realizarse no sólo desde el War Room, sino desde cualquier lugar del mundo. Tu misión como “System Velociraptor” y tu actitud “It’s a UNIX System, I know this” consiste en elaborar una aplicación web para monitorear la excelente ;) ;) seguridad del parque.
-
-
-
-
La aplicación debe permitir a cualquier persona registrar un incidente de seguridad. El incidente debe contener la hora y fecha del incidente, el lugar del incidente y el tipo de incidente. El lugar y el tipo de incidente deben estar en tablas independientes en la base de datos. Así, si el parque se expande, las formas del sitio no tendrán que reprogramarse.
-
Los lugares del incidente inicialmente deben ser: Centro turístico, Laboratorios, Restaurante, Centro operativo, Triceratops, Dilofosaurios, Velociraptors, TRex, Planicie de los herbívoros.
-
Los tipos de incidente inicialmente deben ser: Falla eléctrica, Fuga de herbívoro, Fuga de Velociraptors, Fuga de TRex, Robo de ADN, Auto descompuesto, Visitantes en zona no autorizada.
-
Debido a la nueva política de la empresa "Comunicación abierta y honesta dentro y fuera de nuestra empresa”, la aplicación debe permitir a cualquier persona consultar los registros de incidentes del más reciente al más antiguo.
-
-
Nota 1: Recuerda que puedes manejar el guardado de las fechas y horas directamente con el motor de la base de datos, es decir, no necesitas un datepicker para manejar las fechas, ni generar la fecha desde php.
-
Nota 2: El examen es abierto, puedes consultar cualquier referencia (excepto comunicarte con tus compañeros).
-
-
- Para obtener los grados más altos esperados en el desarrollo de competencias:
-
Idealmente, la aplicación debe ser una RIA (Rich Internet Application), es decir, las interacciones con el servidor deben ser asíncronas.
-
Idealmente las interacciones con la base de datos deben ser por medio de procedimientos almacenados.
-
-
-
-
-
-
-
-
- Entregables por mensaje directo de slack a edjuarezp
-
-
1. URL de la aplicación funcionando
-
2. Enlace al repositorio con el código. Dentro del código debe estar el script de creación de la base de datos, y el script con los procedimientos almacenados.
-
3. En la página principal del repositorio se debe indicar dónde están los scripts de creación de la base de datos y de los procedimientos almacenados, así como los archivos de código donde se invocan los procedimientos almacenados.
-
-
-
-
-
-
-
- assessment
- Evaluación
-
-
-
-
-
Competencia
-
Nivel “Welcome to Jurassic Park” (Practicante)
-
Nivel “It’s a dinosaur” (Ing. Junior)
-
Nivel “We are going to make a fortune with this place” (Ing. Senior)
-
Nivel “It’s a UNIX System, I know this” (Arquitecto)
-
-
-
-
-
2. Implementa sistemas de bases de datos que satisfacen requisitos de información y permiten la escalabilidad del sistema
-
No hay evidencia de implementación de sistemas de bases de datos
-
Diseña un modelo de datos que atiende de manera parcial las necesidades de información de un sistema de cómputo.
-
Diseña un modelo de datos que atiende las necesidades de información de un sistema de cómputo considerando los posibles cambios que se presenten en el futuro. Es evidente el uso adecuado de consultas para la extracción de datos.
-
Diseña un modelo de datos que atiende las necesidades de información de un sistema de cómputo considerando los posibles cambios que se presenten en el futuro. Es evidente el uso adecuado de consultas para la extracción de datos. Hay técnicas de automatización para creación de estructuras de bases de datos, cargas masivas de datos y procedimientos almacenados.
-
-
-
4. Desarrolla aplicaciones web con interacción asíncrona con el servidor y que consumen servicios web
-
No hay evidencia del desarrollo de una aplicación con comunicación entre un cliente y un servidor
-
Desarrolla aplicaciones web sencillas con un uso adecuado de las peticiones HTTP, manejo correcto de sesiones, estilo arquitectónico MVC e interacción con una base de datos con operaciones para crear, modificar y borrar datos, así como consultas complejas y un nivel básico de seguridad.
-
Desarrolla aplicaciones web enriquecidas con un uso adecuado de las peticiones HTTP, manejo correcto de sesiones, estilo arquitectónico MVC e interacción con una base de datos con operaciones para crear, modificar y borrar datos, así como consultas complejas, un nivel básico de seguridad e interacción asíncrona para las características usadas más frecuentemente.
-
Se evalúa hasta el periodo final.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/lab0Slack.html b/backup/lab0Slack.html
deleted file mode 100644
index a73a25a..0000000
--- a/backup/lab0Slack.html
+++ /dev/null
@@ -1,145 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Laboratorio 0: Presentación en la plataforma de comunicación
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Laboratorio 0: Presentación en la plataforma de comunicación.
-
-
-
-
-
-
- info_outline
- Actividad: Presentación en la plataforma de comunicación.
-
-
En esta actividad te presentarás en el canal de comunicación del curso edjuarezp.slack.com
-
-
-
-
-
- group
- Modalidad
-
-
Individual.
-
-
-
-
-
- check
- Objetivos
-
-
Que todos los miembros del grupo se conozcan e identifiquen en la plataforma de comunicación.
-
Explorar la plataforma de comunicación del curso: Discord
-
-
-
-
-
-
- list
- Instrucciones
-
-
-
Ingresa a Discord y crea tu usuario con tu correo del Tec.
-
Una vez dentro de la plataforma, sigue el tutorial de uso que la misma plataforma te presenta la primera vez que la utilizas.
-
-
Edita tu perfil y agrega una foto para que el profesor y tus compañeros te ubiquen tanto en el aula como en la plataforma. Se recomienda que configures las preferencias sobre los correos y notificaciones que recibes de la plataforma.
-
-
De manera automática te unirás al canal del curso #general , el cual servirá como medio oficial de comunicación.
-
-
Preséntate en tu primer mensaje en el grupo, incluyendo:
-
-
Algunas de tus aficiones e intereses que desees compartir.
-
Tu experiencia previa en construcción de software si es que cuentas con ella.
-
Tus compromisos como miembro de un equipo de desarrollo este semestre.
-
Tus expectativas del curso.
-
-
-
Registra también en tu malla la información anterior, y una vez que termines, puedes marcar como completado este laboratorio.
-
-
Utiliza el canal de manera frecuente para preguntar tus dudas, apoyar a tus compañeros resolviendo sus dudas y retroalimentarlos, además de compartir cualquier enlace relevante relacionado con el curso. La idea es formar una comunidad activa de aprendizaje.
-
-
Para cualquier duda sobre evaluaciones o cualquier aspecto que deba ser tratado en privado sólo con el profesor, puedes enviarle un mensaje directo.
-
-
-
-
-
-
-
attachmentRecursos
-
-
-
-
-
- offline_pin
- Especificaciones de entrega
-
-
No aplica.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/lab10phpBD.html b/backup/lab10phpBD.html
deleted file mode 100644
index 77553a3..0000000
--- a/backup/lab10phpBD.html
+++ /dev/null
@@ -1,160 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Lab 10: Manejo de sesiones con php
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Lab 10: Manejo de sesiones con php
-
-
-
-
-
-
-
-
- Descripción
-
-
En esta actividad veremos cómo manejar sesiones, y subir archivos con php.
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Continuar con el dominio del lenguaje php
-
Aprender a hacer consultas y manipular registros de una
- bases de datos con php
-
Conocer la capa de datos de una arquitectura multicapas
Desarrolla tu propia biblioteca util.php tomando como base el ejemplo de la presentación. Modifica las funciones según tus necesidades e incrementa su funcionalidad.
-
Utilizando alguno de tus laboratorios anteriores (o si lo prefieres empieza de 0), con ayuda de tu biblioteca, elabora una aplicación php que procese al menos una forma e interactúe con una base de datos para insertar un registro, y posteriormente haga una consulta de los registros de una tabla.
-
-
Utiliza una arquitectura MVC
-
Debes de validar los datos del lado del servidor, si hay
- alguno incorrecto o faltante, la aplicación debe mostrar los
- mensajes correspondientes en la forma para que el usuario
- pueda corregirlos. También debe realizar validaciones al
- insertar el registro en la base de datos.
-
Si los datos de la forma son correctos la aplicación debe
- llevar a cabo algún procesamiento de los datos y presentar
- la información en una página html5.
-
La aplicación debe tener coherencia y cierto nivel de
- complejidad.
-
Recuerda que tu aplicación debe ser agradable para el
- usuario, debe tener un CSS (y posiblemente JavaScript), y
- que las preguntas deben contestarse en alguna página dentro
- de tu sitio.
-
Aparte de los archivos php, html, scss, js que pudiera
- utilizar tu aplicación, entrega también el script de
- creación de la bd de datos.
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Por qué es una buena práctica separar el modelo del
- controlador?.
-
¿Qué es ODBC y para qué es útil?
-
¿Qué técnicas puedes utilizar para evitar ataques de SQL
- Injection?
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
A través de Bitbucket
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-2
\ No newline at end of file
diff --git a/backup/lab10rutas_formas.html b/backup/lab10rutas_formas.html
deleted file mode 100644
index 4da679b..0000000
--- a/backup/lab10rutas_formas.html
+++ /dev/null
@@ -1,168 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lab 10: Rutas y formas
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Lab 10: Rutas y formas
-
-
-
-
-
-
- Descripción
-
-
En esta actividad veremos cómo programar desde el servidor un módulo simple de ruteo y cómo mandarle datos al servidor.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Entender cómo responder desde el servidor a las diferentes rutas para poder diseñar y programar el ruteo de una aplicación.
-
Entender las diferentes maneras de enviar datos a un servidor por medio de HTTP, para poder realizar un procesamiento con estos datos.
-
Entender cómo funciona el ambiente de ejecución de Node.js, y su forma asíncrona de manejo de eventos.
-
-
-
-
-
-
-
- Instrucciones
-
-
-
Sigue la demostración del profesor en la sesión de clase.
-
Utilizando alguno de tus laboratorios anteriores (o si lo prefieres empieza de 0), elabora una aplicación web con las siguientes características:
-
-
La aplicación debe poder responder al menos a 3 rutas diferentes, y mandar una respuesta HTTP 404 cuando la ruta no existe.
-
En alguna de las rutas, la aplicación debe contener al menos 1 forma que debe enviar datos al servidor por POST. El servidor debe reaccionar ante este envío y guardar los datos en un archivo de texto dentro del mismo servidor.
En esta actividad instalaremos y exploraremos Express, un framework de Node.js para hacer desarrollo en el back-end.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Preparar el ambiente de trabajo con Node.js + NPM + Express
-
Entender lo que son los frameworks de desarrollo web de back-end.
-
Explorar Node.js + Express
-
-
-
-
-
-
-
- Instrucciones
-
-
Verifica que ya cuentes con npm ejecutando el comando npm -V. En caso negativo, instala npm y node.
-
Haz que tu trabajo sea un proyecto de npm ejecutando npm init desde tu carpeta de trabajo, y sigue las indicaciones del comando. Observa y analiza el contenido del archivo package.json
-
Si deseas instalar nuevos paquetes, utiliza npm install [nombre_paquete]. Puedes probar con npm install --save-dev nodemon, un paquete que sirve para que automáticamente se reinicie el servidor cada vez que haces un cambio en un archivo. El argumento --save-dev sirve para que sólo se instale el paquete mientras te encuentras en el entorno de desarrollo. Observa nuevamente el archivo package.json, y notarás que algunas líneas ahora incluyen nodemon, particularmente "start": "nodemon app.js". Si no existe, modifica la sección scripts para incluirla. Ahora podrás iniciar el servidor con npm start, que iniciará nodemon.
-
Instala Express desde tu carpeta de trabajo con npm install --save express.
- Observa que utilizamos --save para que el paquete esté disponible tanto en desarrollo como en producción.
-
-
-
-
Sigue la demostración del profesor en la sesión de clase.
-
-
Estructura básica de una aplicación con express:
-
-
-const express = require('express');
-const app = express();
-
-//Middleware
-app.use((request, response, next) => {
- console.log('Middleware!');
- next(); //Le permite a la petición avanzar hacia el siguiente middleware
-});
-
-app.use((request, response, next) => {
- console.log('Otro middleware!');
- response.send('¡Hola mundo!'); //Manda la respuesta
-});
-
-app.listen(3000);
-
-
-
-
-
-
Para agregar rutas:
-
-
-app.use('/ruta', (request, response, next) => {
- response.send('Respuesta de la ruta "/ruta"');
-});
-
-
-
-
-
Para instalar un paquete para manipular fácilmente los datos de las peticiones: npm install --save body-parser. Para utilizar el paquete:
-
Para limitar las rutas a un tipo de petición en particular, en lugar de use(), puedes usar, por ejemplo, get() o post().
-
-
Si los archivos comienzan a crecer, es importante reestructurarlos semánticamente en módulos para que sean más fáciles de mantener. Puedes separar un archivo en express utilizando el ruteador de express. Por convención, crearemos el nuevo archivo en una carpeta routes:
-
Para determinar el estado HTTP de una respuesta, puedes utilizar el método status() en el objeto de la respuesta.
-
Utilizando alguno de tus laboratorios anteriores (o si lo prefieres empieza de 0), elabora una aplicación web con las siguientes características:
-
-
La aplicación debe poder responder al menos a 5 rutas diferentes, distribuidas en al menos 2 módulos, y mandar una respuesta HTTP 404 cuando la ruta no existe.
-
En alguna de las rutas, la aplicación debe contener al menos 1 forma que debe enviar datos al servidor por POST. El servidor debe reaccionar ante este envío y guardar los datos en un archivo de texto dentro del mismo servidor.
Realiza el ejercicio de la presentacin y en contesta la
- pregunta en algn lugar dentro del sitio, ya sea que la
- despliegues dentro del mismo servlet, o bien incluyas un
- enlace a otra pgina donde muestres la pregunta y su
- respuesta. Entrega tu sitio por Blackboard.
-
-
-
-
Preguntas
-
-
Qu relacin tiene un servlet con la arquitectura MVC?
Agrega funcionalidad a la aplicacin del tutorial donde se
- interacta con mySql (o si lo prefieres inicia una nueva
- aplicacin o utiliza otra aplicacin). Entre la funcionalidad
- que agregues debes insertar o modificar algn registro de la
- base de datos.
-
-
Recuerda que tu aplicacin debe ser agradable para el
- usuario, y que las preguntas deben contestarse en alguna
- pgina dentro de tu sitio.
-
Aparte de los archivos de tu aplicacin, entrega tambin
- el script de creacin de la bd de datos.
-
-
-
-
-
Preguntas
-
-
Qu diferencias en cuanto a desempeo observas con respecto
- a php? a qu se deben estas diferencias?
-
Qu ventajas observas que tiene JEE sobre php?
-
Pensando en tu proyecto anterior, y considerando que ahora
- conoces la tecnologa JEE, en qu lo desarrollaras y por qu
- razones?
-
-
-
-
-
-
-
-
-
Recursos
-
-
-
-
-
-
-
-
-
-
-
-
Especificaciones de
- entrega
-
-
-
-
-
-
A travs de Blackboard
-
-
-
-
-
-
-
-
DR Tecnolgico de Monterrey Campus Quertaro|
- Departamento de Desarrollo Acadmico| Mxico, 2012
En esta actividad instalaremos y exploraremos cómo generar HTML dinámico desde el back-end.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Generar desde el back-end, interfaces dinámicas HTML5 para el cliente por medio de un motor de templates y que incorporen partials.
-
-
-
-
-
-
-
- Instrucciones
-
-
Recuerda que para generar una aplicación es necesario inicializar el proyecto con npm init, e instalar express y body-parser con npm install --save [nombre_paquete]. Si es necesario, recuerda configurar el archivo package.json para incluir el script que quieres que se ejecute con npm start.
-
-
-
-
Sigue la demostración del profesor en la sesión de clase.
-
-
Para devolver como respuesta un archivo html desde express, se requiere el módulo path, puedes incorporarlo con const path = require('path'); al inicio de tus archivos de código.
- Por convención, los archivos HTML se guardan en un directorio llamado views, en referencia a la capa de la vista de un estilo arquitectónio MVC. Para devolver un archivo HTML, se realiza de la siguiente forma:
-
- La función join sirve para armar la ruta, y la ventaja que tiene sobre hacerlo manualmente, es que considera el sistema operativo donde el código resida para que no tengas problemas si estás en MacOS, Linux o Windows.
-
- La variable global __dirname, contiene el directorio del sistema de archivos donde se encuentra tu aplicación, y el resto de los argumentos son cada uno de los directorios para llegar al archivo. Observa que después de __dirname, el argumento es '..' debido a que si estás en una carpeta de alguno de tus módulos, subirás un nivel en los directorios para llegar al nivel raíz y de ahí viajar a la carpeta views, para finalmente llegar al archivo el-archivo.html.
-
-
-
Por defecto, nuestro servidor no puede entregar un CSS como respuesta, entonces es necesario decirle que cuando un documento HTML requiere de un CSS, este archivo se mande de manera estática. Para ello debemos de configurar una carpeta de archivos estáticos, por conveción la llamaremos public, y al folder donde pondremos los archivos css lo llamaremos css. Para poder incluirlos dentro de los HTML lo podemos hacer con el método static de express:
-
Utilizando alguno de tus laboratorios anteriores (o si lo prefieres empieza de 0), elabora una aplicación web con las siguientes características:
-
-
La aplicación debe poder responder al menos a 5 rutas diferentes, distribuidas en al menos 2 módulos, y mandar una respuesta HTTP 404 cuando la ruta no existe.
-
En alguna de las rutas, la aplicación debe contener al menos 1 forma que debe enviar datos al servidor por POST. El servidor debe reaccionar ante este envío y guardar los datos en un archivo de texto dentro del mismo servidor.
-
-
-
-
Vamos a utilizar EJS como motor de templates para generar html de manera dinámica. Este motor lo estaremos usando en los ejemplos, pero eres libre de utilizar alguno diferente en tus laboratorios y proyecto.
-
Para instalar EJS: npm install --save ejs.
-
-
Para configurar EJS en Express, debemos indicarle a Express que vamos a utilizar como motor para la capa de las vistas EJS, y en seguida indicar por medio de una variable de configuración de Express, la carpeta donde estarán almacenados los archivos html correspondientes a las vistas, por convención, utilizaremos la carpeta views:
-
- Los archivos EJS (.ejs), son archivos que en su mayoría contienen código HTML, pero que también permiten escribir código JS, utilizando tags con el símbolo % de la siguiente forma: <% código de javascript %>.
- Para desplegar un template de EJS, lo hacemos con el método render, y como argumento ponemos el nombre del archivo .ejs sin poner la extensión del arhivo:
-
Refactoriza tu aplicación para que mantenga la funcionalidad pero ahora trabaje con un motor de vistas.
-
Mejora tu aplicación para que en lugar de guardar los datos en un archivo, ahora los despliegue en una de las páginas.
-
- Como has podido experimentar, mantener las 5 páginas implica estar copiando y pegando pedazos de código HTML, lo cual es un proceso propenso a errores. Los motores de vistas como EJS, permiten reutilizar código sin la necesidad de estar copiando y pegando. Una estrategia para ello, es la utilización de partials, que son pedazos reutilizables de código de una vista. Por convención, dentro de la carpeta views, crearemos una carpeta includes en donde pondremos nuestroas partials.
- Para incluir un partial en una vista:
-
- <%- include('includes/head.ejs') %>
-
Observa el operador -. A diferencia del operador = que te protege de una inyección de código, el operador - no lo hace, pero esto es lo que permite que se inserte el código HTML de nuestro partial.
-
-
Refactoriza tu aplicación para hacer un uso efectivo de los partials con aspectos como el código del head de tu aplicación, y quizás de la barra de navegación (si cuentas con una) y del footer.
Completa el tutorial sobre subida de archivos con php en http://www.w3schools.com/php/php_file_upload.asp. Presta especial atención al atributo enctype de la forma, su valor es indispensable para poder subir un archivo.
Utilizando alguno de tus laboratorios anteriores (o si lo prefieres empieza de 0), elabora un mini sitio en el que manejes sesiones, y el usuario pueda subir una foto y posteriormente la pueda ver, como por ejemplo, un sitio donde el usuario requiera ejecutar una tarea que implique varios pasos.
-
-
Utiliza una arquitectura en capas, donde separes claramente la vista del controlador.
-
La aplicación debe tener coherencia y cierto nivel de complejidad.
-
La aplicación debe ser agradable para el usuario, debe tener un CSS propio o de terceros (y posiblemente javascript).
-
Las preguntas deben contestarse en alguna página dentro de tu sitio.
-
-
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Por qué es importante hacer un session_unset() y luego un session_destroy()?
-
¿Cuál es la diferencia entre una variable de sesión y una cookie?
-
¿Qué técnicas se utilizan en sitios como facebook para que el usuario no sobreescriba sus fotos en el sistema de archivos cuando sube una foto con el mismo nombre?
En esta actividad exploraremos el estilo arquitectónico Modelo-Vista-Controlador y lo implementaremos con node+express.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Entender el estilo arquitectónico Modelo-Vista-Controlador.
-
Diseñar aplicaciones con un estilo arquitectónico Modelo-Vista-Controlador.
-
Implementar aplicaciones con un estilo arquitectónico Modelo-Vista-Controlador.
-
-
-
-
-
-
-
- Instrucciones
-
-
Atiende a la explicación del profesor del tema MVC y pregunta tus dudas.
-
Recuerda que para generar una aplicación es necesario inicializar el proyecto con npm init, e instalar express, body-parser y un template engine como EJS con npm install --save [nombre_paquete]. Si es necesario, recuerda configurar el archivo package.json para incluir el script que quieres que se ejecute con npm start.
-
Por convención, nuestros controladores los pondremos en una carpeta controllers. Comenzaremos a mover la lógica que antes habíamos puesto en las vistas, hacia una variable que exportaremos del controlador. Utilizaremos de ejemplo el controlador a_controller.js:
-
El archivo con la ruta quedará más ligero, ya que únicamente tendrán nuestra ruta y la referencia hacia el controlador que utilizarán, quedando de la siguiente forma:
-
Para implementar la capa del modelo, por convención, utilizaremos una carpeta models. Ahí pondremos cada uno de los archivos de nuestro modelo. Es común tener un archivo de modelo por tabla de la base de datos. Un archivo de modelo, típicamente tiene la siguiente estructura:
-
-module.exports = class Game {
-
- //Constructor de la clase. Sirve para crear un nuevo objeto, y en él se definen las propiedades del modelo
- constructor(my_value) {
- this.attribute_1 = my_value;
- }
-
- //Este método servirá para guardar de manera persistente el nuevo objeto.
- save() {
-
- }
-
- //Este método servirá para devolver los objetos del almacenamiento persistente.
- static fetchAll() {
-
- }
-
-}
-
-
Para usar el modelo en el controlador:
-
-const Game = require('../models/game');
-
-//Para crear un objeto de nuestro modelo
-const game = new Game('Zelda');
-game.save();
-
-//Para recuperar la lista de objetos del modelo
-const games = Game.fetchAll();
-
-
Por el momento, podemos utilizar un arreglo para almacenar la información, entonces en al archivo del modelo podemos crear un arreglo const games = [], llenar en método save() con games.push(this); y fetchAll() con return games;
-
-
Refactoriza alguno de tus laboratorios anteriores o el avance de tu proyecto utilizando un estilo arquitectónico MVC. Otra opción es que crees una nueva aplicación utilizando este patrón.
-
Agrega una nueva funcionalidad que toque todos los puntos clave de la arquitectura.
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Qué beneficios encuentras en el estilo MVC?
-
¿Encuentras alguna desventaja en el estilo arquitectónico MVC?
Actividad: Lab 14: JSP
- Mdulo: Paradigmas y aplicaciones cliente-servidor
-
-
-
Descripcin
-
-
-
-
-
-
En esta actividad haremos una introduccin a las Java Server
- Pages.
-
-
-
-
-
Modalidad
-
-
-
-
-
-
Individual.
-
-
-
-
-
Objetivos de la
- actividad
-
-
-
-
-
-
Que conozcas las tecnologas JEE
-
Realizar una aplicacin con tecnologas JEE, utilizando JSP's.
-
-
-
-
-
Instrucciones
-
-
-
-
-
-
Revisa la presentacin
- Introduccin a JSP y realiza el
- ejercicio. Deberas entregarlo por Blackboard.
-
Realiza una aplicacin sencilla utilizando JSP's. Puedes
- tomar como base alguno de tus laboratorios anteriores.
-
-
Recuerda que tu aplicacin debe ser agradable para el
- usuario, y que las preguntas deben contestarse en alguna
- pgina dentro de tu sitio.
-
Si utilizaste alguna base de datos, aparte de los archivos
- de tu aplicacin, entrega tambin el script de creacin de
- la base de datos.
-
-
-
-
-
Preguntas
-
-
Qu ventajas y desventajas tiene programar en una jsp sobre
- un servlet?
-
Describe un buen estilo de arquitectura de software
- utilizando jsp, servlets, y clases para interactuar con la
- base de datos. Relacinalo con la arquitectura MVC. Puedes
- utilizar un diagrama. Por qu es importante esta
- arquitectura? Qu valor y qu desventajas presenta?
-
Qu ventajas y desventajas tiene el enfoque JEE sobre el
- enfoque php con respecto a la implementacin de una
- arquitectura de software MVC?
-
-
-
-
-
-
-
-
-
Recursos
-
-
-
-
-
-
-
-
-
-
-
-
Especificaciones de
- entrega
-
-
-
-
-
-
A travs de Blackboard
-
-
-
-
-
-
-
-
DR Tecnolgico de Monterrey Campus Quertaro|
- Departamento de Desarrollo Acadmico| Mxico, 2012
-
-
-
-
-
-
diff --git a/backup/lab14a.html b/backup/lab14a.html
deleted file mode 100644
index 8c65e15..0000000
--- a/backup/lab14a.html
+++ /dev/null
@@ -1,171 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lab 14: php y consultas dinámicas a la base de datos
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Lab 14: php y consultas dinámicas a la base de datos
-
-
-
-
-
-
-
-
- Descripción
-
-
En esta actividad conoceremos como conectarnos a una base de datos usando php.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Continuar con el dominio del lenguaje php
-
Aprender a hacer consultas y manipular registros de una bases de datos con php
Una vez que hayas completado los tutoriales y revisado la presentación, crearemos nuestra librería de manejo de base de datos.
-
-
- 1.- Crea un nuevo archivo y llámalo util.php.
-
-
- 2.- Haz una función llamada connectDb la cual validará que la conexión sea correcta.
-
-
-
- 3.- Haz una función llamada closeDB que recibirá como parámetro una conexión establecida previamente.
-
-
-
- 4.- Haz una función que te permita obtener todos los objetos de una base de datos.
- En esta ocasión, siguiendo con mi tabla "fruit", regresaré todo lo que se encuentra ahí.
-
-
-
- 5.- Haz por lo menos dos funciones que hagan una consulta a la base de datos con algunos parametros.
-
-
-
- 6.- Despliega las funciones hechas anteriormente en algunas vistas.
-
-
-
-
-
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Qué es ODBC y para qué es útil?
-
¿Qué es SQL Injection?
-
-
¿Qué técnicas puedes utilizar para evitar ataques de SQL Injection?
-
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
A través de tu repositorio personal (Bitbucket o GitHub)
Recuerda que para generar una aplicación es necesario inicializar el proyecto con npm init, e instalar express, body-parser y un template engine como EJS con npm install --save [nombre_paquete]. Si es necesario, recuerda configurar el archivo package.json para incluir el script que quieres que se ejecute con npm start.
-
Las cookies viajan en el header de la respuesta, por lo que para definir una cookie, lo podemos hacer en la capa del controlador por medio del método setHeader('Set-Cookie') de la respuesta HTTP:
-
- A partir de que definimos una cookie, el navegador mandará en el header de la petición, todas las cookies que corresponden al sitio. Para acceder a las cookies en el controlador, lo podemos hacer por medio del header 'Cookie' de la petición:
-
Para acceder a un valor de una cookie en particular, puedes hacer manipulando el string, por ejemplo con request.get('Cookie').split(';')[1].trim().split('=')[1]; o bien, con instalando algún módulo como cookie-parser.
-
-
Además del valor, puedes agregarle más propiedades a la cookie como fecha de expiración, segundos de vida, el dominio al que quieres que se envíe, o la propiedad Secure, que sólo enviará la cookie si viaja por HTTPS.
- Es importante tener cuidado con el uso de las cookies, ya que los usuarios pueden editarlas desde el navegador. Para que la cookie no pueda ser leída por el código js del navegador, se le puede agregar la propiedad HttpOnly. Esto protege de ataques XSS.
-
Para manejar sesiones de manera muy práctica, instalaremos el paquete express-session.
-
Para preparar el entorno para trabajar con sesiones, agregamos como middleware el manejo de sesiones:
-
-const session = require('express-session');
-
-app.use(session({
- secret: 'mi string secreto que debe ser un string aleatorio muy largo, no como éste',
- resave: false, //La sesión no se guardará en cada petición, sino sólo se guardará si algo cambió
- saveUninitialized: false, //Asegura que no se guarde una sesión para una petición que no lo necesita
-}));
-
-
Para utilizar las variables de sesión en un controlador:
-
- Si revisas la consola desde el navegador, podrás observar una cookie con tu variable de sesión y con el valor encriptado.
-
-
Para eliminar una sesión, lo cual es principalmente útil cuando un usuario sale de la aplicación, puedes hacerlo de la siguiente forma:
-
-exports.logout = (request, response, next) => {
- request.session.destroy(() => {
- response.redirect('/'); //Este código se ejecuta cuando la sesión se elimina.
- });
-};
-
-
-
Mejora alguno de tus laboratorios anteriores o avanza en tu proyecto haciendo un uso pertinente de sesiones y cookies. Otra opción es que crees una nueva aplicación para que explores la aplicación de estos conceptos.
-
En ocasiones, como por ejemplo para mandar mensajes de error al usuario, deseamos utilizar variables de sesión que tengan un tiempo de vida de sólo 1 petición. Estas variables se llaman flash. Si deseas utilizarlas, el paquete connect-flash lo hace sencillo.
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Qué beneficios encuentras en el estilo MVC?
-
¿Encuentras alguna desventaja en el estilo arquitectónico MVC?
Realiza una aplicacin donde muestres el uso de Java Beans,
- JSTL, y Expression Language. Puedes tomar como base alguno de
- tus laboratorios anteriores y la aplicacin de ejemplo de este
- laboratorio.
-
-
Recuerda que tu aplicacin debe ser agradable para el
- usuario, y que las preguntas deben contestarse en alguna
- pgina dentro de tu sitio.
-
Si utilizaste alguna base de datos (no es obligatorio),
- aparte de los archivos de tu aplicacin, entrega tambin el
- script de creacin de la base de datos.
-
-
-
-
-
Preguntas
-
-
Describe 3 Tags de JSTL distintas a las vistas en la
- presentacin.
-
-
-
-
-
-
-
-
-
Recursos
-
-
-
-
-
-
-
-
-
-
-
-
Especificaciones de
- entrega
-
-
-
-
-
-
A travs de Blackboard
-
-
-
-
-
-
-
-
DR Tecnolgico de Monterrey Campus Quertaro|
- Departamento de Desarrollo Acadmico| Mxico, 2012
-
-
-
-
-
-
diff --git a/backup/lab15masDAW.html b/backup/lab15masDAW.html
deleted file mode 100644
index 803dd1d..0000000
--- a/backup/lab15masDAW.html
+++ /dev/null
@@ -1,165 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- DAW Laboratorio 16: Más allá de php en el desarrollo web
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
DAW Laboratorio 16: Más allá de php en el desarrollo web
-
-
-
-
-
-
-
-
- Descripción
-
-
En esta actividad exploraremos más opciones sobre Construcción de software y toma de decisiones.
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Que conozcas y seas capaz de utilizar otra tecnología de desarrollo web diferente a php, o bien que conozcas algún framework de desarrollo web en php.
-
Desarrollar tu capacidad de autoaprendizaje.
-
Que generes opciones tecnológicas para desarrollar tu siguiente proyecto.
-
-
-
-
-
-
- Instrucciones
-
-
-
Revisa los siguientes enlaces sobre frameworks de desarrollo web para php:
- Revisa los siguientes enlaces sobre tecnologías para el desarrollo web:
-
Realiza una aplicacin utilizando tecnologas JEE bajo una
- arquitectura MVC. Puedes tomar como base alguno de tus
- laboratorios anteriores y la aplicacin de ejemplo de este
- laboratorio.
-
-
Recuerda que tu aplicacin debe ser agradable para el
- usuario, y que las preguntas deben contestarse en alguna
- pgina dentro de tu sitio.
-
Entrega tu script de creacin de la base de datos.
-
-
-
-
-
Preguntas
-
-
Busca 3 frameworks que le den soporte al desarrollo de
- aplicaciones web en JEE utilizando la arquitectura MVC. Indica
- el propsito de cada uno, sus caractersticas principales, y
- el sitio principal del framework.
-
-
-
-
-
-
-
-
-
Recursos
-
-
-
-
-
-
-
-
-
-
-
-
Especificaciones de
- entrega
-
-
-
-
-
-
A travs de Blackboard
-
-
-
-
-
-
-
-
DR Tecnolgico de Monterrey Campus Quertaro|
- Departamento de Desarrollo Acadmico| Mxico, 2012
DR Tecnolgico de Monterrey Campus Quertaro|
- Departamento de Desarrollo Acadmico| Mxico, 2012
-
-
-
-
-
-
diff --git a/backup/lab17interaccion_bd.html b/backup/lab17interaccion_bd.html
deleted file mode 100644
index 0b2417d..0000000
--- a/backup/lab17interaccion_bd.html
+++ /dev/null
@@ -1,259 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lab 17: Interacción con la base de datos
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Lab 17: Interacción con la base de datos
-
-
-
-
-
-
-
-
- Descripción
-
-
En esta actividad comenzaremos con la interacción con una base de datos desde node.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Entender cómo interactúa una aplicación web con una base de datos.
-
Desarrollar aplicaciones web que interactúen con bases de datos.
-
-
-
-
-
-
-
- Instrucciones
-
-
Para interactuar con una base de datos, debemos crear la base de datos. Para este laboratorio usaremos MySQL, sin embargo, la lógica es muy similar si decides trabajar con cualquier otro motor de bases de datos. Crea tu base de datos y crea algunas tablas y ponles algunos datos, de manera similar a la demostración del profesor.
-
Para poder interactuar con el manejador de base de datos MySQL, ocuparemos el paquete mysql2.
-
Para poder conectarnos con la base de datos, utilizaremos el archivo database.js, el cual crearemos dentro de un folder con nuestras librerías de apoyo, típicamente nombrado util. El archivo se encargará de manejar las conexiones con nuestra base de datos:
-
- Asegúrate de cambiar los valores de los atributos del objeto de js para que coincidan con los de tu base de datos. Como podrás observar, se exporta una promesa. Las promesas permiten manejar fácilmente código que se ejecuta de manera asíncrona.
-
-
Para conectarnos con la base de datos y ejecutar consultas desde nuestra aplicación:
-
-const db = require('./util/database');
-
-db.execute('Consulta SQL por ejemplo: SELECT * FROM mi_tabla');
-
- Ahora, debido a que en database.js devolvimos una promesa, esto nos permite hacer algo después de que ejecutamos la consulta con el método .then(), e incluso manejar los errores con el método .catch(). Por ejemplo, si queremos recuperar los registros de la tabla videojuegos:
-
- En la varibale rows, tendremos cada uno de de los registros de nuestra consulta.
-
-
El código de interacción con la base de datos, si seguimos buenas prácticas, lo escribiremos siempre en nuestros modelos. Por lo que normalmente, el método fetchAll() de nuestros modelos quedaría con el siguiente formato:
-
- Y el código del controlador (asumiendo que tenemos un template de la vista llamado 'vista.html' que despliega el contenido de un arreglo de js llamado videojuegos):
-
- Como podrás ver, no se insertan los valores directamente en el string, sino se pone un signo de interrogación, esto es para evitar ataques de inyección de SQL, ya que el método execute, al pasar estos datos en un arreglo como segundo argumento, evita que si se inserta código SQL, éste no se ejecute y simplemente sea interpretado como un string.
- El código del controlador quedaría con el siguiente formato:
-
En ocasiones es necesario recuperar un registro en particular de la base de datos, y muchas veces queremos que esto pueda realizarse desde las rutas. Para indicarle al ruteador de express que un valor en una ruta es una varibale, podemos hacerlo agregando como prefijo el símbolo : seguido del nombre que le queremos dar a la variable:
-
- Y en el controlador para hacer uso de la variable:
-
-export.getVideojuego = (request, response, next) => {
- const id = request.params.videojuego_id;
- //Resto del código del controlador...
-}
-
-
-
Continúa mejorando tus laboratorios anteriores o tu proyecto agregándoles interacción con la base de datos. Asegúrate de al menos realizar una consulta que devuelva varios registros, una consulta que devuelva 1 sólo registro, una inserción, y una edición de un registro de la base de datos. Recuerda que siempre tienes también la opción de crear una nueva aplicación.
-
-
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Qué ventajas tiene escribir el código SQL únicamente en la capa del modelo?
-
En esta actividad exploraremos el proceso de autentificación de usuarios.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Entender cómo funciona la autentificación de usuarios.
-
Desarrollar aplicaciones web con autentificación de usuarios.
-
Desarrollar aplicaciones web con rutas protegidas.
-
Desarrollar aplicaciones web protegidas contra ataques CSRF.
-
-
-
-
-
-
-
- Instrucciones
-
-
Revisa junto con el profesor la presentación sobre Autentificación
-
- Crea la funcionalidad para registrar nuevos usuarios. Recuerda validar que el usuario no exista. Es importante que cuando se guarda un password, lo encriptemos con un método no desencriptable, para que si alguien tiene acceso a la base de datos, no pueda descifrar el password. Un paquete que nos puede servir para ello es bcryptjs, por lo que hay que instalarlo con npm install --save bcryptjs.
- Para encriptar los passwords:
-
-//En un modelo
-const bcrypt = require('bcryptjs');
-
-//Dentro del método del modelo que crea el usuario
-//El segundo argumento es el número de veces que se aplica el algoritmo, actualmente 12 se considera un valor seguro
-//El código es asíncrono, por lo que hay que regresar la promesa
-return bcrypt.hash(password, 12);
-
-
-
Crea la funcionalidad para que un usuario se autentifique. Para ello deberás realizar una consulta a la base de datos recuperando el usuario (si es que existe)y posteriormente, comparar el password introducido por el usuario con el password encriptado en la base de datos. Esto se puede lograr en el controlador de la siguiente forma:
-
Para mejorar la seguridad de nuestro sitio, es importante proteger las rutas. En el código anterior definimos una variables de sesión (isLoggedIn) para indicar que el usuario está autentificado. Esta variable nos puede ayudar en los controladores para proteger una ruta en particular:
-
-exports.ruta = (request, response, next) => {
- if (!request.session.isLoggedIn) {
- return response.redirect('/login');
- }
- //Resto del código de la ruta...
-}
-
- Una alternativa más elegante y sin repetición de código, es implementando la protección en un middleware. Esto lo podemos hacer en un nuevo archivo, por ejemplo, is-auth.js:
-
Nuestro trabajo hasta ahora, ha sido suficientemente bueno para usuarios de confianza. Sin embargo, la web no es un lugar seguro. Por ello es importante preparar nuestras aplicaciones para protegernos contra usuarios malintencionados. Un ataque común es el Cross-Site Request Forgery (CSRF), el cual implica aprovecharse de una sesión de otro usuario, comúnmente perpretado desde una página que parece la oficial pero que en realidad no lo es.
- Para evitar ataques de CSRF, tenemos que asegurar que nuestros usuarios estén trabajando sobre las vistas que nosotros proveemos. Esto lo podemos lograr por medio de un Token CSRF en nuestras formas y con ayuda de la instalación del paquete csurf.
- Para usar el paquete en nuestra aplicación: 1) tenemos que configurar el middleware, lo cual automaticamente protegerá todas nuestras peticiones POST:
-
-const csrf = require('csurf');
-const csrfProtection = csrf();
-
-//...Y después del código para inicializar la sesión...
-app.use(csrfProtection);
-
- Al proteger las peticiones POST, nuestra aplicación deja parcialmente de funcionar porque no estamos mandando el token CSRF desde nuestras formas, por ello tenemos que 2) Mandarle el token a las vistas desde el controlador:
-
Continúa mejorando tus laboratorios anteriores o tu proyecto, esta vez agregándoles autentificación de usuarios, protección de rutas y protección contra ataques CSRF. Recuerda que siempre tienes también la opción de crear una nueva aplicación.
En esta actividad exploraremos el modelo usado como estándar internacional para el control de acceso basado en roles (RBAC), y lo aplicarás en aplicaciones web.
Analiza junto con el profesor el siguiente MER que aplica el modelo RBAC.
-
- ¿Qué capacidades le puede dar a una aplicación la implementación de este modelo de datos con respecto al control de acceso?
-
-
- Implementen en equipo el modelo RBAC en una aplicación. La recomendación es que sea en su proyecto, pero si prefieren hacerlo en otra aplicación, no hay inconveniente.
- Algunos aspectos a considerar en la implementación:
-
-
1. Primero deben soportar el modelo a nivel de la base de datos, y poblar la BD con suficientes registros para que puedan verificar la correcta aplicación del modelo.
-
2. A nivel de la aplicación web, es necesario obtener los permisos del usuario en el momento en el que se autentifica, esto lo pueden realizar obteniendo los roles del usuario, y luego los permisos asignados a cada rol.
-
3. Posteriormente pueden generar la interfaz gráfica de manera dinámica de acuerdo a los permisos del usuario.
-
4. Es importante, validar el permiso requerido en cada ruta.
-
5. El nivel más avanzado de la implementación del modelo RBAC, implica la creación de una interface de usuario con la capacidad para gestionar las asignaciones de roles y permisos. Considerando las restricciones de su aplicación, evalúen e implementen lo que consideren necesario de esta interface.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Preguntas a responder:
-
-
-
¿En qué consiste el control de acceso basado en roles?
-
Investiguen y describan 2 sistemas, uno que aplique RBAC y uno que no. Realicen un análisis de las ventajas y desventajas de cada uno con respecto al control de acceso.
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/lab1Intro_inv_ciclo_desarrollo.html b/backup/lab1Intro_inv_ciclo_desarrollo.html
deleted file mode 100644
index ac9b0a7..0000000
--- a/backup/lab1Intro_inv_ciclo_desarrollo.html
+++ /dev/null
@@ -1,237 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- TC2005B Construcción de software y toma de decisiones: Lab 1: html5 e investigación
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Laboratorio 1: Introducción a las aplicaciones web, HTML5 y ciclo de vida de los sistemas de información
-
-
-
-
-
-
-
-
- Descripción
-
-
En esta actividad se abordarán brevemente los conceptos fundamentales para comenzar con el Construcción de software y toma de decisiones, y se hará un breve repaso sobre html5.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Comprender conceptos básicos del desarrollo web.
-
Comprender las principales etiquetas HTML (imágenes, hipervínculos, listas, tablas, formas)
-
Desarrollar interfaces de usuario sencillas en HTML
-
Explorar editores para desarrollo web
-
Comprender el ciclo de vida y de desarrollo de los sistemas de información
-
-
-
-
-
-
- Instrucciones
-
-
Revisa uno o varios de los siguientes sitios de editores html:
-
Instala y/o configura el (los) de tu preferencia. Eres libre de utilizar cualquier otro que te agrade que no esté en la lista anterior, y de ser así, comarte el enlace en el grupo de slack del curso para que los demás lo conozcan.
-
Crea una página personal o un pequeño sitio donde muestres el uso de las principales etiquetas HTML. Puedes hablar de tus principales proyectos o aficiones, incluir artículos interesantes tuyos, sobre tí, o sobre algo que te apasione. Usa componentes semánticos como por ejemplo <header>,
- <footer> o <strong> en lugar de <div id="header">, <div
- id="footer"> y <b>. El sitio o página debe incluir alguna forma con controles.
-
-
No utilices etiquetas desaprobadas.
-
Recuerda incluir tus datos e información de contacto (nombre, matrícula, correo electrónico).
-
Utiliza tu creatividad.
-
No es necesario que la página cuente con estilos, ni JavaScript.
-
Pon el nombre del editor HMTL que utilizaste y el enlace al sitio del editor como pie de página.
Responde las preguntas de la siguiente sección en algún lugar del sitio.
-
-
-
De ser necesario, soporta tus respuestas utilizando libros, revistas o sitios WEB. En cualquier caso, cita las fuentes utilizadas. Recuerda que si utilizas párrafos de otros autores, deben estar citados adecuadamente.
-
-
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Cuál es la diferencia entre Internet y la World Wide Web?
-
¿Cuáles son las partes de un URL?
-
¿Cuál es el propósito de los métodos HTTP: GET, HEAD, POST, PUT, PATCH, DELETE?
-
¿Qué método HTTP se debe utilizar al enviar un formulario HTML, por ejemplo cuando ingresas tu usuario y contraseña en algún sitio? ¿Por qué?
-
¿Qué método HTTP se utiliza cuando a través de un navegador web se accede a una página a través de un URL?
-
Un servidor web devuelve una respuesta HTTP con código 200. ¿Qué significa esto? ¿Ocurrió algún error?
-
¿Es responsabilidad del desarrollador corregir un sitio web si un usuario reporta que intentó acceder al sitio y se encontró con un error 404? ¿Por qué?
-
¿Es responsabilidad del desarrollador corregir un sitio web si un usuario reporta que intentó acceder al sitio y se encontró con un error 500? ¿Por qué?
-
¿Qué significa que un atributo HTML5 esté depreciado o desaprobado (deprecated)? Menciona algunos elementos de HTML 4 que en HTML5 estén desaprobados.
-
¿Cuáles son las diferencias principales entre HTML 4 y HTML5?
-
¿Qué componentes de estructura y estilo tiene una tabla?
-
¿Cuáles son los principales controles de una forma HTML5?
-
¿Qué tanto soporte HTML5 tiene el navegador que utilizas? Puedes utilizar la siguiente página para descubrirlo: http://html5test.com/ (Al responder la pregunta recuerda poner el navegador que utilizas)
-
- Sobre el ciclo de vida y desarrollo de los sistemas de información:
-
-
- ¿Cuál es el ciclo de vida de los sistemas de información?
-
-
- ¿Cuál es el ciclo de desarrollo de sistemas de información?
-
Una vez que termines, registra la actividad en tu plan de aprendizaje.
-
Guarda tu trabajo en tu computadora personal. En el laboratorio 2 se darán las instrucciones para la entrega de este laboratorio y de todos los siguientes.
En esta actividad exploraremos las heurísticas de usabilidad y llevarás a cabo una evaluación Heurística de Usabilidad.
-
-
-
-
-
-
- Modalidad
-
-
- Colaborativa.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Reconocer las heurísticas de usabilidad propuestas por Nielsen
-
Aprender a realizar evaluaciones heurísticas de usabilidad
-
Documentar los hallazgos de la evaluación de usabilidad por medio de Reportes de Aspecto de Usabilidad (UAR, por sus siglas en inglés).
-
-
-
-
-
-
- Instrucciones
-
-
Revisa junto con el profesor la presentación Usabilidad.
-
Con su equipo, lleven a cabo un análisis heurístico del proyecto de otro equipo.
-
Cada integrante deberá identificar al menos dos aspectos de usabilidad y deberán reportarlos a través de un Usability Aspect Report (UAR) al equipo correspondiente. Recuerda que en el UAR debes incluir:
-
-
La heurística de usabilidad que se está evaluando.
-
La explicación sobre por qué se está violando la heurística o por qué es una buena práctica de la heurística.
-
La evidencia de la heurística (captura de pantalla o diseño de la interfaz).
-
La severidad del problema o impacto del beneficio.
-
Si es un problema, su posible solución o desventajas potenciales.
-
-
Envía tu UAR al equipo que estás evaluando y también entrégala en tu repositorio personal o de equipo.
En esta actividad exploraremos el manejor de archivos con node + express.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Entender cómo funciona la subida de archivos a un servidor.
-
Desarrollar aplicaciones web que le permitan a los usuarios subir archivos que se almacenen en el servidor.
-
-
-
-
-
-
-
- Instrucciones
-
-
- Para poder enviar archivos al servidor, primero debemos preparar la petición HTTP desde el cliente para indicar que vamos a enviar un archivo por medio de una forma que va a enviar muchos datos en partes, y definir un control de entradas tipo file para que el usuario pueda seleccionar un archivo de su computadora:
-
- Para poder manejar archivos desde node, requerimos instalar el paquete multer. Para usar multer, tenemos que registrar el middleware y configurar cómo queremos manejar los archivos:
-
-const multer = require('multer');
-
-//fileStorage: Es nuestra constante de configuración para manejar el almacenamiento
-const fileStorage = multer.diskStorage({
- destination: (request, file, callback) => {
- //'uploads': Es el directorio del servidor donde se subirán los archivos
- callback(null, 'uploads');
- },
- filename: (request, file, callback) => {
- //aquí configuramos el nombre que queremos que tenga el archivo en el servidor,
- //para que no haya problema si se suben 2 archivos con el mismo nombre concatenamos el timestamp
- callback(null, new Date().toISOString() + '-' + file.originalname);
- },
-});
-
-//Idealmente registramos multer después de bodyParser (la siguiente línea ya debería existir)
-app.use(bodyParser.urlencoded({ extended: false }));
-
-//En el registro, pasamos la constante de configuración y
-//usamos single porque es un sólo archivo el que vamos a subir,
-//pero hay diferentes opciones si se quieren subir varios archivos.
-//'archivo' es el nombre del input tipo file de la forma
-app.use(multer({ storage: fileStorage }).single('archivo'));
-
Para acceder a los datos del archivo en el controlador, como por ejemplo si queremos guardar la ruta en la base de datos, podemos acceder a los atributos por medio del objeto request.file:
-
Para poder ver las imágenes de nuestro servidor en nuestros clientes, es necesario indicar que el directorio donde se encuentran las imágenes, también puede ser utilizado de manera estática:
-
- Es importante que a este folder le agreguemos un archivo index.html vacío, para que los contenidos del directorio no puedan ser listados.
-
-
Agrega la funcionalidad para trabajar con archivos en tus laboratorios anteriores o proyecto. Recuerda que siempre tienes también la opción de crear un prototipo para explorar estas características.
-
Si tienes formas de edición de archivos, es importante que agregues funcionalidad para que si no se sube un nuevo archivo, no se modifique el archivo que ya estaba almacenado.
-
Primero, debemos preparar al servidor para que en lugar de enviar una página HTML completa, envíe únicamente una parte de la página, texto, o datos. El ejemplo que realizaremos será con datos estructurados en formato JSON, que es lo ideal para que nuestro servidor pueda atender distintos tipos de clientes. Para poder manipular fácilmente las peticiones que llegan en formato JSON, es necesario registrar el middleware para manejar JSON que incluye bodyParser:
-
-app.use(bodyParser.json());
-
- Y para enviar las respuestas en formato JSON, en nuestro controlador tenemos que cambiar la respuesta por:
-
Para poder hacer una petición asíncrona desde el javascript de nuestro cliente, debemos identificar el evento que detonará la petición asíncrona. En el caso de los botones, debemos asegurarnos que NO sean del tipo submit, y también debemos de eliminar los tags form para evitar que se dispare una petición asíncrona.
- Un ejemplo de código js asíncrono en nuestro cliente, es el siguiente:
-
-const accion_asincrona = () => {
- const mensaje = document.getElementById('mensaje').value;
- //El token de protección CSRF
- const csrf = document.getElementById('_csrf').value;
-
- //función que manda la petición asíncrona
- fetch('/ruta/asincrona', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'csrf-token': csrf
- }
- body: JSON.stringify(data)
- }).then(result => {
- return result.json(); //Regresa otra promesa
- }).then(data => {
- //Modificamos el DOM de nuestra página de acuerdo a los datos de la segunda promesa
- //...
- }).catch(err => {
- console.log(err);
- });
-};
-
-document.getElementById('mi_boton').click = accion_asincrona;
-
-
-
-
Para alguno de tus laboratorios anteriores o tu proyecto (o si lo prefieres empieza con una nueva aplicación), integra al menos 1 componentes AJAX. Recuerda que el componente debe comunicarse con el servidor y debe actualizar alguna parte del sitio de manera asíncrona.
-
-
Indica en alguna parte del sitio cuáles fueron los componentes AJAX que utilizaste.
-
-
Utiliza una arquitectura MVC.
-
El componente debe tener coherencia y cierto nivel de complejidad.
-
-
Recuerda que tu aplicación debe ser agradable para el usuario, y que las preguntas deben contestarse en alguna página dentro de tu sitio.
-
-
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Qué importancia tiene AJAX en el desarrollo de RIA's (Rich Internet Applications?
-
¿Qué implicaciones de seguridad tiene AJAX? ¿Dónde se deben hacer las validaciones de seguridad, del lado del cliente o del lado del servidor?
Diseña e implementa un servicio web con php basado en REST, para ellos puedes utilzar SILEX o cualquier otro framework REST como Slim Framework, o Flight php. Considera al menos 2 operaciones en el servicio. Si gustas puedes utilizar alguno de tus laboratorios anteriores, y seleccionar una parte de la funcionalidad para implementarla como operaciones del servicio, o bien, diseñar un servicio completamente nuevo. Nombra la carpeta donde pongas tu servicio como lab20_servicio
Implementa (o utiliza una página o sitio que hayas desarrollado en laboratorios anteriores) un cliente que consuma las operaciones del servicio web. Recuerda responder las preguntas del laboratorio dentro del sitio. Nombra la carpeta donde esté la aplicación que consuma tu servicio como lab20_cliente
-
Si utilizaste alguna base de datos, también debes incluir el script de creación dentro de los archivos del servicio.
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿A qué se refiere la descentralización de servicios web?
-
¿Cómo puede implementarse un entorno con servicios web disponibles aún cuando falle un servidor?
En esta actividad configurarás tu sistema de control de versiones con git, el cual servirá para que realices todas tus entregas individuales del semestre.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Comprender los principios sobre los que trabajan los sistemas de control de versiones
-
Controlar las versiones del trabajo individual por medio de git. Particularmente dominar los comandos clone, add, commit y push.
Crea un repositorio remoto y asegúrate de que tu repositorio sea público para que tus compañeros y profesores puedan tener acceso a él.
-
Instala y configura git en tu entorno de desarrollo y sincroniza tu repositorio local con tu repositorio remoto.
-
Agrega el laboratorio 1 a tu repositorio local, y posteriormente sincroniza tu repositorio local con tu repositorio remoto
-
Añade la url pública de tu repositorio a tu malla de evaluación en la sección correspondiente, para que se pueda revisar y retroalimentar tu trabajo. Realizar esta acción es indispensable para tener calificación en los laboratorios. No realizarla implica 0 en TODAS las actividades que se entregan por medio del repositorio individual.
Instala y/o configura el (los) de tu preferencia. Eres libre de utilizar cualquier otro que te agrade que no esté en la lista anterior.
-
Crea una página personal o un pequeño sitio donde muestres
- el uso de las principales etiquetas HTML. Puedes hablar de tus
- principales proyectos o aficiones, incluir artículos
- interesantes tuyos, sobre tí, o sobre algo que te apasione.
- Usa componentes semánticos como por ejemplo <header>,
- <footer> o <strong> en
- lugar de <div id="header">, <div
- id="footer"> y <b>. El sitio o
- página debe incluir alguna forma con controles.
-
-
No utilices etiquetas desaprobadas.
-
Recuerda incluir tus datos e información de contacto
- (nombre, matrícula, correo electrónico).
-
Utiliza tu creatividad.
-
No es necesario
- que la página cuente con estilos, ni JavaScript.
-
Pon el nombre del editor HMTL que utilizaste y el enlace al sitio del editor como pie de página.
Pon las siguientes preguntas en algún lugar del sitio y
- dales respuesta.
-
-
-
Preguntas
-
-
¿Qué significa que un atributo HTML5 esté depreciado o desaprobado (deprecated)? Menciona algunos elementos de HTML 4 que en HTML5 estén desaprobados.
-
¿Cuáles son las diferencias principales entre HTML 4 y HTML5?
-
¿Qué componentes de estructura y estilo tiene una tabla?
-
¿Cuáles son los principales controles de una forma HTML5?
-
¿Qué tanto soporte HTML5 tiene el navegador que utilizas?
- Puedes utilizar la siguiente página para descubrirlo: http://html5test.com/ (Al responder la pregunta recuerda poner el navegador que utilizas)
Crea un CSS externo y agrégalo al documento(s) que elaboraste en el laboratorio de html para agregarle una buena presentación a tu sitio.
-
El CSS debe contener al menos un estilo definido con cada uno de los selectores y el estilo debe ser aplicado al documento. Puedes utilizar como guía o o ayudarte de algunos de los sitios web que se enlistan en la sección de Recursos, o en algún otro sitio.
-
-
Asegúrate que tus documentos HTML no tengan estilos en línea.
-
-
-
-
Para un mejor rendimiento de tu sitio, crea una versión minimizada de tu CSS y enlázala a tu sitio en lugar del CSS que acabas de crear. Puedes apoyarte de herramientas como http://cssminifier.com/
-
Agrega las preguntas con sus respectivas respuestas en el documento HTML
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
Como ingeniero de software ¿cuál es tu recomendación sobre el uso de !important en un CSS?
-
Si se pone una imagen de fondo en una página HTML, ¿por qué debe escogerse con cuidado?
-
Como ingeniero de software, ¿cuál es tu recomendación al elegir las unidades de un propiedad de estilo entre %, px y pt?
-
¿Por qué el uso de una versión minimizada del CSS mejora el rendimiento del sitio?
Escribe, prueba y corrige scripts de JavaScript para los siguientes problemas. Cuando se requiera escribir funciones, es necesario incluir un script para probar la función con un conjunto adecuado de casos de prueba. Para automatizar las pruebas, puedes utilizar console.assert() . Además, es importante permitir al usuario ver los resultados de las funciones en un documento HTML.
-
-
- 1:
- Entrada: un número pedido con un prompt. Salida: Una tabla con los números del 1 al número dado con sus cuadrados y cubos. Utiliza document.write para producir la salida
-
-
- 2:
- Entrada: Usando un prompt se pide el resultado de la suma de 2 números generados de manera aleatoria. Salida: La página debe indicar si el resultado fue correcto o incorrecto, y el tiempo que tardó el usuario en escribir la respuesta.
-
-
- 3:
- Función: contador.
- Parámetros: Un arreglo de números. Regresa: La cantidad de números negativos en el arreglo, la cantidad de 0's, y la cantidad de valores mayores a 0 en el arreglo.
-
- 4:
- Función: promedios. Parámetros: Un arreglo de arreglos de números. Regresa: Un arreglo con los promedios de cada uno de los renglones de la matriz.
-
- 5:
- Función: inverso.
- Parámetros: Un número. Regresa: El número con sus dígitos en orden inverso.
-
- 6:
- Crea una solución para un problema de tu elección (puede ser algo relacionado con tus intereses, alguna problemática que hayas identificado en algún ámbito, un problema de programación que hayas resuelto en otro lenguaje, un problema de la ACM, entre otros). El problema debe estar descrito en un documento HTML, y la solución implementada en JavaScript, utilizando al menos la creación de un objeto, el objeto además de su constructor deben tener al menos 2 métodos. Muestra los resultados en el documento HTML.
-
-
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Qué diferencias y semejanzas hay entre Java y JavaScript?
-
¿Qué métodos tiene el objeto Date? (Menciona al menos 5*)
-
¿Qué métodos tienen los arreglos? (Menciona al menos 5*)
-
¿Cómo se declara una variable con alcance local dentro de una función?
-
¿Qué implicaciones tiene utilizar variables globales dentro de funciones?
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/lab5FrontEnd.html b/backup/lab5FrontEnd.html
deleted file mode 100644
index a555743..0000000
--- a/backup/lab5FrontEnd.html
+++ /dev/null
@@ -1,170 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lab 5: Frameworks de estilo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Lab 5: Frameworks de estilo
-
-
-
-
-
-
- Descripción
-
-
En esta actividad exploraremos algunos de los frameworks de estilo para aplicaciones web, y aprenderás a usar uno de estos frameworks para mejorar la experiencia del usuario.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
- Conocer, explorar y aplicar frameworks de estilos para aplicaciones web.
-
-
-
-
-
-
-
- Instrucciones
-
-
-
-
Revisa los siguientes enlaces a sitios de frameworks de Front-end:
Selecciona uno de los frameworks. Coordínate con tu equipo para que cada uno de los miembros, seleccione un framework diferente y puedan compartir su experiencia.
-
-
-
Aplica el framework y sus componentes a alguno de tus laboratorios o proyectos anteriores, a algún prototipo que elabores de tu proyecto, o bien elabora una pequeña aplicación.
Con JavaScript y HTML5 [y quizás CSS], desarrolla una página' +
- " para validar passwords. La página debe tener una forma con 2"+
- " campos, el campo de password, y el campo de verificar"+
- " password. Utiliza al máximo tu creatividad e ingeniería para"+
- " que la página sea un validador de passwords de estado del"+
- " arte, con la mejor experiencia para el usuario.
"+
- '
Con JavaScript y HTML5 [y quizás CSS], desarrolla una página'+
- " para vender 3 productos de tu interés, con los precios y"+
- " promociones a tu gusto. La página debe permitir al usuario"+
- " escoger la cantidad de unidades de cada producto, y debe"+
- " mostrar el precio total, el IVA que se está cargando, y toda"+
- " la información que consideres pertinente para que la"+
- " experiencia del usuario sea la mejor. La página debe validar"+
- " los rangos de las unidades de cada producto.
"+
- '
[Opcional] Con JavaScript y HTML5 y CSS, desarrolla una página con'+
- " alguna temática o problema de tu interés. La página debe"+
- " contener una forma y debes realizar las validaciones necesarias."+
- " Después de validar la forma, la página debe desplegar"+
- " información relacionada con los datos introducidos en la forma,"+
- " es decir la solución del problema o información respectiva a"+
- " los datos introducidos.
"+
- '
A partir de un documento o sitio de los anteriores, agrega las siguientes características:' +
- '
' +
- '
Cambia el estilo de las letras de alguna parte del documento con eventos diferentes a onClick.
' +
- '
Para los campos de una forma, o para algún texto corto del documento, haz que de manera dinámica aparezca ayuda o información relevante o complementaria. Usa tu creatividad.
' +
- '
[Opcional]Investiga y prueba las funciones setInterval y setTimeout de JavaScript. De alguna manera incorpora una de ellas en el documento para agregarle una nueva característica al sitio.
' +
- '
[Opcional] Afortunadamente drag-and-drop ahora es parte del estándar HTML5 :) Utilizando como referencia el siguiente tutorial, incorpora esta característica a tu documento: http://www.w3schools.com/html/html5_draganddrop.asp
' +
- '
' +
- '
' +
- '
Responde a las preguntas en el documento HTML
' +
- '
Agrega al documento HTML una lista de enlaces con otras fuentes que hayas consultado para la realización de este laboratorio.
' +
- "";
- }
\ No newline at end of file
diff --git a/backup/lab6DynDocsJS.html b/backup/lab6DynDocsJS.html
deleted file mode 100644
index e357d3d..0000000
--- a/backup/lab6DynDocsJS.html
+++ /dev/null
@@ -1,176 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Lab 6: Documentos dinámicos con JavaScript
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Lab 6: Documentos dinámicos
- con JavaScript
-
-
-
-
-
- Descripción
-
-
En esta actividad profundizaremos en la manipulación de documentos HTML con JavaScript.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Profundizar en el dominio de JavaScript, HTML y CSS
-
Desarrollar competencias de programación orientada a eventos
-
Que seas capaz de elaborar documentos HTML dinámicos manipulándolos con JavaScript.
A partir de un documento o sitio de los que entregaste previamente (o si lo prefieres elabora uno nuevo), agrega las siguientes características:
-
-
Usando texto con estilos o alguna imagen, crea una marca de agua para tu documento HTML.
-
Cambia el estilo de las letras de alguna parte del documento con eventos diferentes a onClic.
-
Para los campos de una forma, o para algún texto corto del documento, haz que de manera dinámica aparezca ayuda o información relevante o complementaria. Usa tu creatividad.
-
Investiga y prueba las funciones setInterval y
- setTimeout de JavaScript. De alguna manera incorpora una de ellas en el documento para agregarle una nueva característica al sitio.
-
Afortunadamente drag-and-drop ahora es parte del estándar HTML5 :) Utilizando como referencia el siguiente tutorial, incorpora esta característica a tu documento: http://www.w3schools.com/html/html5_draganddrop.asp
-
-
-
Responde a las preguntas en el documento HTML
-
Agrega al documento HTML una lista de enlaces con otras fuentes que hayas consultado para la realización de este laboratorio.
-
-
-
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Cuáles son las diferencias entre los posibles valores de la propiedad position?
-
¿Cuáles son los valores estándar para la propiedad visibility?
En esta actividad exploraremos el uso de ramas para controlar e integrar diferentes versiones de código.
-
-
-
-
-
-
- Modalidad
-
-
- Colaborativa
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
- Conocer, explorar y aplicar frameworks de estilos para aplicaciones web.
-
-
-
-
-
-
-
- Instrucciones
-
-
-
-
- Presta mucha atención a la demostración del profesor en la sesión de clase. Es importante que preguntes conforme te van surgiendo dudas.
-
-
-
-
- En equipo, creen un repositorio para su proyecto en BitBucket, GitHub o GitLab.
-
-
-
-
- Una persona del equipo, deberá clonar el repositorio de manera local, y a partir de la rama main o master, deberá crear la rama develop, después creará una rama adicional y deberá agregar un archivo index.html y sincronizar la rama local, con la rama remota, para finalmente hacer un merge a través de un Pull Request con develop.
-
-
A partir de este momento, éste será su espacio de trabajo en su proyecto (a.k.a. Single Repository of Truth). Únicamente el código que se encuentre integrado en las ramas main o master y develop, formará oficialmente parte de su proyecto y se considerará como evidencia para evaluación.
-
-
-
- Una persona por equipo debe compartir el enlace del repositorio de equipo junto con los nombres y usuarios de todos sus miembros en el canal de discord del equipo, para que se pueda revisar y retroalimentar su trabajo.
-
-
También deberán agregar a su repositorio a los profesores con usuarios: black4ninja y strike277.Realizar esta acción es indispensable para tener calificación en los avances de proyecto. No realizarla implica 0 en TODAS las actividades que se entregan por medio del repositorio de equipo.
-
En ningún momento deberán existir commits directos a las ramas main, master o develop, solo merges a través de Pull Request que hayan sido validados por el equipo.
-
Deberán eliminar los branches personales o por features que vayan generando, por ejemplo: cuando todos hayan finalizado el trabajo deberán existir solo main o master y develop.
-
-
-
Algunos puntos importantes a considerar:
-
-
-Las ramas main o master y develop son de integración, nunca se escribe código directamente en ellas.
-
-Todo el código debe escribirse en ramas personales, derivadas siempre de la versión más reciente de develop.
-
-Idealmente, en cada sesión de trabajo debe crearse una nueva rama personal nombrada nombre/feature. Durante la sesión debe terminarse el trabajo y el código debe quedar estable para que al final de la sesión, la rama personal se integre a la rama develop.
-
-Idealmente, en cada sesión de trabajo deben realizarse varios commits, cada uno, con una unidad pequeña, lógica y completa de trabajo.
-
-Entre más tiempo pase entre una integración y otra, mayor es la probabilidad de conflictos, y más riesgoso es arreglarlos, por lo que es importante integrar el trabajo frecuentemente para reducir esta posibilidad.
Dise�a un documento XML para guardar informaci�n sobre
- alguna tem�tica de tu inter�s, o de impacto social, ambiental,
- o educativo. El documento debe incluir tags
- anidadas y atributos, utilizados de la manera m�s
- conveniente. Incluye al menos 4 ejemplos de datos de ejemplo.
- [Si te sientes muy motivado, adem�s puedes crear un XML
- Schema, para tu documento y que pueda utilizarse como
- especificaci�n para futuros documentos.]
-
Crea un CSS para el documento XML y �salo para desplegar el
- documento.
-
Crea una hoja de estilos XSLT y �sala para desplegar el
- documento.
-
-
Preguntas
-
-
�Cu�les son los objetivos de las tecnolog�as XML?
-
�Bajo qu� circunstancias son mejores las tags
- anidadas que los atributos de un tag?
-
�Bajo qu� circunstancias son mejores los atributos de un tag que las tags
- anidadas?
-
�Qu� ventajas tiene utilizar XML
- Schemas sobre DTD's?
-
�Cu�l es tu opini�n sobre el almacenamiento de datos en
- documentos XML?
En esta actividad haremos una introducción al desarrollo en el back-end con node.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Entender cómo funcionan las aplicaciones web
-
Preparar el ambiente de trabajo para hacer desarrollo en el back-end
-
Instalar y ejecutar un servidor de aplicaciones web
-
Atender peticiones HTTP desde el servidor y mandar respuestas HTTP
-
-
-
-
-
-
- Instrucciones
-
-
Sigue la demostración del profesor en la sesión de clase.
-
Instala Node.js. Node.js es un ambiente de ejecución de JavaScript, el cual, a diferencia de js que se ejecuta en el navegador, permite tener acceso al sistema de archivos de la computadora, y ejecutar programas como servidores web.
-
Sigue la demostración del profesor en la sesión de clase sobre los ejemplos básicos de node.
-
Escribe, prueba y ejecuta con node, scripts de js para los siguientes ejercicios y problemas. Muestra los resultados en consola:
-
-
Una función que reciba un arreglo de números y devuelva su promedio.
-
Una función que reciba un string y escriba el string en un archivo de texto. Apóyate del módulo fs.
-
Escoge algún problema que hayas implementado en otro lenguaje de programación, y dale una solución en js que se ejecute sobre node.
-
-
-
Sigue la demostración del profesor en la sesión de clase sobre los ejemplos básicos para crear un servidor web que se ejecute sobre node, reciba peticiones de un cliente, y le responda.
-
Crea una pequeña aplicación web que al enviar una petición al servidor, devuelva una de las páginas que creaste anteriormente en tus laboratorios.
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/laboratorios/lab8_access.html b/backup/laboratorios/lab8_access.html
deleted file mode 100644
index 71d6057..0000000
--- a/backup/laboratorios/lab8_access.html
+++ /dev/null
@@ -1,361 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- Lab 9: Bases de Datos de Escritorio (MS Access)
-
-
-
-
-
-
-
-
-
-
-
-
Laboratorio 9: Bases de Datos de Escritorio (MS Access)
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
Objetivos de aprendizaje
-
-
Usar un DBMS (Database Management System) de escritorio, Microsoft Access,
- para presentar algunas de las actividades necesarias para administrar bases de datos.
-
-
Nota: Las imágenes de este laboratorio fueron hechas usando Microsoft Access 2007
-
-
-
-
-
-
- Instrucciones
-
-
-
-
Crear una base de datos
-
-
La primera actividad en el proceso es crear una base de datos. En esta actividad se establece
- el lugar o espacio en el que físicamente será alojada una base de datos.
- En el caso de MS Access, el procedimiento es muy sencillo:
-
-
-
Iniciar el programa de Microsoft Access.
-
Selecciona crear una base de datos en blanco.
-
Selecciona la ruta (path) en que será creada la base de datos.
-
Deberás usar la extensión .mdb solo para Access 2003, para la versión 2007 puedes usar la extensión .accdb
-
Indica el nombre para la base de datos (te recomiendo usar tu matrícula) con la extensión .mdb
-
-
-
-
Crear tablas
-
Una definición operativa simple de base de datos relacional es "colección de tablas interrelacionadas".
- De aquí que una de las tareas básicas en la administración de bases de datos consista en crear y modificar tablas.
-
En el fólder de tablas de la base de datos que acabas de crear,
- utiliza el botón Nuevo y la opción Vista Diseño para crear las siguientes tablas, seleccionando como llave la columna subrayada.
-
En la versión 2007 una tabla será creada por default, simplemente en View cambia a Design View y te pedirá el nombre de la tabla para salvarla.
-
Nota: Para seleccionar una columna como llave, una vez que hayas definido su nombre y tipo, posiciónate
- sobre ella y has click sobre "Clave Principal", este es un ícono en forma de llave de la barra de herramientas.
- En la versión 2007 por default ya viene incluida una clave principal, la cual tiene el ícono de una llave en su lado derecho.
-
-
-
Tabla: Materiales
-
-
-
-
Columna
-
Tipo
-
-
-
Clave
-
Numérico (entero largo)
-
-
-
Descripción
-
Texto (100 caracteres)
-
-
-
Precio
-
Numérico (simple)
-
-
-
-
-
-
-
Tabla: Proyectos
-
-
-
Columna
-
Tipo
-
-
-
Número
-
Numérico (entero largo)
-
-
-
Denominación
-
Texto (100 caracteres)
-
-
-
-
-
-
-
Tabla: Proveedores
-
-
-
Columna
-
Tipo
-
-
-
RFC
-
Texto (15 caracteres)
-
-
-
RazonSocial
-
Texto (100 caracteres)
-
-
-
-
-
-
-
Tabla: Entregan
-
-
-
-
Columna
-
Tipo
-
-
-
Clave
-
Numérico (entero largo)
-
-
-
RFC
-
Texto (15 caracteres)
-
-
-
Número
-
Numérico (entero largo)
-
-
-
Fecha
-
Fecha/Hora
-
-
-
Cantidad
-
Numérico (entero largo)
-
-
-
-
- Importante: Ten cuidado con el campo Fecha, ya que es neceario definirlo con
- formato de fecha corta, ya que los datos de origen tienen definido ese formato de fecha.
-
-
-
- Tip: Para definir una llave de varias columnas, selecciona cada columna en el área
- gris situada a la izquierda del nombre de la columna, oprimiendo la tecla Shift.
- Cuando todas las columnas estén seleecionadas, oprimir el ícono de la llave.
-
-
-
-
Establecer las relaciones entre tablas.
-
Es muy conveniente que las relaciones entre tablas se establezcan desde un principio.
- este caso, la tabla Entregan tiene relaciones con las otras tres tablas que definimos.
- En la opción Herramientas --> Relaciones, se definen estas relaciones.
-
-
-
-
-
Agregar todas las tablas a la definición de relaciones.
-
-
- Para definir la relación entre Materiales y Entregan, selecciona la columna Clave de Materiales
- y arrastra el cursos hasta la columna Clave de Entregan. Es muy importante que sea en este órden.
- Marca el Check Box de Exigir Integridad Referenciada, concepto que revisaremos posteriormente.
-
-
-
- Por analogía, define las relaciones con Proyectos y Proveedores con Entregan.
-
-
-
-
-
Carga de datos.
-
-
Otra actividad frecuente en la administración de bases de datos consiste en cargar datos a las tablas,
- provenientes de otros sistemas o sencillamente de archivos que se han creado para este efecto.
-
-
- Los datos que vas a incorporar en cada tabla se encuentran en los siguientes archivos, descárgalos en tu computadora.
-
-
-
-
-
-
-
Nota: Chrome abre los documentos en lugar de descargarlos, utiliza otro buscador
- o guarda los archivos con click derecho ---> Descargar archivo enlazado.
-
-
- Los archivos csv son archivos de texto que tienen renglones en los que cada renglón corresponde
- a un registro o renglón de la tabla y en cada uno de estos renglones, los valores correspondientes
- a cada columa están separados por comas en estos archivos.
-
-
-
-
Cargar datos de archivos .csv
-
-
- Versión (2003) Archivo --> Obtener datos externos --> Importar
-
Selecicona en tipo de archivo Text Files
-
Un wizard te irá proponiendo opciones para cargar el archivo correspondiente. Estas son las respuestas a cada paso:
-
-
-
- Selecciona la opción "Delimitado", ya que las columnas están separadas por coma.
-
-
- Ahora te pregunta por el tipo de delimitador, para estos archivos es "coma". Asegúrate de que no esté seleccionada la opción "Primera fila contiene nombres de campos", esto porque los archivos solo contienen datos. La opción de cualificador de texto debe estar en "Ninguno".
-
-
- Selecciona la opción de almacenar los datos de una tabla existente: selecciona la tabla con el mismo nombre que el csv correspondiente.
-
-
- Terminar la importación.
-
-
-
-
Importante: Cuando estés en el paso 3 de la tabla Entregan, has click sobre el botón de "Avanzado..."
- Ahí asegúrate de que en la opción "Orden de la fecha" esté seleccionada la opción "DMA" y "/" como delimitador,
- que es el formato en el que aparece la fecha en el archivo entregan.csv
-
-
-
-
-
(Versión 2007) Primero selecciona cualquier tabla -> External Data -> Text File
-
-
-
Escoge de qué archivo y de dónde se va a importar.
-
Elige la segunda opción: "Append a copy of the records to an existing table" y elige la tabla a donde vas a mandar los datos.
-
Escoge la opción de delimitado y en la siguiente pantalla la de coma. En la misma pantalla cuando estés
- importando los datos de la tabla Entregan.csv aprieta el botón para opciones avanzadas y asegúrate de que la
- parte de la fecha se vea como en la siguiente imagen.
-
-
-
-
Verifica el contenido de cada tabla después de la importación.
-
-
Definición de consultas.
-
- A fin de proporcionar a cada usuario o grupo de usuarios la información de su interés, una de las tareas del diseñador o administrador de bases de datos es definir consultas que muestran conjuntos de datos que incluyen columnas específicas, renglones específicos que pueden provenir de una tabla o de varias tablas interrelacionadas.
-
-
- En el folder de Consultas, utilizando el botón Nuevo y la opción Vista Diseño, es posible definir consultas. Para esto, se agrega a la consulta la tabla o tablas involucradas, se seleccionan las columnas que se desea incluir en la consulta y se agregan los criterios que debe cumplir el valor de las columnas a restringir para que el renglón sea incluido.
-
-
-
-
-
Para las columnas con tipos de datos numéricos pueden utilizarse criterios como los que se ejemplifican a continuación:
-
- 1000 Significa que el valor de la columna debe ser igual a 1000.
- > 1000 Significa que el valor de la columna debe ser mayor que 1000.
- < 1000 Significa que el valor de la columna debe ser mayor que 1000.
- Entre 10 y 100 Significa que el valor de la columna debe ser <=10 y <=100.
-
- Pregunta para pensar: (No es parte de la entrega)
-
Si estuvieras haciendo una búsqueda en un rango de fecha, como buscarías todo los datos del año 2000?
-
-
-
Para las columnas con tipo de datos de texto, se utilizan criterios como los que se ejemplifican a continuación:
-
"ABC" Significa que el valor de la columna debe ser igual a "ABC".
-
>"A" Significa que el valor de la columna debe ser mayor que "A" en el orden alfabético.
-
Como "*ABC*" Significa que el valor de la columna debe contener el patrón "ABC". Los asteriscos
- son "comodines", es decir "ABC*" significa que inicia con "ABC", "*ABC" significa que termina con "ABC" y "*ABC*"
- significa que tiene ABC en cualquier parte del valor de la columna.
-
-
Define las siguientes consultas y guárdalas con el nombre que se indica:
-
Consulta Tuberías
-
Clave, descripción y precio unitario de los materiales cuya descripción contenga el patrón "Tub".
-
Consulta Caros
-
Clave, descripción y precio unitario de los materiales cuyo precio es mayor que 300 pesos.
-
Consulta Proveedores Ladrillos
-
-
Aquí tendrás que agregar las tablas: materiales, entregan y proveedores. En la parte de abajo seleccionar
- las columnas que quieras que aparezcan así como sus condiciones.
-
Clave del material y razón social de los proveedores que entregan productos cuya descripción contiene el patrón "Ladrillos"
-
Consulta Pinturas98
-
Aquí tendrás que agregar todas las tablas. En la parte de abajo seleccionar las columnas que quieras que aparezcan así como sus condiciones.
- Descripción del material, razón social del proveedor, denominación del proyecto, fecha y cantidad de las entregas de pinturas realizadas durante 1998.
-
-
-
-
-
- Especificaciones de entrega
-
-
A través de Bitbucket o GitHub con el nombre matricula.mdb para archivo de Access 2003 o matricula.accdb para archivo de Access 2007.
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/proyecto.html b/backup/proyecto.html
deleted file mode 100644
index aff911a..0000000
--- a/backup/proyecto.html
+++ /dev/null
@@ -1,192 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- DAW: Explicación del proyecto
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Proyecto: Consultoría de aplicaciones web
-
-
-
-
-
-
-
-
- Proyecto: Consultoría de aplicaciones web?
-
-
A lo largo del curso desarrollarás un proyecto para potenciar tu aprendizaje. Si estás llevando la materia de Bases de Datos (BD), el proyecto será el mismo para ambas materias. El proyecto deberá ser un sistema web que
- proponga una solución para una problemática social, ambiental, educativa, o bien para un problema de las ciencias computacionales. Si como parte de tu servicio social vas a desarrollar una aplicación web, tienes la opción de alinear ese proyecto con el curso.
-
Los proyectos se trabajarán en equipos base de 3 personas que estén tomando esta clase. Si estás llevando BD todos los integrantes de tu equipo deberán estar llevando la materia también y ambos profesores deben aceptar la propuesta. Si no estás cursando BD, tus compañeros de equipo tampoco deberán hacerlo.
-
-
-
-
-
-
- Modalidad
-
-
- Colaborativa.
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Que desarrolles tu competencia de programación en un
- paradigma cliente-servidor
-
Que desarrolles tu comepetencia de programación en un
- paradigma orientado a eventos
-
Que desarrolles tus competencias para desarrollar
- aplicaciones bajo una arquitectura modelo, vista, controlador
-
Que desarrolles tus competencias de diseño de bases de datos
-
Que desarrolles tus competencias para diseñar y desarrollar
- servicios web
-
Que desarrolles tus competencias para diseñar aplicaciones
- amigables para el usuario, y que seas capaz de evaluar de
- manera formal la usabilidad de una aplicación por medio de heurísticas
-
Que te enfrentes con el reto de desplegar una aplicación web
-
Desarrollar tus competencias de trabajo colaborativo
-
Desarrollar tu competencia de administración del tiempo
-
Desarrollar tu competencia de comunicación oral
-
Desarrollar tu competencia para proponer soluciones
- tecnológicas para mejorar los procesos de negocio, sociales,
- ambientales y educativos
-
Desarrollar tu competencia de aprendizaje por cuenta propia
- de nuevas tecnologías y herramientas
-
-
-
-
-
-
- Instrucciones
-
-
- En equipo desarrolla una aplicación web. La aplicación
- debe resolver una problemática propia de las ciencias
- computacionales, o bien debe tener un impacto social,
- ambiental, o educativo.
-
-
Una vez que tengas la idea de tu proyecto, durante las
- sesiones de clase, discútela con tu profesor hasta que lleguen a
- un acuerdo sobre el proyecto.
-
Elabora un breve documento sobre la visión del proyecto y
- sus características.
-
Por medio de un diagramas de casos de uso, acuerden el
- alcance de la aplicación con tu profesor.
-
Elaboren un mapa del sitio atienda los aspectos establecidos
- en la visión, características y alcance del proyecto.
-
Para la primera entrega deberás entregar una primera versión
- de la interface del usuario. Para ello utiliza tecnologías
- HTML5, CSS y JavaScript. Se sugiere que te apoyes de un
- framework de front-end.
-
Tu interface debe procurar ser agradable a la vista, y debe
- fomentar la facilidad de uso de tu aplicación.
-
Incluye la visión del proyecto y sus características, así
- como todos los artefactos de ingeniería de software que
- estés utilizando dentro de una sección de tu sitio.
-
Antes de presentar, entreguen al profesor un documento
- firmado por todos los integrantes del equipo, con los
- porcentajes de trabajo de cada uno.
Presentación en la sesión de clase y a por medio de un respositorio en Bitbucket
-
-
-
-
-
-
-
- Preguntas
-
-
-
¿Por qué es importante la accesibilidad?
-
¿Cuáles son los lineamientos de accesibilidad que consideras más importantes? ¿Por qué?
-
Describe 3 cambios que harás en tu proyecto para hacerlo más accesible.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/proyecto2.html b/backup/proyecto2.html
deleted file mode 100644
index 3cd547e..0000000
--- a/backup/proyecto2.html
+++ /dev/null
@@ -1,173 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- DAW: Segundo avance de proyecto
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
DAW: Segundo avance de proyecto
-
-
-
-
-
-
-
-
- Descripción
-
-
Después de la exitosa implementación del front-end de tu aplicación, es momento de iniciar la implementación del back-end de tu aplicación web.
-
No se espera que para este avance tengas la aplicación terminada, pero sí se espera que estén implementados los casos de uso que le dan mayor valor a tu aplicación.
-
-
-
-
-
- Modalidad
-
-
- Colaborativa.
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Que desarrolles tu competencia de programación en un
- apradigma cliente-servidor
-
Que desarrolles tu comepetencia de programación en un
- paradigma orientado a eventos
-
Que desarrolles tus competencias para desarrollar
- aplicaciones bajo una arquitectura modelo, vista, controlador
-
Que desarrolles tus competencias de diseño de bases de datos
-
Que desarrolles tus competencias para diseñar aplicaciones
- amigables para el usuario, y que seas capaz de evaluar de
- manera formal la usabilidad de una aplicación por medio de heurísticas
-
Que te enfrentes con el reto de desplegar una aplicación web
- php
-
Desarrollar tus competencias de trabajo colaborativo
-
Desarrollar tu competencia de administración del tiempo
-
Desarrollar tu competencia de comunicación oral
-
Desarrollar tu competencia de aprendizaje por cuenta propia
- de nuevas tecnologías y herramientas
-
-
-
-
-
-
- Instrucciones
-
-
En equipo desarrolla el back-end de tu aplicación web. Implementa los casos de uso que le den mayor valor a tu aplicación
-
-
La funcionalidad de la aplicación que implementes debe incluir al menos
- una consulta para listar elementos de una base datos, una
- inserción de un registro (a través de una forma html), una
- modificación de un registro, y una eliminación de un
- registro.
-
La aplicación debe seguir un estilo arquitectónico en capas modelo-vista-controlador.
-
Cada miembro del equipo debe implementar al menos 1
- componente AJAX. Pueden hacer uso de alguna librería
- de terceros como jQuery, Dojo, entre otros.
-
-
Considera la
- evaluación heurística de tu aplicación realizada por tus
- compañeros. Implementa al menos una de las recomendaciones.
- Durante la presentación, debes presentar el estado previo a
- la evaluación heurística de tu aplicación y compararlo con
- el estado actual. Presenta también las recomendaciones que
- se hicieron que no se implementaron.
-
La aplicación debe estar disponible en un servidor
- accesible a través de la www
-
Antes de presentar, entreguen al profesor un documento firmado por todos los integrantes del equipo, con los porcentajes de trabajo de cada uno.
-
Las contribuciones de los miembros del equipo con el proyecto deben estar balanceadas.
Dado el éxito de tu última aplicación, haz decidido continuar con el Construcción de software y toma de decisiones más profesionales.
-
-
-
-
-
Modalidad
-
-
-
-
-
-
Colaborativa.
-
-
-
-
-
Objetivos de la
- actividad
-
-
-
-
-
-
Desarrollar tus competencias de trabajo colaborativo
-
Que desarrolles tu competencia de programación en un paradigma cliente-servidor
-
Que desarrolles tus competencias para desarrollar aplicaciones bajo una arquitectura modelo, vista, controlador
-
Que desarrolles tus competencias de diseño de bases de datos
-
Que desarrolles tus competencias para integrar servicios web en aplicaciones
-
Desarrollar tu competencia de administración del tiempo
-
Desarrollar tu competencia de comunicación oral
-
Desarrollar tu competencia para proponer soluciones tecnológicas para mejorar los procesos de negocio, sociales, ambientales y educativos
-
Desarrollar tu competencia de aprendizaje por cuenta propia de nuevas tecnologías y herramientas
-
Desarrollar tu competencia de de análisis de impacto de las herramientas tecnológicas en los individuos, organizaciones, y la sociedad para prestar servicios profesionales de manera ética y responsable
-
-
-
-
-
Instrucciones
-
-
-
-
-
-
En equipo, con la mínima cantidad de casos de uso, desarrolla una aplicación web que resuelva una problemática propia de las ciencias computacionales, o bien que tenga un impacto social, ambiental, o educativo.
-
Sobre la plataforma tecnológica tienes las siguientes opciones:
-
-
Desarrolla una aplicación web JEE
-
Desarrolla un servicio web con tecnología JEE y desarrolla una aplicación php que consuma el servicio
-
Desarrolla una aplicación web con php que integre al menos 1 servicio web desarrollados por terceros (facebook, twitter, google calendar, google maps, entre muchos otros)
-
Desarrolla una aplicación web con alguna tecnología no vista en clase (meteor, python, ruby, perl, .net, JEE, entre otras)
La funcionalidad de la aplicación debe incluir al menos una consulta para listar elementos de una base datos, y una inserción de un registro a través de una forma.
-
Una vez que tengas la idea de tu proyecto, durante las sesiones de clase, discútela con tu profesor. Posteriormente por medio de diagramas de casos de uso, acuerda el alcance de la aplicación con tu profesor. Para la entrega del proyecto, incluye los diagramas de casos de uso dentro de una página de tu aplicación.
-
Para el desarrollo debes utilizar un sistema de control de versiones y mostrar evidencia de ello.
-
Aplica las heurísticas de usabilidad en tu diseño.
-
Tu aplicación debe estar construida bajo una arquitectura MVC, y debes mostrar evidencia de ello.
-
Cada miembro del equipo debe implementar al menos 1 componente AJAX. Pueden hacer uso de alguna librería predefinada como jQuery, Dojo, entre otros.
-
En este documento puedes encontrar una lista de posibles proyectos vinculados con la organización Q PET Educación Ambiental, A.C.
-
-
diff --git a/backup/recursos/3de9.ttf b/backup/recursos/3de9.ttf
deleted file mode 100644
index cbad3e9..0000000
Binary files a/backup/recursos/3de9.ttf and /dev/null differ
diff --git a/backup/recursos/BD.mdb b/backup/recursos/BD.mdb
deleted file mode 100644
index a6fb3b2..0000000
Binary files a/backup/recursos/BD.mdb and /dev/null differ
diff --git a/backup/recursos/entregan.csv b/backup/recursos/entregan.csv
deleted file mode 100644
index cc6dd01..0000000
--- a/backup/recursos/entregan.csv
+++ /dev/null
@@ -1,132 +0,0 @@
-1000,AAAA800101,5000,8/7/1998,165
-1010,BBBB800101,5001,3/5/2000,528
-1020,CCCC800101,5002,29/7/2001,582
-1030,DDDD800101,5003,21/2/1998,202
-1040,EEEE800101,5004,11/12/1999,263
-1050,FFFF800101,5005,14/10/2000,503
-1060,GGGG800101,5006,4/5/2000,324
-1070,HHHH800101,5007,23/2/1998,2
-1080,AAAA800101,5008,12/1/1999,86
-1090,BBBB800101,5009,1/8/2000,73
-1100,CCCC800101,5010,10/9/2001,699
-1110,DDDD800101,5011,28/6/2003,368
-1120,EEEE800101,5012,3/4/2001,215
-1130,FFFF800101,5013,4/4/2002,63
-1140,GGGG800101,5014,12/7/2001,219
-1150,HHHH800101,5015,4/3/1999,458
-1160,AAAA800101,5016,1/6/2000,162
-1170,BBBB800101,5017,4/2/1998,180
-1180,CCCC800101,5018,14/6/2002,407
-1190,DDDD800101,5019,12/9/1998,94
-1200,EEEE800101,5000,5/3/2000,177
-1210,FFFF800101,5001,5/11/1999,43
-1220,GGGG800101,5002,1/2/2003,24
-1230,HHHH800101,5003,6/1/2003,530
-1240,AAAA800101,5004,12/1/2003,152
-1250,BBBB800101,5005,8/7/2002,71
-1260,CCCC800101,5006,10/5/1999,460
-1270,DDDD800101,5007,10/3/1999,506
-1280,EEEE800101,5008,29/7/2002,107
-1290,FFFF800101,5009,8/1/1998,132
-1300,GGGG800101,5010,8/1/2003,119
-1310,HHHH800101,5011,12/4/2002,72
-1320,AAAA800101,5012,6/1/2003,698
-1330,BBBB800101,5013,10/12/1998,554
-1340,CCCC800101,5014,2/12/2002,324
-1350,DDDD800101,5015,9/5/1999,272
-1360,EEEE800101,5016,7/11/2000,364
-1370,FFFF800101,5017,12/2/2000,44
-1380,GGGG800101,5018,3/3/2002,302
-1390,HHHH800101,5019,12/1/2003,107
-1400,AAAA800101,5000,12/3/2002,382
-1410,BBBB800101,5001,5/2/2000,601
-1420,CCCC800101,5002,7/4/1998,603
-1430,DDDD800101,5003,2/9/1999,576
-1000,AAAA800101,5019,8/8/1999,254
-1010,BBBB800101,5018,29/3/2002,523
-1020,CCCC800101,5017,4/2/1999,8
-1030,DDDD800101,5016,5/11/2000,295
-1040,EEEE800101,5015,12/7/2002,540
-1050,FFFF800101,5014,7/3/1999,623
-1060,GGGG800101,5013,2/1/2000,692
-1070,HHHH800101,5012,3/12/1999,503
-1080,AAAA800101,5011,7/11/2002,699
-1090,BBBB800101,5010,3/1/1998,421
-1100,CCCC800101,5009,6/8/2000,466
-1110,DDDD800101,5008,10/5/1999,337
-1120,EEEE800101,5007,7/7/2001,692
-1130,FFFF800101,5006,6/7/2002,562
-1140,GGGG800101,5005,2/9/2001,583
-1150,HHHH800101,5004,10/8/2001,453
-1160,AAAA800101,5019,9/6/1999,244
-1170,BBBB800101,5018,12/11/1999,53
-1180,CCCC800101,5017,3/3/2001,334
-1190,DDDD800101,5016,4/2/2000,356
-1200,EEEE800101,5015,6/5/2000,585
-1210,FFFF800101,5014,3/11/2001,70
-1220,GGGG800101,5013,4/7/2002,658
-1230,HHHH800101,5012,9/12/2002,312
-1240,AAAA800101,5011,12/8/2000,366
-1250,BBBB800101,5010,4/4/2002,691
-1260,CCCC800101,5009,9/8/1999,631
-1270,DDDD800101,5008,3/9/1997,546
-1280,EEEE800101,5007,3/2/2000,331
-1290,FFFF800101,5006,8/2/2001,279
-1300,GGGG800101,5005,10/6/2002,521
-1310,HHHH800101,5019,2/10/2002,199
-1320,AAAA800101,5018,7/3/2000,413
-1330,BBBB800101,5017,11/8/2000,93
-1340,CCCC800101,5016,6/11/1998,674
-1350,DDDD800101,5015,2/8/1999,261
-1360,EEEE800101,5014,7/4/2002,265
-1370,FFFF800101,5013,8/4/2000,575
-1380,GGGG800101,5012,8/7/1998,645
-1390,HHHH800101,5011,8/11/2001,697
-1400,AAAA800101,5010,5/6/1998,116
-1410,BBBB800101,5009,3/5/2002,467
-1420,CCCC800101,5008,2/8/2000,278
-1430,DDDD800101,5007,9/1/1998,13
-1000,AAAA800101,5019,6/4/2000,7
-1010,BBBB800101,5018,10/11/2000,667
-1020,CCCC800101,5017,4/5/2001,478
-1030,DDDD800101,5016,9/4/1998,139
-1040,EEEE800101,5015,10/6/2000,546
-1050,FFFF800101,5014,4/6/1999,90
-1060,GGGG800101,5013,10/7/2000,47
-1070,HHHH800101,5012,1/4/2000,516
-1080,AAAA800101,5011,1/6/2003,429
-1090,BBBB800101,5010,6/6/1998,612
-1100,CCCC800101,5009,7/5/2002,523
-1110,DDDD800101,5008,9/2/2000,292
-1120,EEEE800101,5007,12/3/1998,167
-1130,FFFF800101,5006,6/5/1999,673
-1140,GGGG800101,5005,7/2/1998,651
-1150,HHHH800101,5004,1/9/2003,270
-1160,AAAA800101,5019,8/2/2002,665
-1170,BBBB800101,5018,6/8/2001,517
-1180,CCCC800101,5017,1/6/2001,216
-1190,DDDD800101,5016,7/3/2003,622
-1200,EEEE800101,5015,10/7/2000,653
-1210,FFFF800101,5014,6/9/2001,479
-1220,GGGG800101,5013,8/2/2001,653
-1230,HHHH800101,5012,8/3/1999,115
-1240,AAAA800101,5011,5/8/2003,549
-1250,BBBB800101,5010,8/5/1998,690
-1260,CCCC800101,5009,10/2/2003,2
-1270,DDDD800101,5008,12/4/2002,324
-1280,EEEE800101,5007,7/12/2002,448
-1290,FFFF800101,5006,7/1/1999,336
-1300,GGGG800101,5005,2/2/2003,457
-1310,HHHH800101,5019,3/8/2000,463
-1320,AAAA800101,5018,3/12/1999,163
-1330,BBBB800101,5017,12/6/1998,558
-1340,CCCC800101,5016,10/2/1999,11
-1350,DDDD800101,5015,5/6/1999,330
-1360,EEEE800101,5014,6/7/2001,37
-1370,FFFF800101,5013,5/6/2002,423
-1380,GGGG800101,5012,1/2/2001,147
-1390,HHHH800101,5011,6/1/2002,308
-1400,AAAA800101,5010,3/5/2002,441
-1410,BBBB800101,5009,5/11/2002,461
-1420,CCCC800101,5008,12/2/2001,444
-1430,DDDD800101,5007,10/6/2002,506
diff --git a/backup/recursos/images.zip b/backup/recursos/images.zip
deleted file mode 100644
index b1c6ca7..0000000
Binary files a/backup/recursos/images.zip and /dev/null differ
diff --git a/backup/recursos/images/application.png b/backup/recursos/images/application.png
deleted file mode 100644
index 0b363e1..0000000
Binary files a/backup/recursos/images/application.png and /dev/null differ
diff --git a/backup/recursos/images/entregan.png b/backup/recursos/images/entregan.png
deleted file mode 100644
index 8e87c43..0000000
Binary files a/backup/recursos/images/entregan.png and /dev/null differ
diff --git a/backup/recursos/images/gEntregan.png b/backup/recursos/images/gEntregan.png
deleted file mode 100644
index 9b4cbb0..0000000
Binary files a/backup/recursos/images/gEntregan.png and /dev/null differ
diff --git a/backup/recursos/images/gMaterial.png b/backup/recursos/images/gMaterial.png
deleted file mode 100644
index e52aca7..0000000
Binary files a/backup/recursos/images/gMaterial.png and /dev/null differ
diff --git a/backup/recursos/images/gProveedor.png b/backup/recursos/images/gProveedor.png
deleted file mode 100644
index a0c2913..0000000
Binary files a/backup/recursos/images/gProveedor.png and /dev/null differ
diff --git a/backup/recursos/images/gProyecto.png b/backup/recursos/images/gProyecto.png
deleted file mode 100644
index c8517e5..0000000
Binary files a/backup/recursos/images/gProyecto.png and /dev/null differ
diff --git a/backup/recursos/images/material.png b/backup/recursos/images/material.png
deleted file mode 100644
index a1d51a0..0000000
Binary files a/backup/recursos/images/material.png and /dev/null differ
diff --git a/backup/recursos/images/proveedor.png b/backup/recursos/images/proveedor.png
deleted file mode 100644
index 1d579b8..0000000
Binary files a/backup/recursos/images/proveedor.png and /dev/null differ
diff --git a/backup/recursos/images/proyecto.png b/backup/recursos/images/proyecto.png
deleted file mode 100644
index f6702dc..0000000
Binary files a/backup/recursos/images/proyecto.png and /dev/null differ
diff --git a/backup/recursos/materiales.csv b/backup/recursos/materiales.csv
deleted file mode 100644
index 7d4e3b5..0000000
--- a/backup/recursos/materiales.csv
+++ /dev/null
@@ -1,44 +0,0 @@
-1000,Varilla 3/16,100
-1010,Varilla 4/32,115
-1020,Varilla 3/17,130
-1030,Varilla 4/33,145
-1040,Varilla 3/18,160
-1050,Varilla 4/34,175
-1060,Varilla 3/19,190
-1070,Varilla 4/35,205
-1080,Ladrillos rojos,50
-1090,Ladrillos grises,35
-1100,Block,30
-1110,Megablock,40
-1120,Sillar rosa,100
-1130,Sillar gris,110
-1140,Cantera blanca,200
-1150,Cantera gris,1210
-1160,Cantera rosa,1420
-1170,Cantera amarilla,230
-1180,Recubrimiento P1001,200
-1190,Recubrimiento P1010,220
-1200,Recubrimiento P1019,240
-1210,Recubrimiento P1028,250
-1220,Recubrimiento P1037,280
-1230,Cemento ,300
-1240,Arena,200
-1250,Grava,100
-1260,Gravilla,90
-1270,Tezontle,80
-1280,Tepetate,34
-1290,Tubera 3.5,200
-1300,Tubera 4.3,210
-1310,Tubera 3.6,220
-1320,Tubera 4.4,230
-1330,Tubera 3.7,240
-1340,Tubera 4.5,250
-1350,Tubera 3.8,260
-1360,Pintura C1010,125
-1370,Pintura B1020,125
-1380,Pintura C1011,725
-1390,Pintura B1021,125
-1400,Pintura C1011,125
-1410,Pintura B1021,125
-1420,Pintura C1012,125
-1430,Pintura B1022,125
diff --git a/backup/recursos/modUtil.vb b/backup/recursos/modUtil.vb
deleted file mode 100644
index 1195867..0000000
--- a/backup/recursos/modUtil.vb
+++ /dev/null
@@ -1,204 +0,0 @@
-Option Explicit On
-
-Imports System.Data
-Imports System.Data.OleDb
-Imports ZedGraph
-
-Module modUtil
-
- Private connection As OleDbConnection
-
- '---------------------------------------------------------------------------------------------------------'
- ' Despliega un MessageBox de error que muestra el mensaje pasado como parmetro '
- ' '
- ' @param err El error a desplegar '
- '---------------------------------------------------------------------------------------------------------'
- Sub msgError(ByVal err As String)
- MessageBox.Show(err, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, _
- MessageBoxDefaultButton.Button1, 0, False)
- End Sub
-
- '---------------------------------------------------------------------------------------------------------'
- ' Ejecuta el comando de DML pasado como parmetro ' '
- ' '
- ' @param dml El comando que se quiere ejecutar '
- ' @return Verdadero si el comando se ejecut de manera correcta, falso en caso contrario '
- '---------------------------------------------------------------------------------------------------------'
- Function execute(ByVal dml As String) As Boolean
- Try
- Dim command As New OleDbCommand(dml, connection)
- connection.Open()
- command.ExecuteNonQuery()
- connection.Close()
- Return True
- Catch OleDbex As OleDbException
- Dim err As String
- err = "Error de base de datos al ejecutar el query" & vbCrLf & _
- OleDbex.Message
- msgError(err)
- Return False
- Catch ex As Exception
- Dim err As String
- err = "Error al ejecutar el query" & vbCrLf & _
- ex.Message
- msgError(err)
- Return False
- End Try
- End Function
-
- '---------------------------------------------------------------------------------------------------------'
- ' Esta funcin utiliza la conexin abierta para realizar el query pasado como parmetro y regresa un '
- ' DataReader con los datos leidos '
- ' '
- ' @param query El query que se va a ejecutar en la base de datos '
- ' @return Un OleDbDataReader con los datos leidos de la base de datos al ejecutar el query '
- '---------------------------------------------------------------------------------------------------------'
- Function getDataReader(ByVal query As String) As OleDbDataReader
- Dim command As New OleDbCommand(query, connection)
- Try
- Return command.ExecuteReader(CommandBehavior.CloseConnection)
- Catch OleDbex As OleDbException
- Dim err As String
- err = "Error de base de datos al ejecutar el query" & vbCrLf & _
- OleDbex.Message
- msgError(err)
- Catch ex As Exception
- msgError("Error al ejecutar el query")
- End Try
- Return Nothing
- End Function
-
- '---------------------------------------------------------------------------------------------------------'
- ' Esta funcin utiliza la conexin abierta para realizar el query pasado como parmetro y regresa un '
- ' DataTable con los datos leidos '
- ' '
- ' @param query El query que se va a ejecutar en la base de datos '
- ' @return Un DataTable con los datos leidos de la base de datos al ejecutar el query '
- '---------------------------------------------------------------------------------------------------------'
- Function getDataTable(ByVal query As String) As DataTable
- Dim command As New OleDbCommand(query, connection)
- Dim adapter As New OleDbDataAdapter(command)
- Dim table As New DataTable
-
- Try
- adapter.Fill(table)
- If table.Rows.Count > 0 Then
- Return table
- Else
- Return Nothing
- End If
- Catch OleDbex As OleDbException
- Dim err As String
- err = "Error de base de datos al ejecutar el query" & vbCrLf & _
- OleDbex.Message
- msgError(err)
- Catch ex As Exception
- msgError("Error al ejecutar el query")
- End Try
- Return Nothing
- End Function
-
- '---------------------------------------------------------------------------------------------------------'
- ' Llena la forma pasada como parmetro utilizando el query pasado como parmetro. Dentro de la forma '
- ' deben haber controles que tengan los mismos nombres que las columnas que se encuentran en el query. '
- ' Por ejemplo, si el query es 'SELECT nombre FROM usuarios' debe haber un control llamado 'nombre' entre '
- ' los controles de la forma '
- ' '
- ' @param query El query que se quiere ejecutar en la base de datos. '
- ' @param form La forma que se quire llenar con los datos '
- '---------------------------------------------------------------------------------------------------------'
- Sub showData(ByVal query As String, ByVal container As Control)
- Dim dt As DataTable
- Dim ctrl As Control
- dt = getDataTable(query)
- If Not dt Is Nothing Then
- For Each ctrl In container.Controls
- If dt.Columns.Contains(ctrl.Name) Then
- If TypeOf (ctrl) Is TextBox Or TypeOf (ctrl) Is MaskedTextBox Then
- ctrl.Text = dt.Rows(0)(ctrl.Name)
- ElseIf TypeOf (ctrl) Is ComboBox Or TypeOf (ctrl) Is ListBox Then
- DirectCast(ctrl, ComboBox).SelectedValue = dt.Rows(0)(ctrl.Name)
- ElseIf TypeOf (ctrl) Is DateTimePicker Then
- DirectCast(ctrl, DateTimePicker).Value = dt.Rows(0)(ctrl.Name)
- Else
- Try
- ctrl.Text = dt.Rows(0)(ctrl.Name)
- Catch ex As Exception
- End Try
- End If
- End If
- If ctrl.HasChildren Then
- showData(query, ctrl)
- End If
- Next
- End If
- End Sub
-
- '---------------------------------------------------------------------------------------------------------'
- ' Llena el ComboBox pasado como parmetro con los datos que se recuperan del query pasado como parmetro.'
- ' El ComboBox se va a llenar mediante su ValueMember y su DisplayMember con la primera y segunda columna '
- ' regresadas del query respectivamente. '
- ' '
- ' @param lst El ComboBox que se va a llenar con los datos del query '
- ' @param query El query que se quiere ejecutar en la base de datos. El query debe tener la forma '
- ' SELECT INTEGER, $ FROM Tabla [...] '
- ' en donde $ es un dato de cualquier tipo. '
- '---------------------------------------------------------------------------------------------------------'
- Sub fillList(ByVal lst As Windows.Forms.ComboBox, ByVal query As String)
- Dim table As DataTable
- table = getDataTable(query)
- If Not table Is Nothing Then
- lst.DataSource = Nothing
- lst.Items.Clear()
- lst.DataSource = table
- If table.Columns.Count = 2 Then
- lst.ValueMember = table.Columns(0).ToString
- lst.DisplayMember = table.Columns(1).ToString
- Else
- lst.DisplayMember = table.Columns(0).ToString
- End If
- End If
- End Sub
-
- '---------------------------------------------------------------------------------------------------------'
- ' Llena el ListBox pasado como parmetro con los datos que se recuperan del query pasado como parmetro. '
- ' El ListBox se va a llenar mediante su ValueMember y su DisplayMember con la primera y segunda columna '
- ' regresadas del query respectivamente. '
- ' '
- ' @param lst El ListBox que se va a llenar con los datos del query '
- ' @param query El query que se quiere ejecutar en la base de datos. El query debe tener la forma '
- ' SELECT INTEGER, $ FROM Tabla [...] '
- ' en donde $ es un dato de cualquier tipo. '
- '---------------------------------------------------------------------------------------------------------'
- Sub fillList(ByRef lst As Windows.Forms.ListBox, ByVal query As String)
- Dim table As DataTable
- table = getDataTable(query)
- If Not table Is Nothing Then
- lst.DataSource = table
- If table.Columns.Count = 2 Then
- lst.ValueMember = table.Columns(0).ToString
- lst.DisplayMember = table.Columns(1).ToString
- Else
- lst.ValueMember = table.Columns(0).ToString
- End If
- End If
- End Sub
-
- '---------------------------------------------------------------------------------------------------------'
- ' Llena el DataGrid pasado como parmetro con los datos que se recuperan del query pasado como '
- ' parmetro. '
- ' '
- ' @param grid El DataGrid que se va a llenar con los datos del query '
- ' @param query El query que se quiere ejecutar en la base de datos. '
- '---------------------------------------------------------------------------------------------------------'
- Sub fillGrid(ByVal grid As Windows.Forms.DataGridView, ByVal query As String)
- Dim table As DataTable
- table = getDataTable(query)
- If Not table Is Nothing Then
- grid.DataSource = table
- Else
- grid.DataSource = New DataTable
- End If
- End Sub
-
-End Module
diff --git a/backup/recursos/proveedores.csv b/backup/recursos/proveedores.csv
deleted file mode 100644
index 84432cf..0000000
--- a/backup/recursos/proveedores.csv
+++ /dev/null
@@ -1,8 +0,0 @@
-AAAA800101,La fragua
-BBBB800101,Oviedo
-CCCC800101,La Ferre
-DDDD800101,Cecoferre
-EEEE800101,Alvin
-FFFF800101,Comex
-GGGG800101,Tabiquera del centro
-HHHH800101,Tubasa
diff --git a/backup/recursos/proyectos.csv b/backup/recursos/proyectos.csv
deleted file mode 100644
index 45f9ad7..0000000
--- a/backup/recursos/proyectos.csv
+++ /dev/null
@@ -1,20 +0,0 @@
-5000,Vamos Mexico
-5001,Aztecn
-5002,CIT Campeche
-5003,Mexico sin ti no estamos completos
-5004,Educando en Coahuila
-5005,Infonavit Durango
-5006,Reconstruccin del templo de Guadalupe
-5007,Construccin de plaza Magnolias
-5008,Televisa en accin
-5009,Disco Atlantic
-5010,Construccin de Hospital Infantil
-5011,Remodelacin de aulas del IPP
-5012,Restauracin de instalaciones del CEA
-5013,Reparacin de la plaza Sonora
-5014,Remodelacin de Soriana
-5015,CIT Yucatan
-5016,Ampliacin de la carretera a la huasteca
-5017,Reparacin de la carretera del sol
-5018,Tu cambio por la educacion
-5019,Queretaro limpio
diff --git a/backup/universo_alternativo.html b/backup/universo_alternativo.html
deleted file mode 100644
index 957d45d..0000000
--- a/backup/universo_alternativo.html
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Calendario | TC2005B Construcción de software y toma de decisiones
-
-
-
-
-
-
-
-
-
Universo Alternativo
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/web4screenReaders.html b/backup/web4screenReaders.html
deleted file mode 100644
index 32ace2e..0000000
--- a/backup/web4screenReaders.html
+++ /dev/null
@@ -1,146 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Lab 26: Accesibilidad en aplicaciones web
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Lab 26: Accesibilidad en aplicaciones web
-
-
-
-
-
-
-
-
- Descripción
-
-
En este laboratorio abordaremos los aspectos para hacer las aplicaciones web accesibles para todos.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
-
Que conozcas y apliques los lineamientos de accesibilidad para las aplicaciones web.
Para alguno de los laboratorios anteriores y/o para tu proyecto, incorpora los lineamientos de accesibilidad que sean aplicables.
-
En algún lugar del sitio describe los lineamientos que aplicaste junto con su descripción.
-
Responde a las preguntas dentro de tu aplicación.
-
-
-
-
-
-
- Preguntas a responder
-
-
-
¿Por qué es importante la accesibilidad?
-
¿Cuáles son los lineamientos de accesibilidad que consideras más importantes? ¿Por qué?
-
-
Describe 3 cambios que harás en tu proyecto para hacerlo más accesible.
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
A través de tu repositorio personal (Bitbucket o GitHub).
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/backup/zombies.html b/backup/zombies.html
deleted file mode 100644
index 346e659..0000000
--- a/backup/zombies.html
+++ /dev/null
@@ -1,194 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- Examen de segundo parcial: PETZ
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Examen de segundo parcial: Personas por la Ética en el Trato de los Zombis (PETZ)
-
-
-
-
Código de ética
- "Apegándome al Código de Ética de los Estudiantes del Tecnológico de Monterrey, me comprometo a que mi actuación en esta actividad de evaluación esté regida por la honestidad académica. En congruencia con el mismo, realizaré esta actividad de forma honesta y personal, para reflejar a través de ella mis conocimientos y competencias."
-
-
-
-
-
Información
-
-
-
-
-
-
El virus DAWBD te convierte en zombie y te acabas de contagiar. Una persona contagiada se convierte en Zombi DAWBD en 170 minutos, más vale tener el siguiente sistema listo para las 12:55pm para asegurarte que te traten considerando que alguna vez fuiste humano.
-
-
-
-
-
-
- Modalidad
-
-
- Individual.
-
-
-
-
-
-
-
-
- Competencias a observar
-
-
3. Implementa sistemas de bases de datos que satisfacen requisitos de información y permiten la escalabilidad del sistema
-
5. Desarrolla aplicaciones web con interacción asíncrona con el servidor y que consumen servicios web
-
-
-
-
-
-
Instrucciones
-
-
- Para observar el nivel mínimo de desarrollo de competencias esperado hasta este punto:
-
La organización denominada Personas por la Ética en el Trato de los Zombis (PETZ) requiere un sistema accesible desde Internet con una base de datos centralizada para llevar un registro de zombis. Con este registro, PETZ podrá darle seguimiento a los zombis para asegurarse que reciban un trato adecuado considerando que alguna vez fueron humanos.
-
-
La aplicación debe permitir 2 tipos de registro:
-
-
1. Nuevos zombis con su nombre completo.
-
2. El estado actual del zombi (infección, desorientación, violencia, desmayo, transformación), con su fecha y hora de registro del nuevo estado, de tal forma que sea posible tener el histórico de todos los estados por los que ha pasado un zombi. El usuario no debe ingresar la fecha y hora, la aplicación debe hacerlo de manera automática.
-
-
-
Además, el sitio debe tener una sección para mostrar las siguientes consultas:
-
-
1. Todos los registros de zombis con todas las actualizaciones de cada uno.
-
2. La cantidad total de zombis registrados, y la cantidad de zombis en cada estado.
-
3. Todos los registros de actualización de estado de zombis del más reciente al más antiguo por la fecha de su registro.
-
4. Los registros de zombis con el estado elegido por el usuario y la cantidad de ellos.
-
-
Debido a la política de transparencia y participación ciudadana, la aplicación debe permitir a cualquier persona hacer los registros y las consultas.
-
Para tu fortuna, Ellie (única persona inmune al virus DAWDB) implementó la interfaz del usuario, pero no le fue posible implementar el back-end por no haber realizado los laboratorios del segundo parcial. Su código está disponible en: https://bitbucket.org/ejuarez/the_last_of_us).
-
Nota 1: Recuerda que puedes manejar el guardado de las fechas y horas directamente con el motor de la base de datos, es decir, no necesitas un datepicker para manejar las fechas, ni generar la fecha desde php.
-
Nota 2: El examen es abierto, puedes consultar cualquier referencia (excepto comunicarte con tus compañeros).
-
-
- Para obtener los grados más altos esperados en el desarrollo de competencias:
-
Idealmente, la aplicación debe ser una RIA (Rich Internet Application), es decir, las interacciones con el servidor deben ser asíncronas.
-
Idealmente las interacciones con la base de datos deben ser por medio de procedimientos almacenados.
-
-
-
-
-
-
-
-
- Entregables por mensaje directo de slack a edjuarezp
-
-
1. URL de la aplicación funcionando en la web.
-
2. Enlace al repositorio de git. El repositorio debe incluir el script de la base de datos y todos los archivos de código.
-
-
-
-
-
-
- assessment
- Evaluación
-
-
-
-
-
Competencia
-
Nivel “Don't keep calm ... Run!” (Practicante)
-
Nivel “Let me eat your br-a-a-a-a-a-a-i-n-s” (Ing. Junior)
-
Nivel “Get that screwdriver out of my head!” (Ing. Senior)
-
Nivel “Zombies, they love you for what is on the inside” (Arquitecto)
-
-
-
-
-
3. Implementa sistemas de bases de datos que satisfacen requisitos de información y permiten la escalabilidad del sistema
-
No hay evidencia de implementación de sistemas de bases de datos
-
Diseña un modelo de datos que atiende de manera parcial las necesidades de información de un sistema de cómputo.
-
Diseña un modelo de datos que atiende las necesidades de información de un sistema de cómputo considerando los posibles cambios que se presenten en el futuro. Es evidente el uso adecuado de consultas para la extracción de datos.
-
Diseña un modelo de datos que atiende las necesidades de información de un sistema de cómputo considerando los posibles cambios que se presenten en el futuro. Es evidente el uso adecuado de consultas para la extracción de datos. Hay técnicas de automatización para creación de estructuras de bases de datos, cargas masivas de datos y procedimientos almacenados.
-
-
-
5. Desarrolla aplicaciones web con interacción asíncrona con el servidor y que consumen servicios web
-
No hay evidencia del desarrollo de una aplicación con comunicación entre un cliente y un servidor
-
Desarrolla aplicaciones web sencillas con un uso adecuado de las peticiones HTTP, manejo correcto de sesiones, estilo arquitectónico MVC e interacción con una base de datos con operaciones para crear, modificar y borrar datos, así como consultas complejas y un nivel básico de seguridad.
-
Desarrolla aplicaciones web enriquecidas con un uso adecuado de las peticiones HTTP, manejo correcto de sesiones, estilo arquitectónico MVC e interacción con una base de datos con operaciones para crear, modificar y borrar datos, así como consultas complejas, un nivel básico de seguridad e interacción asíncrona para las características usadas más frecuentemente.
Sube tu carta firmada (en formato PDF) a la siguiente carpeta: Carpeta de Google Drive. Puedes escanear el documento firmado o usar una firma digital.'
+ contenido: 'Los socios formadores comparten información sensible de su organización, por lo cual debemos tratar dicha información con el cuidado y la seriedad que merece. Por ello, de manera individual hay que descargar, llenar y firmar la Carta de confidencialidad, y honrar los acuerdos que en ella se establecen.
Sube tu carta firmada (en formato PDF) a la siguiente carpeta: Carpeta de Google Drive. Puedes escanear el documento firmado o usar una firma digital.'
},
{
contenido: "Elaboren un documento para establecer su despacho de consultoría en Construcción de software, y otro documento en el que presentarán la propuesta formal del proyecto.
El documento del despacho debe contener al menos lo siguiente:"
diff --git a/avances/data/av3.js b/data-source/avances/av3.js
similarity index 100%
rename from avances/data/av3.js
rename to data-source/avances/av3.js
diff --git a/avances/data/av4.js b/data-source/avances/av4.js
similarity index 100%
rename from avances/data/av4.js
rename to data-source/avances/av4.js
diff --git a/avances/data/av5.js b/data-source/avances/av5.js
similarity index 100%
rename from avances/data/av5.js
rename to data-source/avances/av5.js
diff --git a/avances/data/av6.js b/data-source/avances/av6.js
similarity index 100%
rename from avances/data/av6.js
rename to data-source/avances/av6.js
diff --git a/avances/data/av7.js b/data-source/avances/av7.js
similarity index 100%
rename from avances/data/av7.js
rename to data-source/avances/av7.js
diff --git a/data/calendario-grupo2.js b/data-source/calendario/calendario-grupo2.js
similarity index 100%
rename from data/calendario-grupo2.js
rename to data-source/calendario/calendario-grupo2.js
diff --git a/labs/data/lab-index.js b/data-source/labs/lab-index.js
similarity index 100%
rename from labs/data/lab-index.js
rename to data-source/labs/lab-index.js
diff --git a/labs/data/lab0.js b/data-source/labs/lab0.js
similarity index 100%
rename from labs/data/lab0.js
rename to data-source/labs/lab0.js
diff --git a/labs/data/lab1.js b/data-source/labs/lab1.js
similarity index 96%
rename from labs/data/lab1.js
rename to data-source/labs/lab1.js
index 7b63346..5526ca0 100644
--- a/labs/data/lab1.js
+++ b/data-source/labs/lab1.js
@@ -71,5 +71,10 @@ const LAB = {
{ texto: 'http://html5doctor.com/', url: 'http://html5doctor.com/', externo: true },
{ texto: 'I bet you didn\'t know about these 15 HTML features.', url: 'https://medium.com/codex/i-bet-you-didnt-know-about-these-15-html-features-9b0824dba28f', externo: true }
],
- entrega: 'Una vez que termines, registra la actividad en tu plan de aprendizaje. Guarda tu trabajo en tu computadora personal. En el laboratorio 2 se dar\u00e1n las instrucciones para la entrega de este laboratorio y de todos los siguientes.'
+ entrega: 'Una vez que termines, registra la actividad en tu plan de aprendizaje. Guarda tu trabajo en tu computadora personal. En el laboratorio 2 se dar\u00e1n las instrucciones para la entrega de este laboratorio y de todos los siguientes.',
+ practica: {
+ titulo: "Pr\u00e1ctica: Intro a Desarrollo Web con HTML",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab1HTML/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab10.js b/data-source/labs/lab10.js
similarity index 86%
rename from labs/data/lab10.js
rename to data-source/labs/lab10.js
index 5f4c2b8..bbede64 100644
--- a/labs/data/lab10.js
+++ b/data-source/labs/lab10.js
@@ -17,7 +17,7 @@ const LAB = {
'' +
'
' +
'
' +
- '' +
+ '' +
'
' +
'
' +
'
Sigue la demostracion del profesor en la sesion de clase.
' +
@@ -35,5 +35,10 @@ const LAB = {
{ texto: 'How to Create and Validate Modern Web Forms with HTML5', url: 'https://www.freecodecamp.org/news/create-and-validate-modern-web-forms-html5/', externo: true },
{ texto: 'Text fields in UI Design: 7 Common Styles', url: 'https://uxplanet.org/text-fields-in-ui-design-7-common-styles-ea5a76689892', externo: true }
],
- entrega: 'A traves de tu repositorio personal.'
+ entrega: 'A traves de tu repositorio personal.',
+ practica: {
+ titulo: "Pr\u00e1ctica: Rutas y Formas",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab10RutasYFormas/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab11.js b/data-source/labs/lab11.js
similarity index 95%
rename from labs/data/lab11.js
rename to data-source/labs/lab11.js
index 7b7e884..339ba12 100644
--- a/labs/data/lab11.js
+++ b/data-source/labs/lab11.js
@@ -103,5 +103,10 @@ const LAB = {
{ texto: 'body-parser', url: 'https://www.npmjs.com/package/body-parser', externo: true },
{ texto: 'JavaScript Modules – A Beginner\'s Guide', url: 'https://www.freecodecamp.org/news/javascript-modules-beginners-guide/', externo: true }
],
- entrega: 'A traves de tu repositorio personal.'
+ entrega: 'A traves de tu repositorio personal.',
+ practica: {
+ titulo: "Pr\u00e1ctica: Express",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab11Express/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab12.js b/data-source/labs/lab12.js
similarity index 96%
rename from labs/data/lab12.js
rename to data-source/labs/lab12.js
index 7c9912d..f46e691 100644
--- a/labs/data/lab12.js
+++ b/data-source/labs/lab12.js
@@ -93,5 +93,10 @@ const LAB = {
{ texto: 'EJS', url: 'https://ejs.co/', externo: true },
{ texto: 'Cross-site Scripting (XSS)', url: 'https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)', externo: true }
],
- entrega: 'A trav\u00e9s de tu repositorio personal.'
+ entrega: 'A trav\u00e9s de tu repositorio personal.',
+ practica: {
+ titulo: "Pr\u00e1ctica: EJS",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab12EJS/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab13.js b/data-source/labs/lab13.js
similarity index 95%
rename from labs/data/lab13.js
rename to data-source/labs/lab13.js
index 6ba8dd6..f99127c 100644
--- a/labs/data/lab13.js
+++ b/data-source/labs/lab13.js
@@ -83,5 +83,10 @@ const LAB = {
{ texto: 'The 20 Essential Principles of Software Development: LoD, SoC, SOLID, and Beyond.', url: 'https://levelup.gitconnected.com/the-20-essential-principles-of-software-development-lod-soc-solid-and-beyond-7a39a98b685d', externo: true },
{ texto: 'Understanding the SOLID Principles', url: 'https://blog.stackademic.com/understanding-the-solid-principles-85c625cc27fc', externo: true }
],
- entrega: 'A trav\u00e9s de tu repositorio personal.'
+ entrega: 'A trav\u00e9s de tu repositorio personal.',
+ practica: {
+ titulo: "Pr\u00e1ctica: MVC",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/LAB13MVC/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab14.js b/data-source/labs/lab14.js
similarity index 95%
rename from labs/data/lab14.js
rename to data-source/labs/lab14.js
index b2daf62..6972e8c 100644
--- a/labs/data/lab14.js
+++ b/data-source/labs/lab14.js
@@ -81,5 +81,10 @@ const LAB = {
{ texto: 'express-session', url: 'https://www.npmjs.com/package/express-session', externo: true },
{ texto: 'connect-flash', url: 'https://www.npmjs.com/package/connect-flash', externo: true }
],
- entrega: 'A trav\u00e9s de tu repositorio personal.'
+ entrega: 'A trav\u00e9s de tu repositorio personal.',
+ practica: {
+ titulo: "Pr\u00e1ctica: Sesiones",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/LAB14Sesiones/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab15.js b/data-source/labs/lab15.js
similarity index 92%
rename from labs/data/lab15.js
rename to data-source/labs/lab15.js
index 15c47a5..19b2bee 100644
--- a/labs/data/lab15.js
+++ b/data-source/labs/lab15.js
@@ -22,7 +22,7 @@ const LAB = {
'
Creaci\u00f3n de las tablas de una base de datos en SQL
' +
'En esta practica crearemos las tablas que nos servir\u00e1n para llevar a cabo las pr\u00e1cticas de laboratorio subsecuentes.
' +
'Se tiene el siguiente modelo entidad-relaci\u00f3n:
' +
- '
' +
+ '
' +
'IMPORTANTE: La fecha y la cantidad son atributos de la relaci\u00f3n Entregan, adicionalmente la fecha debe formar parte de la llave de la tabla que represente a la relaci\u00f3n Entregan ya que un proveedor puede hacer m\u00e1s de una entrega de un mismo material a un mismo proyecto, pero con fecha distinta. Esto es equivalente a tener una "entidad virtual" que es el tiempo, cuya llave es la fecha en la que ocurre la entrega. Dicho de otra forma, el atributo Fecha nos est\u00e1 sirviendo para asegurarnos que cada relaci\u00f3n en la tabla Entregan ser\u00e1 \u00fanica.
' +
'A partir de dicho modelo, el esquema relacional que se deriva es el siguiente:' +
'
' +
@@ -34,8 +34,8 @@ const LAB = {
'
' +
'',
recursos: [
- { texto: 'Script de creaci\u00f3n de tablas y carga de datos', url: '../documentos/bd_laboratorio6_crearTablas_cargarTablas.sql', externo: true },
- { texto: 'Script de carga de datos', url: '../documentos/bd_laboratorio6 _cargarTablas.sql', externo: true }
+ { texto: 'Script de creaci\u00f3n de tablas y carga de datos', url: '/documentos/bd_laboratorio6_crearTablas_cargarTablas.sql', externo: true },
+ { texto: 'Script de carga de datos', url: '/documentos/bd_laboratorio6 _cargarTablas.sql', externo: true }
],
entrega: 'Presta atenci\u00f3n a la sesi\u00f3n de clase en el uso de la herramienta, ya que se dar\u00e1n las instrucciones a detalle de la actividad. Esta actividad es de car\u00e1cter individual y se espera que al final de la misma, tengas la base de datos creada con las tablas creadas.'
};
diff --git a/labs/data/lab17.js b/data-source/labs/lab17.js
similarity index 97%
rename from labs/data/lab17.js
rename to data-source/labs/lab17.js
index 253ef9e..285be8b 100644
--- a/labs/data/lab17.js
+++ b/data-source/labs/lab17.js
@@ -120,5 +120,10 @@ const LAB = {
{ texto: 'CRUD Operations \u2013 What is CRUD?', url: 'https://www.freecodecamp.org/news/crud-operations-explained/', externo: true },
{ texto: 'uuid: M\u00f3dulo para crear id\'s \u00fanicos', url: 'https://www.npmjs.com/package/uuid', externo: true }
],
- entrega: 'A trav\u00e9s de tu repositorio personal (Bitbucket o GitHub).'
+ entrega: 'A trav\u00e9s de tu repositorio personal (Bitbucket o GitHub).',
+ practica: {
+ titulo: "Pr\u00e1ctica: Interacci\u00f3n con Base de Datos",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab17BD/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab18.js b/data-source/labs/lab18.js
similarity index 96%
rename from labs/data/lab18.js
rename to data-source/labs/lab18.js
index 7ca15a1..709420e 100644
--- a/labs/data/lab18.js
+++ b/data-source/labs/lab18.js
@@ -112,5 +112,10 @@ const LAB = {
{ texto: 'csurf', url: 'https://www.npmjs.com/package/csurf', externo: true },
{ texto: 'Double CSRF', url: 'https://www.npmjs.com/package/csrf-csrf', externo: true }
],
- entrega: 'A trav\u00e9s de tu repositorio personal (Bitbucket o GitHub).'
+ entrega: 'A trav\u00e9s de tu repositorio personal (Bitbucket o GitHub).',
+ practica: {
+ titulo: "Pr\u00e1ctica: Autenticaci\u00f3n",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab18Autenticacion/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab19.js b/data-source/labs/lab19.js
similarity index 93%
rename from labs/data/lab19.js
rename to data-source/labs/lab19.js
index 9d074a6..de2cb7d 100644
--- a/labs/data/lab19.js
+++ b/data-source/labs/lab19.js
@@ -39,5 +39,10 @@ const LAB = {
{ texto: 'Lectura: Consultas en SQL usando roles y Sub-consultas.', url: 'lectura6_sql_roles/lectura_sql_roles.html', externo: false },
{ texto: 'RBAC NIST Standard.', url: 'https://csrc.nist.gov/Projects/Role-Based-Access-Control', externo: true }
],
- entrega: 'A trav\u00e9s de Bitbucket o GitHub (Repositorio de Equipo).'
+ entrega: 'A trav\u00e9s de Bitbucket o GitHub (Repositorio de Equipo).',
+ practica: {
+ titulo: "Pr\u00e1ctica: RBAC",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab19RBAC/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab2.js b/data-source/labs/lab2.js
similarity index 93%
rename from labs/data/lab2.js
rename to data-source/labs/lab2.js
index 7bf2e1e..ba44a90 100644
--- a/labs/data/lab2.js
+++ b/data-source/labs/lab2.js
@@ -28,5 +28,10 @@ const LAB = {
{ texto: '10 Important Git Commands that Every Developer Should Know', url: 'https://www.freecodecamp.org/news/10-important-git-commands-that-every-developer-should-know/', externo: true },
{ texto: 'The beginner\'s guide to Git & GitHub', url: 'https://www.freecodecamp.org/news/the-beginners-guide-to-git-github/', externo: true }
],
- entrega: 'En tu malla de evaluaci\u00f3n individual en la secci\u00f3n correspondiente'
+ entrega: 'En tu malla de evaluaci\u00f3n individual en la secci\u00f3n correspondiente',
+ practica: {
+ titulo: "Pr\u00e1ctica: Git",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab2Git/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab20.js b/data-source/labs/lab20.js
similarity index 100%
rename from labs/data/lab20.js
rename to data-source/labs/lab20.js
diff --git a/labs/data/lab21.js b/data-source/labs/lab21.js
similarity index 100%
rename from labs/data/lab21.js
rename to data-source/labs/lab21.js
diff --git a/labs/data/lab22.js b/data-source/labs/lab22.js
similarity index 95%
rename from labs/data/lab22.js
rename to data-source/labs/lab22.js
index 3cbddda..a773636 100644
--- a/labs/data/lab22.js
+++ b/data-source/labs/lab22.js
@@ -83,5 +83,10 @@ const LAB = {
{ texto: 'The Express + Node.js Handbook', url: 'https://www.freecodecamp.org/news/the-express-handbook/', externo: true },
{ texto: 'i18n', url: 'https://www.npmjs.com/package/i18n', externo: true }
],
- entrega: 'A trav\u00e9s de tu repositorio personal (Bitbucket o GitHub).'
+ entrega: 'A trav\u00e9s de tu repositorio personal (Bitbucket o GitHub).',
+ practica: {
+ titulo: "Pr\u00e1ctica: Archivos",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab22Archivos/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab23.js b/data-source/labs/lab23.js
similarity index 100%
rename from labs/data/lab23.js
rename to data-source/labs/lab23.js
diff --git a/labs/data/lab24.js b/data-source/labs/lab24.js
similarity index 96%
rename from labs/data/lab24.js
rename to data-source/labs/lab24.js
index e442a5c..305f34e 100644
--- a/labs/data/lab24.js
+++ b/data-source/labs/lab24.js
@@ -84,5 +84,10 @@ const LAB = {
{ texto: 'A Story of JSON', url: 'https://automationstepbystep.com/2020/05/04/a-story-of-json/', externo: true },
{ texto: 'JSON', url: 'https://www.json.org', externo: true }
],
- entrega: 'A traves de tu repositorio personal (Bitbucket o GitHub)'
+ entrega: 'A traves de tu repositorio personal (Bitbucket o GitHub)',
+ practica: {
+ titulo: "Pr\u00e1ctica: AJAX",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab24AJAX/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab25.js b/data-source/labs/lab25.js
similarity index 100%
rename from labs/data/lab25.js
rename to data-source/labs/lab25.js
diff --git a/labs/data/lab26.js b/data-source/labs/lab26.js
similarity index 100%
rename from labs/data/lab26.js
rename to data-source/labs/lab26.js
diff --git a/labs/data/lab27.js b/data-source/labs/lab27.js
similarity index 100%
rename from labs/data/lab27.js
rename to data-source/labs/lab27.js
diff --git a/labs/data/lab28.js b/data-source/labs/lab28.js
similarity index 100%
rename from labs/data/lab28.js
rename to data-source/labs/lab28.js
diff --git a/labs/data/lab3.js b/data-source/labs/lab3.js
similarity index 92%
rename from labs/data/lab3.js
rename to data-source/labs/lab3.js
index 8aa6e69..57cb250 100644
--- a/labs/data/lab3.js
+++ b/data-source/labs/lab3.js
@@ -15,7 +15,7 @@ const LAB = {
],
instruccionesHtml:
'' +
- '
Crea un CSS externo y agr\u00e9galo al documento(s) que elaboraste en el laboratorio de html para agregarle una buena presentaci\u00f3n a tu sitio.
' +
'
El CSS debe contener al menos un estilo definido con cada uno de los selectores y el estilo debe ser aplicado al documento. Puedes utilizar como gu\u00eda o ayudarte de algunos de los sitios web que se enlistan en la secci\u00f3n de Recursos, o en alg\u00fan otro sitio.' +
'
' +
@@ -32,7 +32,7 @@ const LAB = {
'\u00bfPor qu\u00e9 el uso de una versi\u00f3n minimizada del CSS mejora el rendimiento del sitio?'
],
recursos: [
- { texto: 'The box model', url: '../imagenes/boxmodel.png', externo: false },
+ { texto: 'The box model', url: '/imagenes/boxmodel.png', externo: false },
{ texto: 'Web Design in 4 minutes', url: 'http://jgthms.com/web-design-in-4-minutes', externo: true },
{ texto: 'How to Use CSS Selectors to Style Your Web Page', url: 'https://www.freecodecamp.org/news/use-css-selectors-to-style-webpage/', externo: true },
{ texto: 'The CSS Handbook: a handy guide to CSS for developers', url: 'https://www.freecodecamp.org/news/the-css-handbook-a-handy-guide-to-css-for-developers-b56695917d11/', externo: true },
@@ -53,5 +53,10 @@ const LAB = {
{ texto: 'The super fast color palettes generator', url: 'https://coolors.co/', externo: true },
{ texto: 'BEM -- Block Element Modifier', url: 'http://getbem.com/', externo: true }
],
- entrega: 'A trav\u00e9s de tu repositorio personal (Bitbucket o GitHub)'
+ entrega: 'A trav\u00e9s de tu repositorio personal (Bitbucket o GitHub)',
+ practica: {
+ titulo: "Pr\u00e1ctica: CSS",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab3CSS/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab4.js b/data-source/labs/lab4.js
similarity index 96%
rename from labs/data/lab4.js
rename to data-source/labs/lab4.js
index 2b12e6a..891bd1c 100644
--- a/labs/data/lab4.js
+++ b/data-source/labs/lab4.js
@@ -12,7 +12,7 @@ const LAB = {
],
instruccionesHtml:
'' +
- '
Escribe, prueba y corrige scripts de JavaScript para los siguientes problemas:
' +
'
' +
@@ -112,5 +112,10 @@ const LAB = {
{ texto: 'The Best JavaScript Meme I\'ve Ever Seen, Explained in detail', url: 'https://www.freecodecamp.org/news/explaining-the-best-javascript-meme-i-have-ever-seen/', externo: true },
{ texto: 'Little known features of JavaScript', url: 'https://blog.usejournal.com/little-known-features-of-javascript-901665291387', externo: true }
],
- entrega: 'A trav\u00e9s de tu repositorio personal'
+ entrega: 'A trav\u00e9s de tu repositorio personal',
+ practica: {
+ titulo: "Pr\u00e1ctica: JavaScript",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab4JS/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab5.js b/data-source/labs/lab5.js
similarity index 98%
rename from labs/data/lab5.js
rename to data-source/labs/lab5.js
index bf5338d..f3e6af6 100644
--- a/labs/data/lab5.js
+++ b/data-source/labs/lab5.js
@@ -323,5 +323,10 @@ const LAB = {
{ texto: 'DaisyUI Components', url: 'https://daisyui.com/components/', externo: true },
{ texto: 'Bootstrap Video Tutorials', url: 'https://www.youtube.com/results?search_query=bootstrap+tutorial+2026', externo: true }
],
- entrega: 'A trav\u00e9s de tu repositorio personal.'
+ entrega: 'A trav\u00e9s de tu repositorio personal.',
+ practica: {
+ titulo: "Pr\u00e1ctica: Frameworks de estilo",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab5StyleFramework/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab6.js b/data-source/labs/lab6.js
similarity index 93%
rename from labs/data/lab6.js
rename to data-source/labs/lab6.js
index e03bff9..8b3675f 100644
--- a/labs/data/lab6.js
+++ b/data-source/labs/lab6.js
@@ -40,5 +40,10 @@ const LAB = {
{ texto: 'The Best Front-End Hacking Cheatsheets', url: 'https://medium.com/better-programming/modern-frontend-hacking-cheatsheets-df9c2566c72a', externo: true },
{ texto: 'How to make your first JavaScript chart with JSCharting', url: 'https://www.freecodecamp.org/news/how-to-make-your-first-javascript-chart/', externo: true }
],
- entrega: 'A traves de tu repositorio personal (Bitbucket o GitHub)'
+ entrega: 'A traves de tu repositorio personal (Bitbucket o GitHub)',
+ practica: {
+ titulo: "Pr\u00e1ctica: Programaci\u00f3n Orientada a Eventos",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab6POE/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab7.js b/data-source/labs/lab7.js
similarity index 94%
rename from labs/data/lab7.js
rename to data-source/labs/lab7.js
index 323441e..143c414 100644
--- a/labs/data/lab7.js
+++ b/data-source/labs/lab7.js
@@ -49,5 +49,10 @@ const LAB = {
{ texto: 'The Beginner\'s Guide to Understanding Core Version Control Concepts', url: 'https://www.freecodecamp.org/news/git-the-laymans-guide-to-understanding-the-core-concepts/', externo: true },
{ texto: 'How To Remove Committed Files From Git Version Control', url: 'https://medium.com/better-programming/how-to-remove-committed-files-from-git-version-control-b6533b8f9044', externo: true }
],
- entrega: 'En el canal de discord del equipo'
+ entrega: 'En el canal de discord del equipo',
+ practica: {
+ titulo: "Pr\u00e1ctica: Ramas en Git",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab7Branches/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab8.js b/data-source/labs/lab8.js
similarity index 91%
rename from labs/data/lab8.js
rename to data-source/labs/lab8.js
index c5d8ce3..f51611d 100644
--- a/labs/data/lab8.js
+++ b/data-source/labs/lab8.js
@@ -36,5 +36,10 @@ const LAB = {
{ texto: 'JavaScript Clean Code', url: 'https://javascript.plainenglish.io/javascript-clean-code-all-you-need-to-know-f5db4045a400', externo: true },
{ texto: 'Synchronous vs Asynchronous JavaScript – Call Stack, Promises, and More', url: 'https://www.freecodecamp.org/news/synchronous-vs-asynchronous-in-javascript/', externo: true }
],
- entrega: 'A traves de tu repositorio personal'
+ entrega: 'A traves de tu repositorio personal',
+ practica: {
+ titulo: "Pr\u00e1ctica: Intro al Backend",
+ enlace: "/docs/docs/backend/node/tutorials/intro_web/Lab8IntroBackend/",
+ descripcion: "Consulta la gu\u00eda pr\u00e1ctica completa en el Docusaurus del curso"
+ }
};
diff --git a/labs/data/lab9.js b/data-source/labs/lab9.js
similarity index 100%
rename from labs/data/lab9.js
rename to data-source/labs/lab9.js
diff --git a/labs/data/lab_phprest.js b/data-source/labs/lab_phprest.js
similarity index 100%
rename from labs/data/lab_phprest.js
rename to data-source/labs/lab_phprest.js
diff --git a/labs/data/lab_screenreaders.js b/data-source/labs/lab_screenreaders.js
similarity index 100%
rename from labs/data/lab_screenreaders.js
rename to data-source/labs/lab_screenreaders.js
diff --git a/labs/data/lab_seguridad.js b/data-source/labs/lab_seguridad.js
similarity index 100%
rename from labs/data/lab_seguridad.js
rename to data-source/labs/lab_seguridad.js
diff --git a/labs/data/lab_thinkaloud.js b/data-source/labs/lab_thinkaloud.js
similarity index 100%
rename from labs/data/lab_thinkaloud.js
rename to data-source/labs/lab_thinkaloud.js
diff --git a/data-source/labs/lab_usabilidad.js b/data-source/labs/lab_usabilidad.js
new file mode 100644
index 0000000..8f44dcb
--- /dev/null
+++ b/data-source/labs/lab_usabilidad.js
@@ -0,0 +1,45 @@
+// Created by Denisse Maldonado
+const LAB = {
+ id: 'lab_usabilidad',
+ numero: null,
+ titulo: 'Atomic Design — Construcción de Interfaces Modulares',
+ descripcion: 'Aprende a construir interfaces web desde sus componentes más pequeños hasta páginas completas usando la metodología Atomic Design. Construirás un catálogo de videojuegos paso a paso.',
+ modalidad: 'Colaborativa',
+ objetivos: [
+ 'Entender los 5 niveles de Atomic Design: Átomos, Moléculas, Organismos, Plantillas y Páginas',
+ 'Aprender a identificar y separar componentes en diferentes niveles de complejidad',
+ 'Crear CSS modular y reutilizable siguiendo los principios de Atomic Design',
+ 'Construir una página web completa aplicando la metodología desde cero'
+ ],
+ instruccionesHtml:
+ '' +
+ '
Sigue el tutorial paso a paso para construir el proyecto GameVault:' +
+ '
' +
+ '
Paso 0: Preparación del proyecto y estructura de archivos
' +
+ '
Nivel 1 - Átomos: Crea los elementos más pequeños (botones, badges, ratings)
' +
+ '
Nivel 2 - Moléculas: Combina átomos para formar componentes funcionales
' +
+ '
Nivel 3 - Organismos: Construye secciones completas de la interfaz
' +
+ '
Nivel 4 - Plantilla: Define la estructura de la página
' +
+ '
Nivel 5 - Página: Agrega el contenido real
' +
+ '
' +
+ '
' +
+ '
Cada nivel incluye:' +
+ '
' +
+ '
Explicación conceptual del nivel
' +
+ '
Código CSS y HTML con comentarios explicativos
' +
+ '
Previsualizaciones interactivas en vivo
' +
+ '
Checkpoints para validar tu progreso
' +
+ '
' +
+ '
' +
+ '
Al finalizar, tendrás un proyecto completo organizado con Atomic Design que podrás usar como referencia para futuros proyectos.
' +
+ '',
+ recursos: [
+ { texto: '🎮 Tutorial Completo: GameVault con Atomic Design', url: '/docs/node/tutorials/intro_web/Lab3CSS/AtomicDesign.html', externo: false },
+ { texto: 'Atomic Design Methodology', url: 'https://atomicdesign.bradfrost.com/', externo: true },
+ { texto: 'Pattern Lab - Tool for Atomic Design', url: 'https://patternlab.io/', externo: true },
+ { texto: 'BEM Naming Convention', url: 'https://getbem.com/', externo: true },
+ { texto: 'CSS Variables (Custom Properties)', url: 'https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties', externo: true }
+ ],
+ entrega: 'A través de tu repositorio personal (Bitbucket o GitHub) con la estructura completa del proyecto GameVault.'
+};
\ No newline at end of file
diff --git a/archived/calendars/grupo1_fj22.html b/deprecated/archived/calendars/grupo1_fj22.html
similarity index 100%
rename from archived/calendars/grupo1_fj22.html
rename to deprecated/archived/calendars/grupo1_fj22.html
diff --git a/archived/calendars/grupo1_fj23.html b/deprecated/archived/calendars/grupo1_fj23.html
similarity index 100%
rename from archived/calendars/grupo1_fj23.html
rename to deprecated/archived/calendars/grupo1_fj23.html
diff --git a/archived/calendars/grupo1_fj24.html b/deprecated/archived/calendars/grupo1_fj24.html
similarity index 100%
rename from archived/calendars/grupo1_fj24.html
rename to deprecated/archived/calendars/grupo1_fj24.html
diff --git a/archived/calendars/grupo2_fj22.html b/deprecated/archived/calendars/grupo2_fj22.html
similarity index 100%
rename from archived/calendars/grupo2_fj22.html
rename to deprecated/archived/calendars/grupo2_fj22.html
diff --git a/archived/calendars/grupo2_fj23.html b/deprecated/archived/calendars/grupo2_fj23.html
similarity index 100%
rename from archived/calendars/grupo2_fj23.html
rename to deprecated/archived/calendars/grupo2_fj23.html
diff --git a/archived/calendars/grupo2_fj24.html b/deprecated/archived/calendars/grupo2_fj24.html
similarity index 100%
rename from archived/calendars/grupo2_fj24.html
rename to deprecated/archived/calendars/grupo2_fj24.html
diff --git a/archived/calendars/index_ad18.html b/deprecated/archived/calendars/index_ad18.html
similarity index 100%
rename from archived/calendars/index_ad18.html
rename to deprecated/archived/calendars/index_ad18.html
diff --git a/archived/calendars/index_ad20.html b/deprecated/archived/calendars/index_ad20.html
similarity index 100%
rename from archived/calendars/index_ad20.html
rename to deprecated/archived/calendars/index_ad20.html
diff --git a/archived/calendars/index_ad22.html b/deprecated/archived/calendars/index_ad22.html
similarity index 100%
rename from archived/calendars/index_ad22.html
rename to deprecated/archived/calendars/index_ad22.html
diff --git a/archived/calendars/index_ad23.html b/deprecated/archived/calendars/index_ad23.html
similarity index 100%
rename from archived/calendars/index_ad23.html
rename to deprecated/archived/calendars/index_ad23.html
diff --git a/archived/calendars/index_ad24.html b/deprecated/archived/calendars/index_ad24.html
similarity index 100%
rename from archived/calendars/index_ad24.html
rename to deprecated/archived/calendars/index_ad24.html
diff --git a/archived/calendars/index_em19.html b/deprecated/archived/calendars/index_em19.html
similarity index 100%
rename from archived/calendars/index_em19.html
rename to deprecated/archived/calendars/index_em19.html
diff --git a/archived/calendars/index_fj20.html b/deprecated/archived/calendars/index_fj20.html
similarity index 100%
rename from archived/calendars/index_fj20.html
rename to deprecated/archived/calendars/index_fj20.html
diff --git a/archived/calendars/index_fj21.html b/deprecated/archived/calendars/index_fj21.html
similarity index 100%
rename from archived/calendars/index_fj21.html
rename to deprecated/archived/calendars/index_fj21.html
diff --git a/archived/calendars/index_fj22.html b/deprecated/archived/calendars/index_fj22.html
similarity index 100%
rename from archived/calendars/index_fj22.html
rename to deprecated/archived/calendars/index_fj22.html
diff --git a/archived/calendars/index_fj23.html b/deprecated/archived/calendars/index_fj23.html
similarity index 100%
rename from archived/calendars/index_fj23.html
rename to deprecated/archived/calendars/index_fj23.html
diff --git a/archived/calendars/index_fj24.html b/deprecated/archived/calendars/index_fj24.html
similarity index 100%
rename from archived/calendars/index_fj24.html
rename to deprecated/archived/calendars/index_fj24.html
diff --git a/archived/labs_legacy/lab10phpBD.html b/deprecated/archived/labs_legacy/lab10phpBD.html
similarity index 100%
rename from archived/labs_legacy/lab10phpBD.html
rename to deprecated/archived/labs_legacy/lab10phpBD.html
diff --git a/archived/labs_legacy/lab14a.html b/deprecated/archived/labs_legacy/lab14a.html
similarity index 100%
rename from archived/labs_legacy/lab14a.html
rename to deprecated/archived/labs_legacy/lab14a.html
diff --git a/archived/labs_legacy/lab16MVC.html b/deprecated/archived/labs_legacy/lab16MVC.html
similarity index 100%
rename from archived/labs_legacy/lab16MVC.html
rename to deprecated/archived/labs_legacy/lab16MVC.html
diff --git a/archived/labs_legacy/lab17WSDL.html b/deprecated/archived/labs_legacy/lab17WSDL.html
similarity index 100%
rename from archived/labs_legacy/lab17WSDL.html
rename to deprecated/archived/labs_legacy/lab17WSDL.html
diff --git a/archived/labs_legacy/lab1HtmlEjemplo.html b/deprecated/archived/labs_legacy/lab1HtmlEjemplo.html
similarity index 100%
rename from archived/labs_legacy/lab1HtmlEjemplo.html
rename to deprecated/archived/labs_legacy/lab1HtmlEjemplo.html
diff --git a/archived/labs_legacy/lab26jquery.html b/deprecated/archived/labs_legacy/lab26jquery.html
similarity index 100%
rename from archived/labs_legacy/lab26jquery.html
rename to deprecated/archived/labs_legacy/lab26jquery.html
diff --git a/archived/labs_legacy/lab2Html.html b/deprecated/archived/labs_legacy/lab2Html.html
similarity index 100%
rename from archived/labs_legacy/lab2Html.html
rename to deprecated/archived/labs_legacy/lab2Html.html
diff --git a/archived/labs_legacy/lab2ejemploHtml4.html b/deprecated/archived/labs_legacy/lab2ejemploHtml4.html
similarity index 100%
rename from archived/labs_legacy/lab2ejemploHtml4.html
rename to deprecated/archived/labs_legacy/lab2ejemploHtml4.html
diff --git a/archived/labs_legacy/lab6DynDocsJS.html b/deprecated/archived/labs_legacy/lab6DynDocsJS.html
similarity index 100%
rename from archived/labs_legacy/lab6DynDocsJS.html
rename to deprecated/archived/labs_legacy/lab6DynDocsJS.html
diff --git a/archived/misc/_proyecto.html b/deprecated/archived/misc/_proyecto.html
similarity index 100%
rename from archived/misc/_proyecto.html
rename to deprecated/archived/misc/_proyecto.html
diff --git a/archived/misc/av2.html b/deprecated/archived/misc/av2.html
similarity index 100%
rename from archived/misc/av2.html
rename to deprecated/archived/misc/av2.html
diff --git a/archived/misc/av8.html b/deprecated/archived/misc/av8.html
similarity index 100%
rename from archived/misc/av8.html
rename to deprecated/archived/misc/av8.html
diff --git a/archived/misc/cuestionario1.html b/deprecated/archived/misc/cuestionario1.html
similarity index 100%
rename from archived/misc/cuestionario1.html
rename to deprecated/archived/misc/cuestionario1.html
diff --git a/archived/misc/ejemplo_materialize.html b/deprecated/archived/misc/ejemplo_materialize.html
similarity index 100%
rename from archived/misc/ejemplo_materialize.html
rename to deprecated/archived/misc/ejemplo_materialize.html
diff --git a/archived/misc/jurassicpark.html b/deprecated/archived/misc/jurassicpark.html
similarity index 100%
rename from archived/misc/jurassicpark.html
rename to deprecated/archived/misc/jurassicpark.html
diff --git a/archived/misc/plantillaLab.html b/deprecated/archived/misc/plantillaLab.html
similarity index 100%
rename from archived/misc/plantillaLab.html
rename to deprecated/archived/misc/plantillaLab.html
diff --git a/archived/misc/proyecto.html b/deprecated/archived/misc/proyecto.html
similarity index 100%
rename from archived/misc/proyecto.html
rename to deprecated/archived/misc/proyecto.html
diff --git a/archived/misc/proyecto2.html b/deprecated/archived/misc/proyecto2.html
similarity index 100%
rename from archived/misc/proyecto2.html
rename to deprecated/archived/misc/proyecto2.html
diff --git a/archived/misc/universo_alternativo.html b/deprecated/archived/misc/universo_alternativo.html
similarity index 100%
rename from archived/misc/universo_alternativo.html
rename to deprecated/archived/misc/universo_alternativo.html
diff --git a/archived/misc/zombies.html b/deprecated/archived/misc/zombies.html
similarity index 100%
rename from archived/misc/zombies.html
rename to deprecated/archived/misc/zombies.html
diff --git a/archived/unused_folders/RBAC R/Diccionario de datos.xlsx b/deprecated/archived/unused_folders/RBAC R/Diccionario de datos.xlsx
similarity index 100%
rename from archived/unused_folders/RBAC R/Diccionario de datos.xlsx
rename to deprecated/archived/unused_folders/RBAC R/Diccionario de datos.xlsx
diff --git a/archived/unused_folders/RBAC R/RBAC-2.png b/deprecated/archived/unused_folders/RBAC R/RBAC-2.png
similarity index 100%
rename from archived/unused_folders/RBAC R/RBAC-2.png
rename to deprecated/archived/unused_folders/RBAC R/RBAC-2.png
diff --git a/archived/unused_folders/RBAC R/RBAC.png b/deprecated/archived/unused_folders/RBAC R/RBAC.png
similarity index 100%
rename from archived/unused_folders/RBAC R/RBAC.png
rename to deprecated/archived/unused_folders/RBAC R/RBAC.png
diff --git a/archived/unused_folders/RBAC R/rbac.sql b/deprecated/archived/unused_folders/RBAC R/rbac.sql
similarity index 100%
rename from archived/unused_folders/RBAC R/rbac.sql
rename to deprecated/archived/unused_folders/RBAC R/rbac.sql
diff --git a/archived/unused_folders/csv/entregan.csv b/deprecated/archived/unused_folders/csv/entregan.csv
similarity index 100%
rename from archived/unused_folders/csv/entregan.csv
rename to deprecated/archived/unused_folders/csv/entregan.csv
diff --git a/archived/unused_folders/csv/materiales.csv b/deprecated/archived/unused_folders/csv/materiales.csv
similarity index 100%
rename from archived/unused_folders/csv/materiales.csv
rename to deprecated/archived/unused_folders/csv/materiales.csv
diff --git a/archived/unused_folders/csv/proveedores.csv b/deprecated/archived/unused_folders/csv/proveedores.csv
similarity index 100%
rename from archived/unused_folders/csv/proveedores.csv
rename to deprecated/archived/unused_folders/csv/proveedores.csv
diff --git a/archived/unused_folders/csv/proyectos.csv b/deprecated/archived/unused_folders/csv/proyectos.csv
similarity index 100%
rename from archived/unused_folders/csv/proyectos.csv
rename to deprecated/archived/unused_folders/csv/proyectos.csv
diff --git a/archived/unused_folders/nbproject/private/private.properties b/deprecated/archived/unused_folders/nbproject/private/private.properties
similarity index 100%
rename from archived/unused_folders/nbproject/private/private.properties
rename to deprecated/archived/unused_folders/nbproject/private/private.properties
diff --git a/archived/unused_folders/nbproject/private/private.xml b/deprecated/archived/unused_folders/nbproject/private/private.xml
similarity index 100%
rename from archived/unused_folders/nbproject/private/private.xml
rename to deprecated/archived/unused_folders/nbproject/private/private.xml
diff --git a/archived/unused_folders/nbproject/project.properties b/deprecated/archived/unused_folders/nbproject/project.properties
similarity index 100%
rename from archived/unused_folders/nbproject/project.properties
rename to deprecated/archived/unused_folders/nbproject/project.properties
diff --git a/archived/unused_folders/nbproject/project.xml b/deprecated/archived/unused_folders/nbproject/project.xml
similarity index 100%
rename from archived/unused_folders/nbproject/project.xml
rename to deprecated/archived/unused_folders/nbproject/project.xml
diff --git a/archived/unused_folders/noprob_csv/entregan.csv b/deprecated/archived/unused_folders/noprob_csv/entregan.csv
similarity index 100%
rename from archived/unused_folders/noprob_csv/entregan.csv
rename to deprecated/archived/unused_folders/noprob_csv/entregan.csv
diff --git a/archived/unused_folders/noprob_csv/materiales.csv b/deprecated/archived/unused_folders/noprob_csv/materiales.csv
similarity index 100%
rename from archived/unused_folders/noprob_csv/materiales.csv
rename to deprecated/archived/unused_folders/noprob_csv/materiales.csv
diff --git a/archived/unused_folders/noprob_csv/proveedores.csv b/deprecated/archived/unused_folders/noprob_csv/proveedores.csv
similarity index 100%
rename from archived/unused_folders/noprob_csv/proveedores.csv
rename to deprecated/archived/unused_folders/noprob_csv/proveedores.csv
diff --git a/archived/unused_folders/noprob_csv/proyectos.csv b/deprecated/archived/unused_folders/noprob_csv/proyectos.csv
similarity index 100%
rename from archived/unused_folders/noprob_csv/proyectos.csv
rename to deprecated/archived/unused_folders/noprob_csv/proyectos.csv
diff --git a/archived/unused_folders/recursos/3de9.ttf b/deprecated/archived/unused_folders/recursos/3de9.ttf
similarity index 100%
rename from archived/unused_folders/recursos/3de9.ttf
rename to deprecated/archived/unused_folders/recursos/3de9.ttf
diff --git a/archived/unused_folders/recursos/BD.mdb b/deprecated/archived/unused_folders/recursos/BD.mdb
similarity index 100%
rename from archived/unused_folders/recursos/BD.mdb
rename to deprecated/archived/unused_folders/recursos/BD.mdb
diff --git a/archived/unused_folders/recursos/entregan.csv b/deprecated/archived/unused_folders/recursos/entregan.csv
similarity index 100%
rename from archived/unused_folders/recursos/entregan.csv
rename to deprecated/archived/unused_folders/recursos/entregan.csv
diff --git a/archived/unused_folders/recursos/images.zip b/deprecated/archived/unused_folders/recursos/images.zip
similarity index 100%
rename from archived/unused_folders/recursos/images.zip
rename to deprecated/archived/unused_folders/recursos/images.zip
diff --git a/archived/unused_folders/recursos/images/application.png b/deprecated/archived/unused_folders/recursos/images/application.png
similarity index 100%
rename from archived/unused_folders/recursos/images/application.png
rename to deprecated/archived/unused_folders/recursos/images/application.png
diff --git a/archived/unused_folders/recursos/images/entregan.png b/deprecated/archived/unused_folders/recursos/images/entregan.png
similarity index 100%
rename from archived/unused_folders/recursos/images/entregan.png
rename to deprecated/archived/unused_folders/recursos/images/entregan.png
diff --git a/archived/unused_folders/recursos/images/gEntregan.png b/deprecated/archived/unused_folders/recursos/images/gEntregan.png
similarity index 100%
rename from archived/unused_folders/recursos/images/gEntregan.png
rename to deprecated/archived/unused_folders/recursos/images/gEntregan.png
diff --git a/archived/unused_folders/recursos/images/gMaterial.png b/deprecated/archived/unused_folders/recursos/images/gMaterial.png
similarity index 100%
rename from archived/unused_folders/recursos/images/gMaterial.png
rename to deprecated/archived/unused_folders/recursos/images/gMaterial.png
diff --git a/archived/unused_folders/recursos/images/gProveedor.png b/deprecated/archived/unused_folders/recursos/images/gProveedor.png
similarity index 100%
rename from archived/unused_folders/recursos/images/gProveedor.png
rename to deprecated/archived/unused_folders/recursos/images/gProveedor.png
diff --git a/archived/unused_folders/recursos/images/gProyecto.png b/deprecated/archived/unused_folders/recursos/images/gProyecto.png
similarity index 100%
rename from archived/unused_folders/recursos/images/gProyecto.png
rename to deprecated/archived/unused_folders/recursos/images/gProyecto.png
diff --git a/archived/unused_folders/recursos/images/material.png b/deprecated/archived/unused_folders/recursos/images/material.png
similarity index 100%
rename from archived/unused_folders/recursos/images/material.png
rename to deprecated/archived/unused_folders/recursos/images/material.png
diff --git a/archived/unused_folders/recursos/images/proveedor.png b/deprecated/archived/unused_folders/recursos/images/proveedor.png
similarity index 100%
rename from archived/unused_folders/recursos/images/proveedor.png
rename to deprecated/archived/unused_folders/recursos/images/proveedor.png
diff --git a/archived/unused_folders/recursos/images/proyecto.png b/deprecated/archived/unused_folders/recursos/images/proyecto.png
similarity index 100%
rename from archived/unused_folders/recursos/images/proyecto.png
rename to deprecated/archived/unused_folders/recursos/images/proyecto.png
diff --git a/archived/unused_folders/recursos/materiales.csv b/deprecated/archived/unused_folders/recursos/materiales.csv
similarity index 100%
rename from archived/unused_folders/recursos/materiales.csv
rename to deprecated/archived/unused_folders/recursos/materiales.csv
diff --git a/archived/unused_folders/recursos/modUtil.vb b/deprecated/archived/unused_folders/recursos/modUtil.vb
similarity index 100%
rename from archived/unused_folders/recursos/modUtil.vb
rename to deprecated/archived/unused_folders/recursos/modUtil.vb
diff --git a/archived/unused_folders/recursos/proveedores.csv b/deprecated/archived/unused_folders/recursos/proveedores.csv
similarity index 100%
rename from archived/unused_folders/recursos/proveedores.csv
rename to deprecated/archived/unused_folders/recursos/proveedores.csv
diff --git a/archived/unused_folders/recursos/proyectos.csv b/deprecated/archived/unused_folders/recursos/proyectos.csv
similarity index 100%
rename from archived/unused_folders/recursos/proyectos.csv
rename to deprecated/archived/unused_folders/recursos/proyectos.csv
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/.gitignore b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/.gitignore
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/.gitignore
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/.gitignore
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/README.md b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/README.md
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/README.md
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/README.md
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/package-lock.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/package-lock.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/package-lock.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/package-lock.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/numbers.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/numbers.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/numbers.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/numbers.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/validation.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/validation.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/validation.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-frontend/src/util/validation.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/package-lock.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/package-lock.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/package-lock.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/package-lock.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs-esmodules/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/package-lock.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/package-lock.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/package-lock.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/package-lock.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/01-starting-setup/basic-testing-nodejs/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/02-writing-a-first-test/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/03-the-aaa-pattern/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/04-demo-writing-more-tests/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/05-checking-for-errors/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/numbers.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/numbers.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/numbers.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/numbers.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/06-exercise-solution/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/numbers.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/numbers.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/numbers.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/numbers.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/07-tests-with-multiple-assertions/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/numbers.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/numbers.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/numbers.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/numbers.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/validation.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/validation.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/validation.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/08-more-practice/src/util/validation.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/numbers.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/numbers.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/numbers.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/numbers.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/validation.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/validation.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/validation.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/09-introducing-test-suites/src/util/validation.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/index.html b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/index.html
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/index.html
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/index.html
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/package-lock.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/package-lock.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/package-lock.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/package-lock.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/numbers.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/numbers.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/numbers.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/numbers.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/validation.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/validation.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/validation.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-frontend/src/util/validation.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/package-lock.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/package-lock.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/package-lock.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/package-lock.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/numbers.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/numbers.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/numbers.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/numbers.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/validation.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/validation.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/validation.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs-esmodules/src/util/validation.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/app.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/app.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/app.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/app.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/package-lock.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/package-lock.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/package-lock.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/package-lock.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/math.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/math.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/math.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/math.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/math.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/math.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/math.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/math.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/parser.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/parser.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/parser.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/parser.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/numbers.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/numbers.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/numbers.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/numbers.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/numbers.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/numbers.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/numbers.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/numbers.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/validation.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/validation.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/validation.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/validation.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/validation.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/validation.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/validation.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/code/10-finished/basic-testing-nodejs/src/util/validation.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/01-starting-setup.zip b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/01-starting-setup.zip
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/01-starting-setup.zip
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/01-starting-setup.zip
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/.gitignore b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/.gitignore
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/.gitignore
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/.gitignore
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/async/async-example.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/async/async-example.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/async/async-example.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/async/async-example.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/async/async-example.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/async/async-example.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/async/async-example.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/async/async-example.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/package-lock.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/package-lock.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/package-lock.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/package-lock.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/03-async-code-starting-project/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/.gitignore b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/.gitignore
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/.gitignore
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/.gitignore
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/async/async-example.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/async/async-example.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/async/async-example.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/async/async-example.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/async/async-example.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/async/async-example.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/async/async-example.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/async/async-example.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/hooks/hooks.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/hooks/hooks.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/hooks/hooks.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/hooks/hooks.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/hooks/hooks.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/hooks/hooks.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/hooks/hooks.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/hooks/hooks.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/package.json b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/package.json
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/package.json
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/05-hooks-starting-code/package.json
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/async-code-starting-project.zip b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/async-code-starting-project.zip
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/async-code-starting-project.zip
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/async-code-starting-project.zip
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/hooks-starting-code.zip b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/hooks-starting-code.zip
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/hooks-starting-code.zip
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/hooks-starting-code.zip
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/validation.test.js b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/validation.test.js
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/validation.test.js
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/extra-files/validation.test.js
diff --git a/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/slides/slides.pdf b/deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/slides/slides.pdf
similarity index 100%
rename from archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/slides/slides.pdf
rename to deprecated/archived/unused_folders/tests/js-testing-practical-guide-code-03-basics/slides/slides.pdf
diff --git a/archived/unused_folders/uml/ajax.png b/deprecated/archived/unused_folders/uml/ajax.png
similarity index 100%
rename from archived/unused_folders/uml/ajax.png
rename to deprecated/archived/unused_folders/uml/ajax.png
diff --git a/archived/unused_folders/uml/ajax.puml b/deprecated/archived/unused_folders/uml/ajax.puml
similarity index 100%
rename from archived/unused_folders/uml/ajax.puml
rename to deprecated/archived/unused_folders/uml/ajax.puml
diff --git a/archived/unused_folders/uml/command.txt b/deprecated/archived/unused_folders/uml/command.txt
similarity index 100%
rename from archived/unused_folders/uml/command.txt
rename to deprecated/archived/unused_folders/uml/command.txt
diff --git a/archived/unused_folders/uml/evaluacion.puml b/deprecated/archived/unused_folders/uml/evaluacion.puml
similarity index 100%
rename from archived/unused_folders/uml/evaluacion.puml
rename to deprecated/archived/unused_folders/uml/evaluacion.puml
diff --git a/archived/unused_folders/uml/mvc.puml b/deprecated/archived/unused_folders/uml/mvc.puml
similarity index 100%
rename from archived/unused_folders/uml/mvc.puml
rename to deprecated/archived/unused_folders/uml/mvc.puml
diff --git a/archived/unused_folders/uml/mvc_bd.puml b/deprecated/archived/unused_folders/uml/mvc_bd.puml
similarity index 100%
rename from archived/unused_folders/uml/mvc_bd.puml
rename to deprecated/archived/unused_folders/uml/mvc_bd.puml
diff --git a/archived/unused_folders/uml/mvc_bd_secuencia.png b/deprecated/archived/unused_folders/uml/mvc_bd_secuencia.png
similarity index 100%
rename from archived/unused_folders/uml/mvc_bd_secuencia.png
rename to deprecated/archived/unused_folders/uml/mvc_bd_secuencia.png
diff --git a/archived/unused_folders/uml/mvc_secuencia.png b/deprecated/archived/unused_folders/uml/mvc_secuencia.png
similarity index 100%
rename from archived/unused_folders/uml/mvc_secuencia.png
rename to deprecated/archived/unused_folders/uml/mvc_secuencia.png
diff --git a/archived/unused_folders/uml/rutas.puml b/deprecated/archived/unused_folders/uml/rutas.puml
similarity index 100%
rename from archived/unused_folders/uml/rutas.puml
rename to deprecated/archived/unused_folders/uml/rutas.puml
diff --git a/archived/unused_folders/uml/rutas_formas.png b/deprecated/archived/unused_folders/uml/rutas_formas.png
similarity index 100%
rename from archived/unused_folders/uml/rutas_formas.png
rename to deprecated/archived/unused_folders/uml/rutas_formas.png
diff --git a/archived/unused_folders/uml/wbs.png b/deprecated/archived/unused_folders/uml/wbs.png
similarity index 100%
rename from archived/unused_folders/uml/wbs.png
rename to deprecated/archived/unused_folders/uml/wbs.png
diff --git a/archived/unused_folders/uml/wbs.puml b/deprecated/archived/unused_folders/uml/wbs.puml
similarity index 100%
rename from archived/unused_folders/uml/wbs.puml
rename to deprecated/archived/unused_folders/uml/wbs.puml
diff --git a/backup/documentos/0_bloqueAdmP_introduccion.pptx b/deprecated/content-duplicates/documentos/0_bloqueAdmP_introduccion.pptx
similarity index 100%
rename from backup/documentos/0_bloqueAdmP_introduccion.pptx
rename to deprecated/content-duplicates/documentos/0_bloqueAdmP_introduccion.pptx
diff --git a/backup/documentos/2_bloqueAdmP_comunicacion.pptx b/deprecated/content-duplicates/documentos/2_bloqueAdmP_comunicacion.pptx
similarity index 100%
rename from backup/documentos/2_bloqueAdmP_comunicacion.pptx
rename to deprecated/content-duplicates/documentos/2_bloqueAdmP_comunicacion.pptx
diff --git a/backup/documentos/3_bloqueAdmP_alcance.pptx b/deprecated/content-duplicates/documentos/3_bloqueAdmP_alcance.pptx
similarity index 100%
rename from backup/documentos/3_bloqueAdmP_alcance.pptx
rename to deprecated/content-duplicates/documentos/3_bloqueAdmP_alcance.pptx
diff --git a/backup/documentos/Ajax.ppt b/deprecated/content-duplicates/documentos/Ajax.ppt
similarity index 100%
rename from backup/documentos/Ajax.ppt
rename to deprecated/content-duplicates/documentos/Ajax.ppt
diff --git a/backup/documentos/BeansExample.zip b/deprecated/content-duplicates/documentos/BeansExample.zip
similarity index 100%
rename from backup/documentos/BeansExample.zip
rename to deprecated/content-duplicates/documentos/BeansExample.zip
diff --git a/backup/documentos/CSS.ppt b/deprecated/content-duplicates/documentos/CSS.ppt
similarity index 100%
rename from backup/documentos/CSS.ppt
rename to deprecated/content-duplicates/documentos/CSS.ppt
diff --git a/documentos/CartaConfidencialidadAlumno.pdf b/deprecated/content-duplicates/documentos/CartaConfidencialidadAlumno.pdf
similarity index 100%
rename from documentos/CartaConfidencialidadAlumno.pdf
rename to deprecated/content-duplicates/documentos/CartaConfidencialidadAlumno.pdf
diff --git a/backup/documentos/Caso de Estudio-All-about-pools.pdf b/deprecated/content-duplicates/documentos/Caso de Estudio-All-about-pools.pdf
similarity index 100%
rename from backup/documentos/Caso de Estudio-All-about-pools.pdf
rename to deprecated/content-duplicates/documentos/Caso de Estudio-All-about-pools.pdf
diff --git a/backup/documentos/Casos_de_uso.pptx b/deprecated/content-duplicates/documentos/Casos_de_uso.pptx
similarity index 100%
rename from backup/documentos/Casos_de_uso.pptx
rename to deprecated/content-duplicates/documentos/Casos_de_uso.pptx
diff --git a/backup/documentos/CloudSQL-GAE-config.pdf b/deprecated/content-duplicates/documentos/CloudSQL-GAE-config.pdf
similarity index 100%
rename from backup/documentos/CloudSQL-GAE-config.pdf
rename to deprecated/content-duplicates/documentos/CloudSQL-GAE-config.pdf
diff --git a/documentos/Coevaluacion.docx b/deprecated/content-duplicates/documentos/Coevaluacion.docx
similarity index 100%
rename from documentos/Coevaluacion.docx
rename to deprecated/content-duplicates/documentos/Coevaluacion.docx
diff --git a/backup/documentos/Conceptos - Requisitos de Software.pptx b/deprecated/content-duplicates/documentos/Conceptos - Requisitos de Software.pptx
similarity index 100%
rename from backup/documentos/Conceptos - Requisitos de Software.pptx
rename to deprecated/content-duplicates/documentos/Conceptos - Requisitos de Software.pptx
diff --git a/backup/documentos/CuentasServidorEM17.docx b/deprecated/content-duplicates/documentos/CuentasServidorEM17.docx
similarity index 100%
rename from backup/documentos/CuentasServidorEM17.docx
rename to deprecated/content-duplicates/documentos/CuentasServidorEM17.docx
diff --git a/backup/documentos/DAWAgo2014.pdf b/deprecated/content-duplicates/documentos/DAWAgo2014.pdf
similarity index 100%
rename from backup/documentos/DAWAgo2014.pdf
rename to deprecated/content-duplicates/documentos/DAWAgo2014.pdf
diff --git a/backup/documentos/DAWAgo2015.pdf b/deprecated/content-duplicates/documentos/DAWAgo2015.pdf
similarity index 100%
rename from backup/documentos/DAWAgo2015.pdf
rename to deprecated/content-duplicates/documentos/DAWAgo2015.pdf
diff --git a/backup/documentos/Diagrama de contexto.pptx b/deprecated/content-duplicates/documentos/Diagrama de contexto.pptx
similarity index 100%
rename from backup/documentos/Diagrama de contexto.pptx
rename to deprecated/content-duplicates/documentos/Diagrama de contexto.pptx
diff --git a/backup/documentos/DiagramaClases_Videojuego.pdf b/deprecated/content-duplicates/documentos/DiagramaClases_Videojuego.pdf
similarity index 100%
rename from backup/documentos/DiagramaClases_Videojuego.pdf
rename to deprecated/content-duplicates/documentos/DiagramaClases_Videojuego.pdf
diff --git a/backup/documentos/Diagramas de despliegue.pptx b/deprecated/content-duplicates/documentos/Diagramas de despliegue.pptx
similarity index 100%
rename from backup/documentos/Diagramas de despliegue.pptx
rename to deprecated/content-duplicates/documentos/Diagramas de despliegue.pptx
diff --git a/backup/documentos/Diagramas de estado.pptx b/deprecated/content-duplicates/documentos/Diagramas de estado.pptx
similarity index 100%
rename from backup/documentos/Diagramas de estado.pptx
rename to deprecated/content-duplicates/documentos/Diagramas de estado.pptx
diff --git a/backup/documentos/Ejercicio - Diagrama de despliegue.docx b/deprecated/content-duplicates/documentos/Ejercicio - Diagrama de despliegue.docx
similarity index 100%
rename from backup/documentos/Ejercicio - Diagrama de despliegue.docx
rename to deprecated/content-duplicates/documentos/Ejercicio - Diagrama de despliegue.docx
diff --git a/backup/documentos/EjerciciosDiagramaEstado.docx b/deprecated/content-duplicates/documentos/EjerciciosDiagramaEstado.docx
similarity index 100%
rename from backup/documentos/EjerciciosDiagramaEstado.docx
rename to deprecated/content-duplicates/documentos/EjerciciosDiagramaEstado.docx
diff --git a/backup/documentos/EjerciciosDiagramaSecuencia.docx b/deprecated/content-duplicates/documentos/EjerciciosDiagramaSecuencia.docx
similarity index 100%
rename from backup/documentos/EjerciciosDiagramaSecuencia.docx
rename to deprecated/content-duplicates/documentos/EjerciciosDiagramaSecuencia.docx
diff --git a/backup/documentos/GeneratingTestCasesFromUseCasesJune01.pdf b/deprecated/content-duplicates/documentos/GeneratingTestCasesFromUseCasesJune01.pdf
similarity index 100%
rename from backup/documentos/GeneratingTestCasesFromUseCasesJune01.pdf
rename to deprecated/content-duplicates/documentos/GeneratingTestCasesFromUseCasesJune01.pdf
diff --git a/backup/documentos/IntroJavaScript.pptx b/deprecated/content-duplicates/documentos/IntroJavaScript.pptx
similarity index 100%
rename from backup/documentos/IntroJavaScript.pptx
rename to deprecated/content-duplicates/documentos/IntroJavaScript.pptx
diff --git a/backup/documentos/Introduccion al diseno de software - Diagramas de clases.pptx b/deprecated/content-duplicates/documentos/Introduccion al diseno de software - Diagramas de clases.pptx
similarity index 100%
rename from backup/documentos/Introduccion al diseno de software - Diagramas de clases.pptx
rename to deprecated/content-duplicates/documentos/Introduccion al diseno de software - Diagramas de clases.pptx
diff --git a/backup/documentos/Introduccion al diseno de software - Diagramas de secuencia.pptx b/deprecated/content-duplicates/documentos/Introduccion al diseno de software - Diagramas de secuencia.pptx
similarity index 100%
rename from backup/documentos/Introduccion al diseno de software - Diagramas de secuencia.pptx
rename to deprecated/content-duplicates/documentos/Introduccion al diseno de software - Diagramas de secuencia.pptx
diff --git a/backup/documentos/JEEMVC.ppt b/deprecated/content-duplicates/documentos/JEEMVC.ppt
similarity index 100%
rename from backup/documentos/JEEMVC.ppt
rename to deprecated/content-duplicates/documentos/JEEMVC.ppt
diff --git a/backup/documentos/JSPBeansJSTL.ppt b/deprecated/content-duplicates/documentos/JSPBeansJSTL.ppt
similarity index 100%
rename from backup/documentos/JSPBeansJSTL.ppt
rename to deprecated/content-duplicates/documentos/JSPBeansJSTL.ppt
diff --git a/backup/documentos/JSPIntroduction.ppt b/deprecated/content-duplicates/documentos/JSPIntroduction.ppt
similarity index 100%
rename from backup/documentos/JSPIntroduction.ppt
rename to deprecated/content-duplicates/documentos/JSPIntroduction.ppt
diff --git a/backup/documentos/MVC.zip b/deprecated/content-duplicates/documentos/MVC.zip
similarity index 100%
rename from backup/documentos/MVC.zip
rename to deprecated/content-duplicates/documentos/MVC.zip
diff --git a/backup/documentos/MapaConceptual.pptx b/deprecated/content-duplicates/documentos/MapaConceptual.pptx
similarity index 100%
rename from backup/documentos/MapaConceptual.pptx
rename to deprecated/content-duplicates/documentos/MapaConceptual.pptx
diff --git a/backup/documentos/PHP-MultAppAarchTheModel.ppt b/deprecated/content-duplicates/documentos/PHP-MultAppAarchTheModel.ppt
similarity index 100%
rename from backup/documentos/PHP-MultAppAarchTheModel.ppt
rename to deprecated/content-duplicates/documentos/PHP-MultAppAarchTheModel.ppt
diff --git a/backup/documentos/PHP-MultAppArchitecture.ppt b/deprecated/content-duplicates/documentos/PHP-MultAppArchitecture.ppt
similarity index 100%
rename from backup/documentos/PHP-MultAppArchitecture.ppt
rename to deprecated/content-duplicates/documentos/PHP-MultAppArchitecture.ppt
diff --git a/backup/documentos/PHP-RESTws.pptx b/deprecated/content-duplicates/documentos/PHP-RESTws.pptx
similarity index 100%
rename from backup/documentos/PHP-RESTws.pptx
rename to deprecated/content-duplicates/documentos/PHP-RESTws.pptx
diff --git a/backup/documentos/PHP-RESTws_es.pptx b/deprecated/content-duplicates/documentos/PHP-RESTws_es.pptx
similarity index 100%
rename from backup/documentos/PHP-RESTws_es.pptx
rename to deprecated/content-duplicates/documentos/PHP-RESTws_es.pptx
diff --git a/backup/documentos/PHPAjax.ppt b/deprecated/content-duplicates/documentos/PHPAjax.ppt
similarity index 100%
rename from backup/documentos/PHPAjax.ppt
rename to deprecated/content-duplicates/documentos/PHPAjax.ppt
diff --git a/backup/documentos/PHPSOAPClient.pptx b/deprecated/content-duplicates/documentos/PHPSOAPClient.pptx
similarity index 100%
rename from backup/documentos/PHPSOAPClient.pptx
rename to deprecated/content-duplicates/documentos/PHPSOAPClient.pptx
diff --git a/backup/documentos/POE.pptx b/deprecated/content-duplicates/documentos/POE.pptx
similarity index 100%
rename from backup/documentos/POE.pptx
rename to deprecated/content-duplicates/documentos/POE.pptx
diff --git a/backup/documentos/PhpWebServices.pptx b/deprecated/content-duplicates/documentos/PhpWebServices.pptx
similarity index 100%
rename from backup/documentos/PhpWebServices.pptx
rename to deprecated/content-duplicates/documentos/PhpWebServices.pptx
diff --git a/backup/documentos/PoliticasDADIEne2014.pdf b/deprecated/content-duplicates/documentos/PoliticasDADIEne2014.pdf
similarity index 100%
rename from backup/documentos/PoliticasDADIEne2014.pdf
rename to deprecated/content-duplicates/documentos/PoliticasDADIEne2014.pdf
diff --git a/backup/documentos/PoliticasDAWAgo2014.pdf b/deprecated/content-duplicates/documentos/PoliticasDAWAgo2014.pdf
similarity index 100%
rename from backup/documentos/PoliticasDAWAgo2014.pdf
rename to deprecated/content-duplicates/documentos/PoliticasDAWAgo2014.pdf
diff --git a/backup/documentos/PoliticasDAWAgo2016.pdf b/deprecated/content-duplicates/documentos/PoliticasDAWAgo2016.pdf
similarity index 100%
rename from backup/documentos/PoliticasDAWAgo2016.pdf
rename to deprecated/content-duplicates/documentos/PoliticasDAWAgo2016.pdf
diff --git a/backup/documentos/PoliticasDAWAgo2017.pdf b/deprecated/content-duplicates/documentos/PoliticasDAWAgo2017.pdf
similarity index 100%
rename from backup/documentos/PoliticasDAWAgo2017.pdf
rename to deprecated/content-duplicates/documentos/PoliticasDAWAgo2017.pdf
diff --git a/backup/documentos/PoliticasDAWEne2014.pdf b/deprecated/content-duplicates/documentos/PoliticasDAWEne2014.pdf
similarity index 100%
rename from backup/documentos/PoliticasDAWEne2014.pdf
rename to deprecated/content-duplicates/documentos/PoliticasDAWEne2014.pdf
diff --git a/backup/documentos/PoliticasDAWEne2015.pdf b/deprecated/content-duplicates/documentos/PoliticasDAWEne2015.pdf
similarity index 100%
rename from backup/documentos/PoliticasDAWEne2015.pdf
rename to deprecated/content-duplicates/documentos/PoliticasDAWEne2015.pdf
diff --git a/backup/documentos/PoliticasDAWEne2016.pdf b/deprecated/content-duplicates/documentos/PoliticasDAWEne2016.pdf
similarity index 100%
rename from backup/documentos/PoliticasDAWEne2016.pdf
rename to deprecated/content-duplicates/documentos/PoliticasDAWEne2016.pdf
diff --git a/backup/documentos/PoliticasDAWEne2017.pdf b/deprecated/content-duplicates/documentos/PoliticasDAWEne2017.pdf
similarity index 100%
rename from backup/documentos/PoliticasDAWEne2017.pdf
rename to deprecated/content-duplicates/documentos/PoliticasDAWEne2017.pdf
diff --git a/backup/documentos/PoliticasDAWEne2018.pdf b/deprecated/content-duplicates/documentos/PoliticasDAWEne2018.pdf
similarity index 100%
rename from backup/documentos/PoliticasDAWEne2018.pdf
rename to deprecated/content-duplicates/documentos/PoliticasDAWEne2018.pdf
diff --git a/backup/documentos/Proyecto2Evaluacion.pdf b/deprecated/content-duplicates/documentos/Proyecto2Evaluacion.pdf
similarity index 100%
rename from backup/documentos/Proyecto2Evaluacion.pdf
rename to deprecated/content-duplicates/documentos/Proyecto2Evaluacion.pdf
diff --git a/backup/documentos/Proyecto3Evaluacion.pdf b/deprecated/content-duplicates/documentos/Proyecto3Evaluacion.pdf
similarity index 100%
rename from backup/documentos/Proyecto3Evaluacion.pdf
rename to deprecated/content-duplicates/documentos/Proyecto3Evaluacion.pdf
diff --git a/backup/documentos/ProyectoEvaluacion2.pdf b/deprecated/content-duplicates/documentos/ProyectoEvaluacion2.pdf
similarity index 100%
rename from backup/documentos/ProyectoEvaluacion2.pdf
rename to deprecated/content-duplicates/documentos/ProyectoEvaluacion2.pdf
diff --git a/backup/documentos/ProyectoFinalEvaluacion.pdf b/deprecated/content-duplicates/documentos/ProyectoFinalEvaluacion.pdf
similarity index 100%
rename from backup/documentos/ProyectoFinalEvaluacion.pdf
rename to deprecated/content-duplicates/documentos/ProyectoFinalEvaluacion.pdf
diff --git a/backup/documentos/ProyectosQPET.pdf b/deprecated/content-duplicates/documentos/ProyectosQPET.pdf
similarity index 100%
rename from backup/documentos/ProyectosQPET.pdf
rename to deprecated/content-duplicates/documentos/ProyectosQPET.pdf
diff --git a/backup/documentos/RESTful Web Services.pptx b/deprecated/content-duplicates/documentos/RESTful Web Services.pptx
similarity index 100%
rename from backup/documentos/RESTful Web Services.pptx
rename to deprecated/content-duplicates/documentos/RESTful Web Services.pptx
diff --git a/backup/documentos/Requisitos de Software - Casos de Uso.pptx b/deprecated/content-duplicates/documentos/Requisitos de Software - Casos de Uso.pptx
similarity index 100%
rename from backup/documentos/Requisitos de Software - Casos de Uso.pptx
rename to deprecated/content-duplicates/documentos/Requisitos de Software - Casos de Uso.pptx
diff --git a/backup/documentos/Requisitos.pptx b/deprecated/content-duplicates/documentos/Requisitos.pptx
similarity index 100%
rename from backup/documentos/Requisitos.pptx
rename to deprecated/content-duplicates/documentos/Requisitos.pptx
diff --git a/backup/documentos/Rubrica_investigacion.docx b/deprecated/content-duplicates/documentos/Rubrica_investigacion.docx
similarity index 100%
rename from backup/documentos/Rubrica_investigacion.docx
rename to deprecated/content-duplicates/documentos/Rubrica_investigacion.docx
diff --git a/backup/documentos/ServletsDatabases.ppt b/deprecated/content-duplicates/documentos/ServletsDatabases.ppt
similarity index 100%
rename from backup/documentos/ServletsDatabases.ppt
rename to deprecated/content-duplicates/documentos/ServletsDatabases.ppt
diff --git a/backup/documentos/ServletsIntro.pptx b/deprecated/content-duplicates/documentos/ServletsIntro.pptx
similarity index 100%
rename from backup/documentos/ServletsIntro.pptx
rename to deprecated/content-duplicates/documentos/ServletsIntro.pptx
diff --git a/backup/documentos/Tema01-Introduccion_SI.pptx b/deprecated/content-duplicates/documentos/Tema01-Introduccion_SI.pptx
similarity index 100%
rename from backup/documentos/Tema01-Introduccion_SI.pptx
rename to deprecated/content-duplicates/documentos/Tema01-Introduccion_SI.pptx
diff --git a/backup/documentos/Tema02-Ciclo_de_vida_SI.pptx b/deprecated/content-duplicates/documentos/Tema02-Ciclo_de_vida_SI.pptx
similarity index 100%
rename from backup/documentos/Tema02-Ciclo_de_vida_SI.pptx
rename to deprecated/content-duplicates/documentos/Tema02-Ciclo_de_vida_SI.pptx
diff --git a/backup/documentos/Usabilidad.pptx b/deprecated/content-duplicates/documentos/Usabilidad.pptx
similarity index 100%
rename from backup/documentos/Usabilidad.pptx
rename to deprecated/content-duplicates/documentos/Usabilidad.pptx
diff --git a/backup/documentos/Videojuegos.pdf b/deprecated/content-duplicates/documentos/Videojuegos.pdf
similarity index 100%
rename from backup/documentos/Videojuegos.pdf
rename to deprecated/content-duplicates/documentos/Videojuegos.pdf
diff --git a/backup/documentos/WebServices.pptx b/deprecated/content-duplicates/documentos/WebServices.pptx
similarity index 100%
rename from backup/documentos/WebServices.pptx
rename to deprecated/content-duplicates/documentos/WebServices.pptx
diff --git a/backup/documentos/XML.pptx b/deprecated/content-duplicates/documentos/XML.pptx
similarity index 100%
rename from backup/documentos/XML.pptx
rename to deprecated/content-duplicates/documentos/XML.pptx
diff --git a/backup/documentos/bd_laboratorio6 _cargarTablas.sql b/deprecated/content-duplicates/documentos/bd_laboratorio6 _cargarTablas.sql
similarity index 100%
rename from backup/documentos/bd_laboratorio6 _cargarTablas.sql
rename to deprecated/content-duplicates/documentos/bd_laboratorio6 _cargarTablas.sql
diff --git a/backup/documentos/bd_laboratorio6_crearTablas_cargarTablas.sql b/deprecated/content-duplicates/documentos/bd_laboratorio6_crearTablas_cargarTablas.sql
similarity index 100%
rename from backup/documentos/bd_laboratorio6_crearTablas_cargarTablas.sql
rename to deprecated/content-duplicates/documentos/bd_laboratorio6_crearTablas_cargarTablas.sql
diff --git a/backup/documentos/boxmodel.jpg b/deprecated/content-duplicates/documentos/boxmodel.jpg
similarity index 100%
rename from backup/documentos/boxmodel.jpg
rename to deprecated/content-duplicates/documentos/boxmodel.jpg
diff --git a/backup/documentos/evalProyectoP1.pdf b/deprecated/content-duplicates/documentos/evalProyectoP1.pdf
similarity index 100%
rename from backup/documentos/evalProyectoP1.pdf
rename to deprecated/content-duplicates/documentos/evalProyectoP1.pdf
diff --git a/backup/documentos/imc.zip b/deprecated/content-duplicates/documentos/imc.zip
similarity index 100%
rename from backup/documentos/imc.zip
rename to deprecated/content-duplicates/documentos/imc.zip
diff --git a/backup/documentos/introPHP.ppt b/deprecated/content-duplicates/documentos/introPHP.ppt
similarity index 100%
rename from backup/documentos/introPHP.ppt
rename to deprecated/content-duplicates/documentos/introPHP.ppt
diff --git a/backup/documentos/versionControl.pptx b/deprecated/content-duplicates/documentos/versionControl.pptx
similarity index 100%
rename from backup/documentos/versionControl.pptx
rename to deprecated/content-duplicates/documentos/versionControl.pptx
diff --git a/backup/documentos/~$PHP-MultAppAarchTheModel.ppt b/deprecated/content-duplicates/documentos/~$PHP-MultAppAarchTheModel.ppt
similarity index 100%
rename from backup/documentos/~$PHP-MultAppAarchTheModel.ppt
rename to deprecated/content-duplicates/documentos/~$PHP-MultAppAarchTheModel.ppt
diff --git a/ejercicios/ej1_MER_farmaceutica_futbol.html b/deprecated/content-duplicates/ejercicios/ej1_MER_farmaceutica_futbol.html
similarity index 100%
rename from ejercicios/ej1_MER_farmaceutica_futbol.html
rename to deprecated/content-duplicates/ejercicios/ej1_MER_farmaceutica_futbol.html
diff --git a/ejercicios/ej2_MER_DD_MR.html b/deprecated/content-duplicates/ejercicios/ej2_MER_DD_MR.html
similarity index 100%
rename from ejercicios/ej2_MER_DD_MR.html
rename to deprecated/content-duplicates/ejercicios/ej2_MER_DD_MR.html
diff --git a/backup/ej3_mr_ar/ar.jpg b/deprecated/content-duplicates/ejercicios/ej3_mr_ar/ar.jpg
similarity index 100%
rename from backup/ej3_mr_ar/ar.jpg
rename to deprecated/content-duplicates/ejercicios/ej3_mr_ar/ar.jpg
diff --git a/ejercicios/ej3_mr_ar/ej3.html b/deprecated/content-duplicates/ejercicios/ej3_mr_ar/ej3.html
similarity index 100%
rename from ejercicios/ej3_mr_ar/ej3.html
rename to deprecated/content-duplicates/ejercicios/ej3_mr_ar/ej3.html
diff --git a/backup/ej3_mr_ar/mr.jpg b/deprecated/content-duplicates/ejercicios/ej3_mr_ar/mr.jpg
similarity index 100%
rename from backup/ej3_mr_ar/mr.jpg
rename to deprecated/content-duplicates/ejercicios/ej3_mr_ar/mr.jpg
diff --git a/ejercicios/ej4_ar_sql.html b/deprecated/content-duplicates/ejercicios/ej4_ar_sql.html
similarity index 100%
rename from ejercicios/ej4_ar_sql.html
rename to deprecated/content-duplicates/ejercicios/ej4_ar_sql.html
diff --git a/ejercicios/ej5_ar_sql2.html b/deprecated/content-duplicates/ejercicios/ej5_ar_sql2.html
similarity index 100%
rename from ejercicios/ej5_ar_sql2.html
rename to deprecated/content-duplicates/ejercicios/ej5_ar_sql2.html
diff --git a/ejercicios/ej6_sql_subconsultas.html b/deprecated/content-duplicates/ejercicios/ej6_sql_subconsultas.html
similarity index 100%
rename from ejercicios/ej6_sql_subconsultas.html
rename to deprecated/content-duplicates/ejercicios/ej6_sql_subconsultas.html
diff --git a/ejercicios/ej9_normalizacion.html b/deprecated/content-duplicates/ejercicios/ej9_normalizacion.html
similarity index 100%
rename from ejercicios/ej9_normalizacion.html
rename to deprecated/content-duplicates/ejercicios/ej9_normalizacion.html
diff --git a/backup/imagenes/Bullet2.gif b/deprecated/content-duplicates/imagenes/Bullet2.gif
similarity index 100%
rename from backup/imagenes/Bullet2.gif
rename to deprecated/content-duplicates/imagenes/Bullet2.gif
diff --git a/backup/imagenes/CrystalReports.png b/deprecated/content-duplicates/imagenes/CrystalReports.png
similarity index 100%
rename from backup/imagenes/CrystalReports.png
rename to deprecated/content-duplicates/imagenes/CrystalReports.png
diff --git a/backup/imagenes/TgC_boton30.gif b/deprecated/content-duplicates/imagenes/TgC_boton30.gif
similarity index 100%
rename from backup/imagenes/TgC_boton30.gif
rename to deprecated/content-duplicates/imagenes/TgC_boton30.gif
diff --git a/backup/imagenes/actividades.jpg b/deprecated/content-duplicates/imagenes/actividades.jpg
similarity index 100%
rename from backup/imagenes/actividades.jpg
rename to deprecated/content-duplicates/imagenes/actividades.jpg
diff --git a/backup/imagenes/b_regr.gif b/deprecated/content-duplicates/imagenes/b_regr.gif
similarity index 100%
rename from backup/imagenes/b_regr.gif
rename to deprecated/content-duplicates/imagenes/b_regr.gif
diff --git a/backup/imagenes/ball.gif b/deprecated/content-duplicates/imagenes/ball.gif
similarity index 100%
rename from backup/imagenes/ball.gif
rename to deprecated/content-duplicates/imagenes/ball.gif
diff --git a/backup/imagenes/bookBlanco.gif b/deprecated/content-duplicates/imagenes/bookBlanco.gif
similarity index 100%
rename from backup/imagenes/bookBlanco.gif
rename to deprecated/content-duplicates/imagenes/bookBlanco.gif
diff --git a/backup/imagenes/bookBlanco_old.gif b/deprecated/content-duplicates/imagenes/bookBlanco_old.gif
similarity index 100%
rename from backup/imagenes/bookBlanco_old.gif
rename to deprecated/content-duplicates/imagenes/bookBlanco_old.gif
diff --git a/backup/imagenes/boxmodel.png b/deprecated/content-duplicates/imagenes/boxmodel.png
similarity index 100%
rename from backup/imagenes/boxmodel.png
rename to deprecated/content-duplicates/imagenes/boxmodel.png
diff --git a/backup/imagenes/calendario.jpg b/deprecated/content-duplicates/imagenes/calendario.jpg
similarity index 100%
rename from backup/imagenes/calendario.jpg
rename to deprecated/content-duplicates/imagenes/calendario.jpg
diff --git a/backup/imagenes/captura1.PNG b/deprecated/content-duplicates/imagenes/captura1.PNG
similarity index 100%
rename from backup/imagenes/captura1.PNG
rename to deprecated/content-duplicates/imagenes/captura1.PNG
diff --git a/backup/imagenes/captura2.PNG b/deprecated/content-duplicates/imagenes/captura2.PNG
similarity index 100%
rename from backup/imagenes/captura2.PNG
rename to deprecated/content-duplicates/imagenes/captura2.PNG
diff --git a/backup/imagenes/captura3.PNG b/deprecated/content-duplicates/imagenes/captura3.PNG
similarity index 100%
rename from backup/imagenes/captura3.PNG
rename to deprecated/content-duplicates/imagenes/captura3.PNG
diff --git a/backup/imagenes/captura4.PNG b/deprecated/content-duplicates/imagenes/captura4.PNG
similarity index 100%
rename from backup/imagenes/captura4.PNG
rename to deprecated/content-duplicates/imagenes/captura4.PNG
diff --git a/backup/imagenes/captura5.PNG b/deprecated/content-duplicates/imagenes/captura5.PNG
similarity index 100%
rename from backup/imagenes/captura5.PNG
rename to deprecated/content-duplicates/imagenes/captura5.PNG
diff --git a/backup/imagenes/captura6.PNG b/deprecated/content-duplicates/imagenes/captura6.PNG
similarity index 100%
rename from backup/imagenes/captura6.PNG
rename to deprecated/content-duplicates/imagenes/captura6.PNG
diff --git a/backup/imagenes/captura7.PNG b/deprecated/content-duplicates/imagenes/captura7.PNG
similarity index 100%
rename from backup/imagenes/captura7.PNG
rename to deprecated/content-duplicates/imagenes/captura7.PNG
diff --git a/backup/imagenes/captura8.PNG b/deprecated/content-duplicates/imagenes/captura8.PNG
similarity index 100%
rename from backup/imagenes/captura8.PNG
rename to deprecated/content-duplicates/imagenes/captura8.PNG
diff --git a/backup/imagenes/captura_ejemplo_estructura.PNG b/deprecated/content-duplicates/imagenes/captura_ejemplo_estructura.PNG
similarity index 100%
rename from backup/imagenes/captura_ejemplo_estructura.PNG
rename to deprecated/content-duplicates/imagenes/captura_ejemplo_estructura.PNG
diff --git a/backup/imagenes/capture10.PNG b/deprecated/content-duplicates/imagenes/capture10.PNG
similarity index 100%
rename from backup/imagenes/capture10.PNG
rename to deprecated/content-duplicates/imagenes/capture10.PNG
diff --git a/backup/imagenes/capture9.PNG b/deprecated/content-duplicates/imagenes/capture9.PNG
similarity index 100%
rename from backup/imagenes/capture9.PNG
rename to deprecated/content-duplicates/imagenes/capture9.PNG
diff --git a/backup/imagenes/cuentasServer.png b/deprecated/content-duplicates/imagenes/cuentasServer.png
similarity index 100%
rename from backup/imagenes/cuentasServer.png
rename to deprecated/content-duplicates/imagenes/cuentasServer.png
diff --git a/backup/imagenes/descripcion.gif b/deprecated/content-duplicates/imagenes/descripcion.gif
similarity index 100%
rename from backup/imagenes/descripcion.gif
rename to deprecated/content-duplicates/imagenes/descripcion.gif
diff --git a/backup/imagenes/ejemplo_controlador.PNG b/deprecated/content-duplicates/imagenes/ejemplo_controlador.PNG
similarity index 100%
rename from backup/imagenes/ejemplo_controlador.PNG
rename to deprecated/content-duplicates/imagenes/ejemplo_controlador.PNG
diff --git "a/backup/imagenes/elementos sem\303\241nticos articulo html5.png" "b/deprecated/content-duplicates/imagenes/elementos sem\303\241nticos articulo html5.png"
similarity index 100%
rename from "backup/imagenes/elementos sem\303\241nticos articulo html5.png"
rename to "deprecated/content-duplicates/imagenes/elementos sem\303\241nticos articulo html5.png"
diff --git "a/backup/imagenes/elementos sem\303\241nticos html5.png" "b/deprecated/content-duplicates/imagenes/elementos sem\303\241nticos html5.png"
similarity index 100%
rename from "backup/imagenes/elementos sem\303\241nticos html5.png"
rename to "deprecated/content-duplicates/imagenes/elementos sem\303\241nticos html5.png"
diff --git a/backup/imagenes/entrega.gif b/deprecated/content-duplicates/imagenes/entrega.gif
similarity index 100%
rename from backup/imagenes/entrega.gif
rename to deprecated/content-duplicates/imagenes/entrega.gif
diff --git a/backup/imagenes/entregan.png b/deprecated/content-duplicates/imagenes/entregan.png
similarity index 100%
rename from backup/imagenes/entregan.png
rename to deprecated/content-duplicates/imagenes/entregan.png
diff --git a/backup/imagenes/equipo.jpg b/deprecated/content-duplicates/imagenes/equipo.jpg
similarity index 100%
rename from backup/imagenes/equipo.jpg
rename to deprecated/content-duplicates/imagenes/equipo.jpg
diff --git a/backup/imagenes/especificaciones.jpg b/deprecated/content-duplicates/imagenes/especificaciones.jpg
similarity index 100%
rename from backup/imagenes/especificaciones.jpg
rename to deprecated/content-duplicates/imagenes/especificaciones.jpg
diff --git a/backup/imagenes/events.gif b/deprecated/content-duplicates/imagenes/events.gif
similarity index 100%
rename from backup/imagenes/events.gif
rename to deprecated/content-duplicates/imagenes/events.gif
diff --git a/backup/imagenes/gliffy.png b/deprecated/content-duplicates/imagenes/gliffy.png
similarity index 100%
rename from backup/imagenes/gliffy.png
rename to deprecated/content-duplicates/imagenes/gliffy.png
diff --git a/backup/imagenes/gliffy_2.png b/deprecated/content-duplicates/imagenes/gliffy_2.png
similarity index 100%
rename from backup/imagenes/gliffy_2.png
rename to deprecated/content-duplicates/imagenes/gliffy_2.png
diff --git a/backup/imagenes/icoActividadClase.png b/deprecated/content-duplicates/imagenes/icoActividadClase.png
similarity index 100%
rename from backup/imagenes/icoActividadClase.png
rename to deprecated/content-duplicates/imagenes/icoActividadClase.png
diff --git a/backup/imagenes/icoCheck.png b/deprecated/content-duplicates/imagenes/icoCheck.png
similarity index 100%
rename from backup/imagenes/icoCheck.png
rename to deprecated/content-duplicates/imagenes/icoCheck.png
diff --git a/backup/imagenes/icoExamen.png b/deprecated/content-duplicates/imagenes/icoExamen.png
similarity index 100%
rename from backup/imagenes/icoExamen.png
rename to deprecated/content-duplicates/imagenes/icoExamen.png
diff --git a/backup/imagenes/icoFinal.png b/deprecated/content-duplicates/imagenes/icoFinal.png
similarity index 100%
rename from backup/imagenes/icoFinal.png
rename to deprecated/content-duplicates/imagenes/icoFinal.png
diff --git a/backup/imagenes/icoInvestigacion.png b/deprecated/content-duplicates/imagenes/icoInvestigacion.png
similarity index 100%
rename from backup/imagenes/icoInvestigacion.png
rename to deprecated/content-duplicates/imagenes/icoInvestigacion.png
diff --git a/backup/imagenes/icoLaboratorio.png b/deprecated/content-duplicates/imagenes/icoLaboratorio.png
similarity index 100%
rename from backup/imagenes/icoLaboratorio.png
rename to deprecated/content-duplicates/imagenes/icoLaboratorio.png
diff --git a/backup/imagenes/icoLaboratorioTeam.png b/deprecated/content-duplicates/imagenes/icoLaboratorioTeam.png
similarity index 100%
rename from backup/imagenes/icoLaboratorioTeam.png
rename to deprecated/content-duplicates/imagenes/icoLaboratorioTeam.png
diff --git a/backup/imagenes/icoPresentacion.png b/deprecated/content-duplicates/imagenes/icoPresentacion.png
similarity index 100%
rename from backup/imagenes/icoPresentacion.png
rename to deprecated/content-duplicates/imagenes/icoPresentacion.png
diff --git a/backup/imagenes/icoProject.png b/deprecated/content-duplicates/imagenes/icoProject.png
similarity index 100%
rename from backup/imagenes/icoProject.png
rename to deprecated/content-duplicates/imagenes/icoProject.png
diff --git a/backup/imagenes/icoRead.png b/deprecated/content-duplicates/imagenes/icoRead.png
similarity index 100%
rename from backup/imagenes/icoRead.png
rename to deprecated/content-duplicates/imagenes/icoRead.png
diff --git a/backup/imagenes/iconPractice.png b/deprecated/content-duplicates/imagenes/iconPractice.png
similarity index 100%
rename from backup/imagenes/iconPractice.png
rename to deprecated/content-duplicates/imagenes/iconPractice.png
diff --git a/backup/imagenes/individual.jpg b/deprecated/content-duplicates/imagenes/individual.jpg
similarity index 100%
rename from backup/imagenes/individual.jpg
rename to deprecated/content-duplicates/imagenes/individual.jpg
diff --git a/backup/imagenes/instrucciones.gif b/deprecated/content-duplicates/imagenes/instrucciones.gif
similarity index 100%
rename from backup/imagenes/instrucciones.gif
rename to deprecated/content-duplicates/imagenes/instrucciones.gif
diff --git a/backup/imagenes/instrucciones.jpg b/deprecated/content-duplicates/imagenes/instrucciones.jpg
similarity index 100%
rename from backup/imagenes/instrucciones.jpg
rename to deprecated/content-duplicates/imagenes/instrucciones.jpg
diff --git a/backup/imagenes/item-lista.gif b/deprecated/content-duplicates/imagenes/item-lista.gif
similarity index 100%
rename from backup/imagenes/item-lista.gif
rename to deprecated/content-duplicates/imagenes/item-lista.gif
diff --git a/backup/imagenes/lab10-1.jpg b/deprecated/content-duplicates/imagenes/lab10-1.jpg
similarity index 100%
rename from backup/imagenes/lab10-1.jpg
rename to deprecated/content-duplicates/imagenes/lab10-1.jpg
diff --git a/backup/imagenes/lab10-10.jpg b/deprecated/content-duplicates/imagenes/lab10-10.jpg
similarity index 100%
rename from backup/imagenes/lab10-10.jpg
rename to deprecated/content-duplicates/imagenes/lab10-10.jpg
diff --git a/backup/imagenes/lab10-11.jpg b/deprecated/content-duplicates/imagenes/lab10-11.jpg
similarity index 100%
rename from backup/imagenes/lab10-11.jpg
rename to deprecated/content-duplicates/imagenes/lab10-11.jpg
diff --git a/backup/imagenes/lab10-12.jpg b/deprecated/content-duplicates/imagenes/lab10-12.jpg
similarity index 100%
rename from backup/imagenes/lab10-12.jpg
rename to deprecated/content-duplicates/imagenes/lab10-12.jpg
diff --git a/backup/imagenes/lab10-2.jpg b/deprecated/content-duplicates/imagenes/lab10-2.jpg
similarity index 100%
rename from backup/imagenes/lab10-2.jpg
rename to deprecated/content-duplicates/imagenes/lab10-2.jpg
diff --git a/backup/imagenes/lab10-3.jpg b/deprecated/content-duplicates/imagenes/lab10-3.jpg
similarity index 100%
rename from backup/imagenes/lab10-3.jpg
rename to deprecated/content-duplicates/imagenes/lab10-3.jpg
diff --git a/backup/imagenes/lab10-4.jpg b/deprecated/content-duplicates/imagenes/lab10-4.jpg
similarity index 100%
rename from backup/imagenes/lab10-4.jpg
rename to deprecated/content-duplicates/imagenes/lab10-4.jpg
diff --git a/backup/imagenes/lab10-5.jpg b/deprecated/content-duplicates/imagenes/lab10-5.jpg
similarity index 100%
rename from backup/imagenes/lab10-5.jpg
rename to deprecated/content-duplicates/imagenes/lab10-5.jpg
diff --git a/backup/imagenes/lab10-6.jpg b/deprecated/content-duplicates/imagenes/lab10-6.jpg
similarity index 100%
rename from backup/imagenes/lab10-6.jpg
rename to deprecated/content-duplicates/imagenes/lab10-6.jpg
diff --git a/backup/imagenes/lab10-7.jpg b/deprecated/content-duplicates/imagenes/lab10-7.jpg
similarity index 100%
rename from backup/imagenes/lab10-7.jpg
rename to deprecated/content-duplicates/imagenes/lab10-7.jpg
diff --git a/backup/imagenes/lab10-8.jpg b/deprecated/content-duplicates/imagenes/lab10-8.jpg
similarity index 100%
rename from backup/imagenes/lab10-8.jpg
rename to deprecated/content-duplicates/imagenes/lab10-8.jpg
diff --git a/backup/imagenes/lab10-9.jpg b/deprecated/content-duplicates/imagenes/lab10-9.jpg
similarity index 100%
rename from backup/imagenes/lab10-9.jpg
rename to deprecated/content-duplicates/imagenes/lab10-9.jpg
diff --git a/backup/imagenes/lab10_img1.jpg b/deprecated/content-duplicates/imagenes/lab10_img1.jpg
similarity index 100%
rename from backup/imagenes/lab10_img1.jpg
rename to deprecated/content-duplicates/imagenes/lab10_img1.jpg
diff --git a/backup/imagenes/lab15-1.png b/deprecated/content-duplicates/imagenes/lab15-1.png
similarity index 100%
rename from backup/imagenes/lab15-1.png
rename to deprecated/content-duplicates/imagenes/lab15-1.png
diff --git a/backup/imagenes/lab15-2.jpg b/deprecated/content-duplicates/imagenes/lab15-2.jpg
similarity index 100%
rename from backup/imagenes/lab15-2.jpg
rename to deprecated/content-duplicates/imagenes/lab15-2.jpg
diff --git a/backup/imagenes/lab15-3.jpg b/deprecated/content-duplicates/imagenes/lab15-3.jpg
similarity index 100%
rename from backup/imagenes/lab15-3.jpg
rename to deprecated/content-duplicates/imagenes/lab15-3.jpg
diff --git a/backup/imagenes/lab16-Consulta.png b/deprecated/content-duplicates/imagenes/lab16-Consulta.png
similarity index 100%
rename from backup/imagenes/lab16-Consulta.png
rename to deprecated/content-duplicates/imagenes/lab16-Consulta.png
diff --git a/backup/imagenes/lab17-1.jpg b/deprecated/content-duplicates/imagenes/lab17-1.jpg
similarity index 100%
rename from backup/imagenes/lab17-1.jpg
rename to deprecated/content-duplicates/imagenes/lab17-1.jpg
diff --git a/backup/imagenes/lab17-2.jpg b/deprecated/content-duplicates/imagenes/lab17-2.jpg
similarity index 100%
rename from backup/imagenes/lab17-2.jpg
rename to deprecated/content-duplicates/imagenes/lab17-2.jpg
diff --git a/backup/imagenes/lab17-3.jpg b/deprecated/content-duplicates/imagenes/lab17-3.jpg
similarity index 100%
rename from backup/imagenes/lab17-3.jpg
rename to deprecated/content-duplicates/imagenes/lab17-3.jpg
diff --git a/backup/imagenes/lab17-ZedGraph.png b/deprecated/content-duplicates/imagenes/lab17-ZedGraph.png
similarity index 100%
rename from backup/imagenes/lab17-ZedGraph.png
rename to deprecated/content-duplicates/imagenes/lab17-ZedGraph.png
diff --git a/backup/imagenes/lab17-img0.png b/deprecated/content-duplicates/imagenes/lab17-img0.png
similarity index 100%
rename from backup/imagenes/lab17-img0.png
rename to deprecated/content-duplicates/imagenes/lab17-img0.png
diff --git a/backup/imagenes/lab17-img1.png b/deprecated/content-duplicates/imagenes/lab17-img1.png
similarity index 100%
rename from backup/imagenes/lab17-img1.png
rename to deprecated/content-duplicates/imagenes/lab17-img1.png
diff --git a/backup/imagenes/lab17-img10.png b/deprecated/content-duplicates/imagenes/lab17-img10.png
similarity index 100%
rename from backup/imagenes/lab17-img10.png
rename to deprecated/content-duplicates/imagenes/lab17-img10.png
diff --git a/backup/imagenes/lab17-img11.png b/deprecated/content-duplicates/imagenes/lab17-img11.png
similarity index 100%
rename from backup/imagenes/lab17-img11.png
rename to deprecated/content-duplicates/imagenes/lab17-img11.png
diff --git a/backup/imagenes/lab17-img12.png b/deprecated/content-duplicates/imagenes/lab17-img12.png
similarity index 100%
rename from backup/imagenes/lab17-img12.png
rename to deprecated/content-duplicates/imagenes/lab17-img12.png
diff --git a/backup/imagenes/lab17-img13.png b/deprecated/content-duplicates/imagenes/lab17-img13.png
similarity index 100%
rename from backup/imagenes/lab17-img13.png
rename to deprecated/content-duplicates/imagenes/lab17-img13.png
diff --git a/backup/imagenes/lab17-img14.png b/deprecated/content-duplicates/imagenes/lab17-img14.png
similarity index 100%
rename from backup/imagenes/lab17-img14.png
rename to deprecated/content-duplicates/imagenes/lab17-img14.png
diff --git a/backup/imagenes/lab17-img2.png b/deprecated/content-duplicates/imagenes/lab17-img2.png
similarity index 100%
rename from backup/imagenes/lab17-img2.png
rename to deprecated/content-duplicates/imagenes/lab17-img2.png
diff --git a/backup/imagenes/lab17-img3.png b/deprecated/content-duplicates/imagenes/lab17-img3.png
similarity index 100%
rename from backup/imagenes/lab17-img3.png
rename to deprecated/content-duplicates/imagenes/lab17-img3.png
diff --git a/backup/imagenes/lab17-img4.png b/deprecated/content-duplicates/imagenes/lab17-img4.png
similarity index 100%
rename from backup/imagenes/lab17-img4.png
rename to deprecated/content-duplicates/imagenes/lab17-img4.png
diff --git a/backup/imagenes/lab17-img5.png b/deprecated/content-duplicates/imagenes/lab17-img5.png
similarity index 100%
rename from backup/imagenes/lab17-img5.png
rename to deprecated/content-duplicates/imagenes/lab17-img5.png
diff --git a/backup/imagenes/lab17-img6.png b/deprecated/content-duplicates/imagenes/lab17-img6.png
similarity index 100%
rename from backup/imagenes/lab17-img6.png
rename to deprecated/content-duplicates/imagenes/lab17-img6.png
diff --git a/backup/imagenes/lab17-img7.png b/deprecated/content-duplicates/imagenes/lab17-img7.png
similarity index 100%
rename from backup/imagenes/lab17-img7.png
rename to deprecated/content-duplicates/imagenes/lab17-img7.png
diff --git a/backup/imagenes/lab17-img8.png b/deprecated/content-duplicates/imagenes/lab17-img8.png
similarity index 100%
rename from backup/imagenes/lab17-img8.png
rename to deprecated/content-duplicates/imagenes/lab17-img8.png
diff --git a/backup/imagenes/lab17-img9.png b/deprecated/content-duplicates/imagenes/lab17-img9.png
similarity index 100%
rename from backup/imagenes/lab17-img9.png
rename to deprecated/content-duplicates/imagenes/lab17-img9.png
diff --git a/backup/imagenes/lab17-modUtil.png b/deprecated/content-duplicates/imagenes/lab17-modUtil.png
similarity index 100%
rename from backup/imagenes/lab17-modUtil.png
rename to deprecated/content-duplicates/imagenes/lab17-modUtil.png
diff --git a/backup/imagenes/lab19-1.jpg b/deprecated/content-duplicates/imagenes/lab19-1.jpg
similarity index 100%
rename from backup/imagenes/lab19-1.jpg
rename to deprecated/content-duplicates/imagenes/lab19-1.jpg
diff --git a/backup/imagenes/lab19-img1.png b/deprecated/content-duplicates/imagenes/lab19-img1.png
similarity index 100%
rename from backup/imagenes/lab19-img1.png
rename to deprecated/content-duplicates/imagenes/lab19-img1.png
diff --git a/backup/imagenes/lab19-img2.png b/deprecated/content-duplicates/imagenes/lab19-img2.png
similarity index 100%
rename from backup/imagenes/lab19-img2.png
rename to deprecated/content-duplicates/imagenes/lab19-img2.png
diff --git a/backup/imagenes/lab19-img3.png b/deprecated/content-duplicates/imagenes/lab19-img3.png
similarity index 100%
rename from backup/imagenes/lab19-img3.png
rename to deprecated/content-duplicates/imagenes/lab19-img3.png
diff --git a/backup/imagenes/lab19-img4.png b/deprecated/content-duplicates/imagenes/lab19-img4.png
similarity index 100%
rename from backup/imagenes/lab19-img4.png
rename to deprecated/content-duplicates/imagenes/lab19-img4.png
diff --git a/backup/imagenes/lab19-img5.png b/deprecated/content-duplicates/imagenes/lab19-img5.png
similarity index 100%
rename from backup/imagenes/lab19-img5.png
rename to deprecated/content-duplicates/imagenes/lab19-img5.png
diff --git a/backup/imagenes/lab19-img6.png b/deprecated/content-duplicates/imagenes/lab19-img6.png
similarity index 100%
rename from backup/imagenes/lab19-img6.png
rename to deprecated/content-duplicates/imagenes/lab19-img6.png
diff --git a/backup/imagenes/lab20-img1.png b/deprecated/content-duplicates/imagenes/lab20-img1.png
similarity index 100%
rename from backup/imagenes/lab20-img1.png
rename to deprecated/content-duplicates/imagenes/lab20-img1.png
diff --git a/backup/imagenes/lab20-statusImg1.png b/deprecated/content-duplicates/imagenes/lab20-statusImg1.png
similarity index 100%
rename from backup/imagenes/lab20-statusImg1.png
rename to deprecated/content-duplicates/imagenes/lab20-statusImg1.png
diff --git a/backup/imagenes/lab20-statusImg2.png b/deprecated/content-duplicates/imagenes/lab20-statusImg2.png
similarity index 100%
rename from backup/imagenes/lab20-statusImg2.png
rename to deprecated/content-duplicates/imagenes/lab20-statusImg2.png
diff --git a/backup/imagenes/lab20-tabImg1.png b/deprecated/content-duplicates/imagenes/lab20-tabImg1.png
similarity index 100%
rename from backup/imagenes/lab20-tabImg1.png
rename to deprecated/content-duplicates/imagenes/lab20-tabImg1.png
diff --git a/backup/imagenes/lab20-tabImg2.png b/deprecated/content-duplicates/imagenes/lab20-tabImg2.png
similarity index 100%
rename from backup/imagenes/lab20-tabImg2.png
rename to deprecated/content-duplicates/imagenes/lab20-tabImg2.png
diff --git a/backup/imagenes/lab20-tbarImg1.png b/deprecated/content-duplicates/imagenes/lab20-tbarImg1.png
similarity index 100%
rename from backup/imagenes/lab20-tbarImg1.png
rename to deprecated/content-duplicates/imagenes/lab20-tbarImg1.png
diff --git a/backup/imagenes/lab20-tbarImg2.png b/deprecated/content-duplicates/imagenes/lab20-tbarImg2.png
similarity index 100%
rename from backup/imagenes/lab20-tbarImg2.png
rename to deprecated/content-duplicates/imagenes/lab20-tbarImg2.png
diff --git a/backup/imagenes/lab21-img1.png b/deprecated/content-duplicates/imagenes/lab21-img1.png
similarity index 100%
rename from backup/imagenes/lab21-img1.png
rename to deprecated/content-duplicates/imagenes/lab21-img1.png
diff --git a/backup/imagenes/lab21-img10.png b/deprecated/content-duplicates/imagenes/lab21-img10.png
similarity index 100%
rename from backup/imagenes/lab21-img10.png
rename to deprecated/content-duplicates/imagenes/lab21-img10.png
diff --git a/backup/imagenes/lab21-img2.png b/deprecated/content-duplicates/imagenes/lab21-img2.png
similarity index 100%
rename from backup/imagenes/lab21-img2.png
rename to deprecated/content-duplicates/imagenes/lab21-img2.png
diff --git a/backup/imagenes/lab21-img3.png b/deprecated/content-duplicates/imagenes/lab21-img3.png
similarity index 100%
rename from backup/imagenes/lab21-img3.png
rename to deprecated/content-duplicates/imagenes/lab21-img3.png
diff --git a/backup/imagenes/lab21-img4.png b/deprecated/content-duplicates/imagenes/lab21-img4.png
similarity index 100%
rename from backup/imagenes/lab21-img4.png
rename to deprecated/content-duplicates/imagenes/lab21-img4.png
diff --git a/backup/imagenes/lab21-img5.png b/deprecated/content-duplicates/imagenes/lab21-img5.png
similarity index 100%
rename from backup/imagenes/lab21-img5.png
rename to deprecated/content-duplicates/imagenes/lab21-img5.png
diff --git a/backup/imagenes/lab21-img6.png b/deprecated/content-duplicates/imagenes/lab21-img6.png
similarity index 100%
rename from backup/imagenes/lab21-img6.png
rename to deprecated/content-duplicates/imagenes/lab21-img6.png
diff --git a/backup/imagenes/lab21-img7.png b/deprecated/content-duplicates/imagenes/lab21-img7.png
similarity index 100%
rename from backup/imagenes/lab21-img7.png
rename to deprecated/content-duplicates/imagenes/lab21-img7.png
diff --git a/backup/imagenes/lab21-img8.png b/deprecated/content-duplicates/imagenes/lab21-img8.png
similarity index 100%
rename from backup/imagenes/lab21-img8.png
rename to deprecated/content-duplicates/imagenes/lab21-img8.png
diff --git a/backup/imagenes/lab21-img9.png b/deprecated/content-duplicates/imagenes/lab21-img9.png
similarity index 100%
rename from backup/imagenes/lab21-img9.png
rename to deprecated/content-duplicates/imagenes/lab21-img9.png
diff --git a/backup/imagenes/lab21-imgHM.png b/deprecated/content-duplicates/imagenes/lab21-imgHM.png
similarity index 100%
rename from backup/imagenes/lab21-imgHM.png
rename to deprecated/content-duplicates/imagenes/lab21-imgHM.png
diff --git a/backup/imagenes/lab22-img1.gif b/deprecated/content-duplicates/imagenes/lab22-img1.gif
similarity index 100%
rename from backup/imagenes/lab22-img1.gif
rename to deprecated/content-duplicates/imagenes/lab22-img1.gif
diff --git a/backup/imagenes/lab23-img1.png b/deprecated/content-duplicates/imagenes/lab23-img1.png
similarity index 100%
rename from backup/imagenes/lab23-img1.png
rename to deprecated/content-duplicates/imagenes/lab23-img1.png
diff --git a/backup/imagenes/lab3-1.jpg b/deprecated/content-duplicates/imagenes/lab3-1.jpg
similarity index 100%
rename from backup/imagenes/lab3-1.jpg
rename to deprecated/content-duplicates/imagenes/lab3-1.jpg
diff --git a/backup/imagenes/lab3-10.jpg b/deprecated/content-duplicates/imagenes/lab3-10.jpg
similarity index 100%
rename from backup/imagenes/lab3-10.jpg
rename to deprecated/content-duplicates/imagenes/lab3-10.jpg
diff --git a/backup/imagenes/lab3-2.jpg b/deprecated/content-duplicates/imagenes/lab3-2.jpg
similarity index 100%
rename from backup/imagenes/lab3-2.jpg
rename to deprecated/content-duplicates/imagenes/lab3-2.jpg
diff --git a/backup/imagenes/lab3-3.jpg b/deprecated/content-duplicates/imagenes/lab3-3.jpg
similarity index 100%
rename from backup/imagenes/lab3-3.jpg
rename to deprecated/content-duplicates/imagenes/lab3-3.jpg
diff --git a/backup/imagenes/lab3-4.jpg b/deprecated/content-duplicates/imagenes/lab3-4.jpg
similarity index 100%
rename from backup/imagenes/lab3-4.jpg
rename to deprecated/content-duplicates/imagenes/lab3-4.jpg
diff --git a/backup/imagenes/lab3-5.jpg b/deprecated/content-duplicates/imagenes/lab3-5.jpg
similarity index 100%
rename from backup/imagenes/lab3-5.jpg
rename to deprecated/content-duplicates/imagenes/lab3-5.jpg
diff --git a/backup/imagenes/lab3-6.jpg b/deprecated/content-duplicates/imagenes/lab3-6.jpg
similarity index 100%
rename from backup/imagenes/lab3-6.jpg
rename to deprecated/content-duplicates/imagenes/lab3-6.jpg
diff --git a/backup/imagenes/lab3-7.jpg b/deprecated/content-duplicates/imagenes/lab3-7.jpg
similarity index 100%
rename from backup/imagenes/lab3-7.jpg
rename to deprecated/content-duplicates/imagenes/lab3-7.jpg
diff --git a/backup/imagenes/lab3-8.jpg b/deprecated/content-duplicates/imagenes/lab3-8.jpg
similarity index 100%
rename from backup/imagenes/lab3-8.jpg
rename to deprecated/content-duplicates/imagenes/lab3-8.jpg
diff --git a/backup/imagenes/lab3-9.jpg b/deprecated/content-duplicates/imagenes/lab3-9.jpg
similarity index 100%
rename from backup/imagenes/lab3-9.jpg
rename to deprecated/content-duplicates/imagenes/lab3-9.jpg
diff --git a/backup/imagenes/lab3-GenerarEvento.png b/deprecated/content-duplicates/imagenes/lab3-GenerarEvento.png
similarity index 100%
rename from backup/imagenes/lab3-GenerarEvento.png
rename to deprecated/content-duplicates/imagenes/lab3-GenerarEvento.png
diff --git a/backup/imagenes/lab3-modo_disenio.png b/deprecated/content-duplicates/imagenes/lab3-modo_disenio.png
similarity index 100%
rename from backup/imagenes/lab3-modo_disenio.png
rename to deprecated/content-duplicates/imagenes/lab3-modo_disenio.png
diff --git a/backup/imagenes/lab3-winapp.JPG b/deprecated/content-duplicates/imagenes/lab3-winapp.JPG
similarity index 100%
rename from backup/imagenes/lab3-winapp.JPG
rename to deprecated/content-duplicates/imagenes/lab3-winapp.JPG
diff --git a/backup/imagenes/lab5-fechacorta.png b/deprecated/content-duplicates/imagenes/lab5-fechacorta.png
similarity index 100%
rename from backup/imagenes/lab5-fechacorta.png
rename to deprecated/content-duplicates/imagenes/lab5-fechacorta.png
diff --git a/backup/imagenes/lab6-1.png b/deprecated/content-duplicates/imagenes/lab6-1.png
similarity index 100%
rename from backup/imagenes/lab6-1.png
rename to deprecated/content-duplicates/imagenes/lab6-1.png
diff --git a/backup/imagenes/lab6-10.png b/deprecated/content-duplicates/imagenes/lab6-10.png
similarity index 100%
rename from backup/imagenes/lab6-10.png
rename to deprecated/content-duplicates/imagenes/lab6-10.png
diff --git a/backup/imagenes/lab6-2.png b/deprecated/content-duplicates/imagenes/lab6-2.png
similarity index 100%
rename from backup/imagenes/lab6-2.png
rename to deprecated/content-duplicates/imagenes/lab6-2.png
diff --git a/backup/imagenes/lab6-3.png b/deprecated/content-duplicates/imagenes/lab6-3.png
similarity index 100%
rename from backup/imagenes/lab6-3.png
rename to deprecated/content-duplicates/imagenes/lab6-3.png
diff --git a/backup/imagenes/lab6-4.png b/deprecated/content-duplicates/imagenes/lab6-4.png
similarity index 100%
rename from backup/imagenes/lab6-4.png
rename to deprecated/content-duplicates/imagenes/lab6-4.png
diff --git a/backup/imagenes/lab6-5.png b/deprecated/content-duplicates/imagenes/lab6-5.png
similarity index 100%
rename from backup/imagenes/lab6-5.png
rename to deprecated/content-duplicates/imagenes/lab6-5.png
diff --git a/backup/imagenes/lab6-6.png b/deprecated/content-duplicates/imagenes/lab6-6.png
similarity index 100%
rename from backup/imagenes/lab6-6.png
rename to deprecated/content-duplicates/imagenes/lab6-6.png
diff --git a/backup/imagenes/lab6-7.png b/deprecated/content-duplicates/imagenes/lab6-7.png
similarity index 100%
rename from backup/imagenes/lab6-7.png
rename to deprecated/content-duplicates/imagenes/lab6-7.png
diff --git a/backup/imagenes/lab6-8.png b/deprecated/content-duplicates/imagenes/lab6-8.png
similarity index 100%
rename from backup/imagenes/lab6-8.png
rename to deprecated/content-duplicates/imagenes/lab6-8.png
diff --git a/backup/imagenes/lab6-9.png b/deprecated/content-duplicates/imagenes/lab6-9.png
similarity index 100%
rename from backup/imagenes/lab6-9.png
rename to deprecated/content-duplicates/imagenes/lab6-9.png
diff --git a/backup/imagenes/lab6-archivos.png b/deprecated/content-duplicates/imagenes/lab6-archivos.png
similarity index 100%
rename from backup/imagenes/lab6-archivos.png
rename to deprecated/content-duplicates/imagenes/lab6-archivos.png
diff --git a/backup/imagenes/lec5_sql.png b/deprecated/content-duplicates/imagenes/lec5_sql.png
similarity index 100%
rename from backup/imagenes/lec5_sql.png
rename to deprecated/content-duplicates/imagenes/lec5_sql.png
diff --git a/backup/imagenes/materiales.png b/deprecated/content-duplicates/imagenes/materiales.png
similarity index 100%
rename from backup/imagenes/materiales.png
rename to deprecated/content-duplicates/imagenes/materiales.png
diff --git a/backup/imagenes/modalidad.gif b/deprecated/content-duplicates/imagenes/modalidad.gif
similarity index 100%
rename from backup/imagenes/modalidad.gif
rename to deprecated/content-duplicates/imagenes/modalidad.gif
diff --git a/backup/imagenes/objetivo.gif b/deprecated/content-duplicates/imagenes/objetivo.gif
similarity index 100%
rename from backup/imagenes/objetivo.gif
rename to deprecated/content-duplicates/imagenes/objetivo.gif
diff --git a/backup/imagenes/objetivos.jpg b/deprecated/content-duplicates/imagenes/objetivos.jpg
similarity index 100%
rename from backup/imagenes/objetivos.jpg
rename to deprecated/content-duplicates/imagenes/objetivos.jpg
diff --git a/backup/imagenes/parejas.jpg b/deprecated/content-duplicates/imagenes/parejas.jpg
similarity index 100%
rename from backup/imagenes/parejas.jpg
rename to deprecated/content-duplicates/imagenes/parejas.jpg
diff --git a/backup/imagenes/petz.jpg b/deprecated/content-duplicates/imagenes/petz.jpg
similarity index 100%
rename from backup/imagenes/petz.jpg
rename to deprecated/content-duplicates/imagenes/petz.jpg
diff --git a/backup/imagenes/proveedores.png b/deprecated/content-duplicates/imagenes/proveedores.png
similarity index 100%
rename from backup/imagenes/proveedores.png
rename to deprecated/content-duplicates/imagenes/proveedores.png
diff --git a/backup/imagenes/proyectos.png b/deprecated/content-duplicates/imagenes/proyectos.png
similarity index 100%
rename from backup/imagenes/proyectos.png
rename to deprecated/content-duplicates/imagenes/proyectos.png
diff --git a/backup/imagenes/recursos.gif b/deprecated/content-duplicates/imagenes/recursos.gif
similarity index 100%
rename from backup/imagenes/recursos.gif
rename to deprecated/content-duplicates/imagenes/recursos.gif
diff --git a/backup/imagenes/recursos.jpg b/deprecated/content-duplicates/imagenes/recursos.jpg
similarity index 100%
rename from backup/imagenes/recursos.jpg
rename to deprecated/content-duplicates/imagenes/recursos.jpg
diff --git a/backup/imagenes/semana1.jpg b/deprecated/content-duplicates/imagenes/semana1.jpg
similarity index 100%
rename from backup/imagenes/semana1.jpg
rename to deprecated/content-duplicates/imagenes/semana1.jpg
diff --git a/backup/imagenes/semana10.jpg b/deprecated/content-duplicates/imagenes/semana10.jpg
similarity index 100%
rename from backup/imagenes/semana10.jpg
rename to deprecated/content-duplicates/imagenes/semana10.jpg
diff --git a/backup/imagenes/semana11.jpg b/deprecated/content-duplicates/imagenes/semana11.jpg
similarity index 100%
rename from backup/imagenes/semana11.jpg
rename to deprecated/content-duplicates/imagenes/semana11.jpg
diff --git a/backup/imagenes/semana12.jpg b/deprecated/content-duplicates/imagenes/semana12.jpg
similarity index 100%
rename from backup/imagenes/semana12.jpg
rename to deprecated/content-duplicates/imagenes/semana12.jpg
diff --git a/backup/imagenes/semana13.jpg b/deprecated/content-duplicates/imagenes/semana13.jpg
similarity index 100%
rename from backup/imagenes/semana13.jpg
rename to deprecated/content-duplicates/imagenes/semana13.jpg
diff --git a/backup/imagenes/semana15.jpg b/deprecated/content-duplicates/imagenes/semana15.jpg
similarity index 100%
rename from backup/imagenes/semana15.jpg
rename to deprecated/content-duplicates/imagenes/semana15.jpg
diff --git a/backup/imagenes/semana16.jpg b/deprecated/content-duplicates/imagenes/semana16.jpg
similarity index 100%
rename from backup/imagenes/semana16.jpg
rename to deprecated/content-duplicates/imagenes/semana16.jpg
diff --git a/backup/imagenes/semana16_2.png b/deprecated/content-duplicates/imagenes/semana16_2.png
similarity index 100%
rename from backup/imagenes/semana16_2.png
rename to deprecated/content-duplicates/imagenes/semana16_2.png
diff --git a/backup/imagenes/semana16_3.png b/deprecated/content-duplicates/imagenes/semana16_3.png
similarity index 100%
rename from backup/imagenes/semana16_3.png
rename to deprecated/content-duplicates/imagenes/semana16_3.png
diff --git a/backup/imagenes/semana2.jpg b/deprecated/content-duplicates/imagenes/semana2.jpg
similarity index 100%
rename from backup/imagenes/semana2.jpg
rename to deprecated/content-duplicates/imagenes/semana2.jpg
diff --git a/backup/imagenes/semana2_2.png b/deprecated/content-duplicates/imagenes/semana2_2.png
similarity index 100%
rename from backup/imagenes/semana2_2.png
rename to deprecated/content-duplicates/imagenes/semana2_2.png
diff --git a/backup/imagenes/semana3.jpg b/deprecated/content-duplicates/imagenes/semana3.jpg
similarity index 100%
rename from backup/imagenes/semana3.jpg
rename to deprecated/content-duplicates/imagenes/semana3.jpg
diff --git a/backup/imagenes/semana3_2.png b/deprecated/content-duplicates/imagenes/semana3_2.png
similarity index 100%
rename from backup/imagenes/semana3_2.png
rename to deprecated/content-duplicates/imagenes/semana3_2.png
diff --git a/backup/imagenes/semana4.jpg b/deprecated/content-duplicates/imagenes/semana4.jpg
similarity index 100%
rename from backup/imagenes/semana4.jpg
rename to deprecated/content-duplicates/imagenes/semana4.jpg
diff --git a/backup/imagenes/semana5.jpg b/deprecated/content-duplicates/imagenes/semana5.jpg
similarity index 100%
rename from backup/imagenes/semana5.jpg
rename to deprecated/content-duplicates/imagenes/semana5.jpg
diff --git a/backup/imagenes/semana6.jpg b/deprecated/content-duplicates/imagenes/semana6.jpg
similarity index 100%
rename from backup/imagenes/semana6.jpg
rename to deprecated/content-duplicates/imagenes/semana6.jpg
diff --git a/backup/imagenes/semana7.jpg b/deprecated/content-duplicates/imagenes/semana7.jpg
similarity index 100%
rename from backup/imagenes/semana7.jpg
rename to deprecated/content-duplicates/imagenes/semana7.jpg
diff --git a/backup/imagenes/semana9.png b/deprecated/content-duplicates/imagenes/semana9.png
similarity index 100%
rename from backup/imagenes/semana9.png
rename to deprecated/content-duplicates/imagenes/semana9.png
diff --git a/backup/imagenes/semana9_2.png b/deprecated/content-duplicates/imagenes/semana9_2.png
similarity index 100%
rename from backup/imagenes/semana9_2.png
rename to deprecated/content-duplicates/imagenes/semana9_2.png
diff --git a/backup/imagenes/semanaParcial.jpg b/deprecated/content-duplicates/imagenes/semanaParcial.jpg
similarity index 100%
rename from backup/imagenes/semanaParcial.jpg
rename to deprecated/content-duplicates/imagenes/semanaParcial.jpg
diff --git a/backup/imagenes/semanaParcial_2.jpg b/deprecated/content-duplicates/imagenes/semanaParcial_2.jpg
similarity index 100%
rename from backup/imagenes/semanaParcial_2.jpg
rename to deprecated/content-duplicates/imagenes/semanaParcial_2.jpg
diff --git a/backup/imagenes/server.png b/deprecated/content-duplicates/imagenes/server.png
similarity index 100%
rename from backup/imagenes/server.png
rename to deprecated/content-duplicates/imagenes/server.png
diff --git a/backup/imagenes/vi_rombo.gif b/deprecated/content-duplicates/imagenes/vi_rombo.gif
similarity index 100%
rename from backup/imagenes/vi_rombo.gif
rename to deprecated/content-duplicates/imagenes/vi_rombo.gif
diff --git a/laboratorios/.!33730!lab13_explotacion_informacion.html b/deprecated/content-duplicates/laboratorios/.!33730!lab13_explotacion_informacion.html
similarity index 100%
rename from laboratorios/.!33730!lab13_explotacion_informacion.html
rename to deprecated/content-duplicates/laboratorios/.!33730!lab13_explotacion_informacion.html
diff --git a/laboratorios/.!33731!lab16_optimizacion.html b/deprecated/content-duplicates/laboratorios/.!33731!lab16_optimizacion.html
similarity index 100%
rename from laboratorios/.!33731!lab16_optimizacion.html
rename to deprecated/content-duplicates/laboratorios/.!33731!lab16_optimizacion.html
diff --git a/laboratorios/.!33734!labDemo2.html b/deprecated/content-duplicates/laboratorios/.!33734!labDemo2.html
similarity index 100%
rename from laboratorios/.!33734!labDemo2.html
rename to deprecated/content-duplicates/laboratorios/.!33734!labDemo2.html
diff --git a/laboratorios/.!33735!lab12_consultas_parametrizadas.html b/deprecated/content-duplicates/laboratorios/.!33735!lab12_consultas_parametrizadas.html
similarity index 100%
rename from laboratorios/.!33735!lab12_consultas_parametrizadas.html
rename to deprecated/content-duplicates/laboratorios/.!33735!lab12_consultas_parametrizadas.html
diff --git a/laboratorios/.!33736!lab22.html b/deprecated/content-duplicates/laboratorios/.!33736!lab22.html
similarity index 100%
rename from laboratorios/.!33736!lab22.html
rename to deprecated/content-duplicates/laboratorios/.!33736!lab22.html
diff --git a/laboratorios/.!33737!labDemo4.html b/deprecated/content-duplicates/laboratorios/.!33737!labDemo4.html
similarity index 100%
rename from laboratorios/.!33737!labDemo4.html
rename to deprecated/content-duplicates/laboratorios/.!33737!labDemo4.html
diff --git a/laboratorios/.!33738!lab18.html b/deprecated/content-duplicates/laboratorios/.!33738!lab18.html
similarity index 100%
rename from laboratorios/.!33738!lab18.html
rename to deprecated/content-duplicates/laboratorios/.!33738!lab18.html
diff --git a/laboratorios/.!33742!lab19.html b/deprecated/content-duplicates/laboratorios/.!33742!lab19.html
similarity index 100%
rename from laboratorios/.!33742!lab19.html
rename to deprecated/content-duplicates/laboratorios/.!33742!lab19.html
diff --git a/laboratorios/.!33743!lab20.html b/deprecated/content-duplicates/laboratorios/.!33743!lab20.html
similarity index 100%
rename from laboratorios/.!33743!lab20.html
rename to deprecated/content-duplicates/laboratorios/.!33743!lab20.html
diff --git a/laboratorios/.!33745!lab10_visor_BD.html b/deprecated/content-duplicates/laboratorios/.!33745!lab10_visor_BD.html
similarity index 100%
rename from laboratorios/.!33745!lab10_visor_BD.html
rename to deprecated/content-duplicates/laboratorios/.!33745!lab10_visor_BD.html
diff --git a/laboratorios/.!33748!labDemo1.html b/deprecated/content-duplicates/laboratorios/.!33748!labDemo1.html
similarity index 100%
rename from laboratorios/.!33748!labDemo1.html
rename to deprecated/content-duplicates/laboratorios/.!33748!labDemo1.html
diff --git a/laboratorios/deprecated/lab11_sqlserver1.html b/deprecated/content-duplicates/laboratorios/deprecated/lab11_sqlserver1.html
similarity index 100%
rename from laboratorios/deprecated/lab11_sqlserver1.html
rename to deprecated/content-duplicates/laboratorios/deprecated/lab11_sqlserver1.html
diff --git a/laboratorios/deprecated/lab16_sql_basico_inv_dbms.html b/deprecated/content-duplicates/laboratorios/deprecated/lab16_sql_basico_inv_dbms.html
similarity index 100%
rename from laboratorios/deprecated/lab16_sql_basico_inv_dbms.html
rename to deprecated/content-duplicates/laboratorios/deprecated/lab16_sql_basico_inv_dbms.html
diff --git a/laboratorios/deprecated/lab19_sql_avanzado.html b/deprecated/content-duplicates/laboratorios/deprecated/lab19_sql_avanzado.html
similarity index 100%
rename from laboratorios/deprecated/lab19_sql_avanzado.html
rename to deprecated/content-duplicates/laboratorios/deprecated/lab19_sql_avanzado.html
diff --git a/laboratorios/deprecated/lab22_store_procedures.html b/deprecated/content-duplicates/laboratorios/deprecated/lab22_store_procedures.html
similarity index 100%
rename from laboratorios/deprecated/lab22_store_procedures.html
rename to deprecated/content-duplicates/laboratorios/deprecated/lab22_store_procedures.html
diff --git a/backup/laboratorios/lab24_transacciones.html b/deprecated/content-duplicates/laboratorios/deprecated/lab24_transacciones.html
similarity index 100%
rename from backup/laboratorios/lab24_transacciones.html
rename to deprecated/content-duplicates/laboratorios/deprecated/lab24_transacciones.html
diff --git a/laboratorios/deprecated/lab8_access.html b/deprecated/content-duplicates/laboratorios/deprecated/lab8_access.html
similarity index 100%
rename from laboratorios/deprecated/lab8_access.html
rename to deprecated/content-duplicates/laboratorios/deprecated/lab8_access.html
diff --git a/backup/laboratorios/lab10_visor_BD.html b/deprecated/content-duplicates/laboratorios/lab10_visor_BD.html
similarity index 100%
rename from backup/laboratorios/lab10_visor_BD.html
rename to deprecated/content-duplicates/laboratorios/lab10_visor_BD.html
diff --git a/backup/laboratorios/lab11_biblioteca_cliente_servidor.html b/deprecated/content-duplicates/laboratorios/lab11_biblioteca_cliente_servidor.html
similarity index 100%
rename from backup/laboratorios/lab11_biblioteca_cliente_servidor.html
rename to deprecated/content-duplicates/laboratorios/lab11_biblioteca_cliente_servidor.html
diff --git a/backup/laboratorios/lab12_consultas_parametrizadas.html b/deprecated/content-duplicates/laboratorios/lab12_consultas_parametrizadas.html
similarity index 100%
rename from backup/laboratorios/lab12_consultas_parametrizadas.html
rename to deprecated/content-duplicates/laboratorios/lab12_consultas_parametrizadas.html
diff --git a/backup/laboratorios/lab13_explotacion_informacion.html b/deprecated/content-duplicates/laboratorios/lab13_explotacion_informacion.html
similarity index 100%
rename from backup/laboratorios/lab13_explotacion_informacion.html
rename to deprecated/content-duplicates/laboratorios/lab13_explotacion_informacion.html
diff --git a/laboratorios/lab13_sqlserver2.html b/deprecated/content-duplicates/laboratorios/lab13_sqlserver2.html
similarity index 100%
rename from laboratorios/lab13_sqlserver2.html
rename to deprecated/content-duplicates/laboratorios/lab13_sqlserver2.html
diff --git a/backup/laboratorios/lab16_optimizacion.html b/deprecated/content-duplicates/laboratorios/lab16_optimizacion.html
similarity index 100%
rename from backup/laboratorios/lab16_optimizacion.html
rename to deprecated/content-duplicates/laboratorios/lab16_optimizacion.html
diff --git a/backup/laboratorios/lab18.html b/deprecated/content-duplicates/laboratorios/lab18.html
similarity index 100%
rename from backup/laboratorios/lab18.html
rename to deprecated/content-duplicates/laboratorios/lab18.html
diff --git a/backup/laboratorios/lab19.html b/deprecated/content-duplicates/laboratorios/lab19.html
similarity index 100%
rename from backup/laboratorios/lab19.html
rename to deprecated/content-duplicates/laboratorios/lab19.html
diff --git a/backup/laboratorios/lab20.html b/deprecated/content-duplicates/laboratorios/lab20.html
similarity index 100%
rename from backup/laboratorios/lab20.html
rename to deprecated/content-duplicates/laboratorios/lab20.html
diff --git a/backup/laboratorios/lab22.html b/deprecated/content-duplicates/laboratorios/lab22.html
similarity index 100%
rename from backup/laboratorios/lab22.html
rename to deprecated/content-duplicates/laboratorios/lab22.html
diff --git a/laboratorios/lab8_sql_basico_deprecated.html b/deprecated/content-duplicates/laboratorios/lab8_sql_basico_deprecated.html
similarity index 100%
rename from laboratorios/lab8_sql_basico_deprecated.html
rename to deprecated/content-duplicates/laboratorios/lab8_sql_basico_deprecated.html
diff --git a/backup/laboratorios/labDemo1.html b/deprecated/content-duplicates/laboratorios/labDemo1.html
similarity index 100%
rename from backup/laboratorios/labDemo1.html
rename to deprecated/content-duplicates/laboratorios/labDemo1.html
diff --git a/backup/laboratorios/labDemo2.html b/deprecated/content-duplicates/laboratorios/labDemo2.html
similarity index 100%
rename from backup/laboratorios/labDemo2.html
rename to deprecated/content-duplicates/laboratorios/labDemo2.html
diff --git a/backup/laboratorios/labDemo4.html b/deprecated/content-duplicates/laboratorios/labDemo4.html
similarity index 100%
rename from backup/laboratorios/labDemo4.html
rename to deprecated/content-duplicates/laboratorios/labDemo4.html
diff --git a/lecturas/lectura1_DBvsDBMS.html b/deprecated/content-duplicates/lecturas/lectura1_DBvsDBMS.html
similarity index 100%
rename from lecturas/lectura1_DBvsDBMS.html
rename to deprecated/content-duplicates/lecturas/lectura1_DBvsDBMS.html
diff --git a/backup/lectura2_Mer/Dibujo1.gif b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo1.gif
similarity index 100%
rename from backup/lectura2_Mer/Dibujo1.gif
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo1.gif
diff --git a/backup/lectura2_Mer/Dibujo1.jpg b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo1.jpg
similarity index 100%
rename from backup/lectura2_Mer/Dibujo1.jpg
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo1.jpg
diff --git a/backup/lectura2_Mer/Dibujo10.png b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo10.png
similarity index 100%
rename from backup/lectura2_Mer/Dibujo10.png
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo10.png
diff --git a/backup/lectura2_Mer/Dibujo11.png b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo11.png
similarity index 100%
rename from backup/lectura2_Mer/Dibujo11.png
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo11.png
diff --git a/backup/lectura2_Mer/Dibujo12.png b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo12.png
similarity index 100%
rename from backup/lectura2_Mer/Dibujo12.png
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo12.png
diff --git a/backup/lectura2_Mer/Dibujo13.png b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo13.png
similarity index 100%
rename from backup/lectura2_Mer/Dibujo13.png
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo13.png
diff --git a/backup/lectura2_Mer/Dibujo14.png b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo14.png
similarity index 100%
rename from backup/lectura2_Mer/Dibujo14.png
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo14.png
diff --git a/backup/lectura2_Mer/Dibujo15.png b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo15.png
similarity index 100%
rename from backup/lectura2_Mer/Dibujo15.png
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo15.png
diff --git a/backup/lectura2_Mer/Dibujo17.jpg b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo17.jpg
similarity index 100%
rename from backup/lectura2_Mer/Dibujo17.jpg
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo17.jpg
diff --git a/backup/lectura2_Mer/Dibujo2.gif b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo2.gif
similarity index 100%
rename from backup/lectura2_Mer/Dibujo2.gif
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo2.gif
diff --git a/backup/lectura2_Mer/Dibujo2.jpg b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo2.jpg
similarity index 100%
rename from backup/lectura2_Mer/Dibujo2.jpg
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo2.jpg
diff --git a/backup/lectura2_Mer/Dibujo3.gif b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo3.gif
similarity index 100%
rename from backup/lectura2_Mer/Dibujo3.gif
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo3.gif
diff --git a/backup/lectura2_Mer/Dibujo3.jpg b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo3.jpg
similarity index 100%
rename from backup/lectura2_Mer/Dibujo3.jpg
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo3.jpg
diff --git a/backup/lectura2_Mer/Dibujo4.gif b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo4.gif
similarity index 100%
rename from backup/lectura2_Mer/Dibujo4.gif
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo4.gif
diff --git a/backup/lectura2_Mer/Dibujo5.gif b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo5.gif
similarity index 100%
rename from backup/lectura2_Mer/Dibujo5.gif
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo5.gif
diff --git a/backup/lectura2_Mer/Dibujo6.gif b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo6.gif
similarity index 100%
rename from backup/lectura2_Mer/Dibujo6.gif
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo6.gif
diff --git a/backup/lectura2_Mer/Dibujo7.gif b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo7.gif
similarity index 100%
rename from backup/lectura2_Mer/Dibujo7.gif
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo7.gif
diff --git a/backup/lectura2_Mer/Dibujo8.gif b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo8.gif
similarity index 100%
rename from backup/lectura2_Mer/Dibujo8.gif
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo8.gif
diff --git a/backup/lectura2_Mer/Dibujo9.png b/deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo9.png
similarity index 100%
rename from backup/lectura2_Mer/Dibujo9.png
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/Dibujo9.png
diff --git "a/backup/lectura2_Mer/FasesDise\303\261o.png" "b/deprecated/content-duplicates/lecturas/lectura2_Mer/FasesDise\303\261o.png"
similarity index 100%
rename from "backup/lectura2_Mer/FasesDise\303\261o.png"
rename to "deprecated/content-duplicates/lecturas/lectura2_Mer/FasesDise\303\261o.png"
diff --git a/backup/lectura2_Mer/d.png b/deprecated/content-duplicates/lecturas/lectura2_Mer/d.png
similarity index 100%
rename from backup/lectura2_Mer/d.png
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/d.png
diff --git a/backup/lectura2_Mer/lectura2.html b/deprecated/content-duplicates/lecturas/lectura2_Mer/lectura2.html
similarity index 100%
rename from backup/lectura2_Mer/lectura2.html
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/lectura2.html
diff --git a/backup/lectura2_Mer/lectura2_deprecated.html b/deprecated/content-duplicates/lecturas/lectura2_Mer/lectura2_deprecated.html
similarity index 100%
rename from backup/lectura2_Mer/lectura2_deprecated.html
rename to deprecated/content-duplicates/lecturas/lectura2_Mer/lectura2_deprecated.html
diff --git a/backup/lectura3_Mr/Dibujo1.jpg b/deprecated/content-duplicates/lecturas/lectura3_Mr/Dibujo1.jpg
similarity index 100%
rename from backup/lectura3_Mr/Dibujo1.jpg
rename to deprecated/content-duplicates/lecturas/lectura3_Mr/Dibujo1.jpg
diff --git a/backup/lectura3_Mr/Dibujo2.jpg b/deprecated/content-duplicates/lecturas/lectura3_Mr/Dibujo2.jpg
similarity index 100%
rename from backup/lectura3_Mr/Dibujo2.jpg
rename to deprecated/content-duplicates/lecturas/lectura3_Mr/Dibujo2.jpg
diff --git a/backup/lectura3_Mr/Dibujo3.jpg b/deprecated/content-duplicates/lecturas/lectura3_Mr/Dibujo3.jpg
similarity index 100%
rename from backup/lectura3_Mr/Dibujo3.jpg
rename to deprecated/content-duplicates/lecturas/lectura3_Mr/Dibujo3.jpg
diff --git a/backup/lectura3_Mr/Dibujo4.jpg b/deprecated/content-duplicates/lecturas/lectura3_Mr/Dibujo4.jpg
similarity index 100%
rename from backup/lectura3_Mr/Dibujo4.jpg
rename to deprecated/content-duplicates/lecturas/lectura3_Mr/Dibujo4.jpg
diff --git a/lecturas/lectura3_Mr/lectura3.html b/deprecated/content-duplicates/lecturas/lectura3_Mr/lectura3.html
similarity index 100%
rename from lecturas/lectura3_Mr/lectura3.html
rename to deprecated/content-duplicates/lecturas/lectura3_Mr/lectura3.html
diff --git a/backup/lectura4_Mr_Ar/Dibujo1.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo1.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo1.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo1.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo10.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo10.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo10.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo10.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo11.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo11.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo11.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo11.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo12.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo12.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo12.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo12.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo2.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo2.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo2.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo2.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo3.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo3.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo3.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo3.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo4.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo4.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo4.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo4.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo5.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo5.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo5.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo5.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo6.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo6.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo6.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo6.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo7.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo7.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo7.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo7.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo8.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo8.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo8.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo8.jpg
diff --git a/backup/lectura4_Mr_Ar/Dibujo9.jpg b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo9.jpg
similarity index 100%
rename from backup/lectura4_Mr_Ar/Dibujo9.jpg
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/Dibujo9.jpg
diff --git a/backup/lectura4_Mr_Ar/lectura_Mr_Ar.html b/deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/lectura_Mr_Ar.html
similarity index 100%
rename from backup/lectura4_Mr_Ar/lectura_Mr_Ar.html
rename to deprecated/content-duplicates/lecturas/lectura4_Mr_Ar/lectura_Mr_Ar.html
diff --git a/backup/lectura5_sql/Dibujo1.jpg b/deprecated/content-duplicates/lecturas/lectura5_sql/Dibujo1.jpg
similarity index 100%
rename from backup/lectura5_sql/Dibujo1.jpg
rename to deprecated/content-duplicates/lecturas/lectura5_sql/Dibujo1.jpg
diff --git a/backup/lectura5_sql/Dibujo2.jpg b/deprecated/content-duplicates/lecturas/lectura5_sql/Dibujo2.jpg
similarity index 100%
rename from backup/lectura5_sql/Dibujo2.jpg
rename to deprecated/content-duplicates/lecturas/lectura5_sql/Dibujo2.jpg
diff --git a/backup/lectura5_sql/Dibujo4.jpg b/deprecated/content-duplicates/lecturas/lectura5_sql/Dibujo4.jpg
similarity index 100%
rename from backup/lectura5_sql/Dibujo4.jpg
rename to deprecated/content-duplicates/lecturas/lectura5_sql/Dibujo4.jpg
diff --git a/lecturas/lectura5_sql/index.html b/deprecated/content-duplicates/lecturas/lectura5_sql/index.html
similarity index 100%
rename from lecturas/lectura5_sql/index.html
rename to deprecated/content-duplicates/lecturas/lectura5_sql/index.html
diff --git a/lecturas/lectura5_sql/lect5_sql1.html b/deprecated/content-duplicates/lecturas/lectura5_sql/lect5_sql1.html
similarity index 100%
rename from lecturas/lectura5_sql/lect5_sql1.html
rename to deprecated/content-duplicates/lecturas/lectura5_sql/lect5_sql1.html
diff --git a/backup/lectura5_sql/lect5_sql2.html b/deprecated/content-duplicates/lecturas/lectura5_sql/lect5_sql2.html
similarity index 100%
rename from backup/lectura5_sql/lect5_sql2.html
rename to deprecated/content-duplicates/lecturas/lectura5_sql/lect5_sql2.html
diff --git a/backup/lectura6_sql_roles/Dibujo1.jpg b/deprecated/content-duplicates/lecturas/lectura6_sql_roles/Dibujo1.jpg
similarity index 100%
rename from backup/lectura6_sql_roles/Dibujo1.jpg
rename to deprecated/content-duplicates/lecturas/lectura6_sql_roles/Dibujo1.jpg
diff --git a/backup/lectura6_sql_roles/Dibujo2.jpg b/deprecated/content-duplicates/lecturas/lectura6_sql_roles/Dibujo2.jpg
similarity index 100%
rename from backup/lectura6_sql_roles/Dibujo2.jpg
rename to deprecated/content-duplicates/lecturas/lectura6_sql_roles/Dibujo2.jpg
diff --git a/lecturas/lectura6_sql_roles/lectura_sql_roles.html b/deprecated/content-duplicates/lecturas/lectura6_sql_roles/lectura_sql_roles.html
similarity index 100%
rename from lecturas/lectura6_sql_roles/lectura_sql_roles.html
rename to deprecated/content-duplicates/lecturas/lectura6_sql_roles/lectura_sql_roles.html
diff --git a/backup/lectura7_dis_aplicaciones/Dibujo1.jpg b/deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Dibujo1.jpg
similarity index 100%
rename from backup/lectura7_dis_aplicaciones/Dibujo1.jpg
rename to deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Dibujo1.jpg
diff --git a/backup/lectura7_dis_aplicaciones/Dibujo2.jpg b/deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Dibujo2.jpg
similarity index 100%
rename from backup/lectura7_dis_aplicaciones/Dibujo2.jpg
rename to deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Dibujo2.jpg
diff --git a/backup/lectura7_dis_aplicaciones/Dibujo3.jpg b/deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Dibujo3.jpg
similarity index 100%
rename from backup/lectura7_dis_aplicaciones/Dibujo3.jpg
rename to deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Dibujo3.jpg
diff --git a/backup/lectura7_dis_aplicaciones/Dibujo4.jpg b/deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Dibujo4.jpg
similarity index 100%
rename from backup/lectura7_dis_aplicaciones/Dibujo4.jpg
rename to deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Dibujo4.jpg
diff --git a/backup/lectura7_dis_aplicaciones/Image6.gif b/deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Image6.gif
similarity index 100%
rename from backup/lectura7_dis_aplicaciones/Image6.gif
rename to deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/Image6.gif
diff --git a/lecturas/lectura7_dis_aplicaciones/lectura.html b/deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/lectura.html
similarity index 100%
rename from lecturas/lectura7_dis_aplicaciones/lectura.html
rename to deprecated/content-duplicates/lecturas/lectura7_dis_aplicaciones/lectura.html
diff --git a/lecturas/lectura8_usabilidad.html b/deprecated/content-duplicates/lecturas/lectura8_usabilidad.html
similarity index 100%
rename from lecturas/lectura8_usabilidad.html
rename to deprecated/content-duplicates/lecturas/lectura8_usabilidad.html
diff --git a/backup/lectura9_normalizacion/fig4_4.gif b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/fig4_4.gif
similarity index 100%
rename from backup/lectura9_normalizacion/fig4_4.gif
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/fig4_4.gif
diff --git a/backup/lectura9_normalizacion/imagen1.gif b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen1.gif
similarity index 100%
rename from backup/lectura9_normalizacion/imagen1.gif
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen1.gif
diff --git a/backup/lectura9_normalizacion/imagen2.gif b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen2.gif
similarity index 100%
rename from backup/lectura9_normalizacion/imagen2.gif
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen2.gif
diff --git a/backup/lectura9_normalizacion/imagen3.gif b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen3.gif
similarity index 100%
rename from backup/lectura9_normalizacion/imagen3.gif
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen3.gif
diff --git a/backup/lectura9_normalizacion/imagen4.gif b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen4.gif
similarity index 100%
rename from backup/lectura9_normalizacion/imagen4.gif
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen4.gif
diff --git a/backup/lectura9_normalizacion/imagen5.gif b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen5.gif
similarity index 100%
rename from backup/lectura9_normalizacion/imagen5.gif
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen5.gif
diff --git a/backup/lectura9_normalizacion/imagen6.gif b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen6.gif
similarity index 100%
rename from backup/lectura9_normalizacion/imagen6.gif
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen6.gif
diff --git a/backup/lectura9_normalizacion/imagen8.gif b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen8.gif
similarity index 100%
rename from backup/lectura9_normalizacion/imagen8.gif
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen8.gif
diff --git a/backup/lectura9_normalizacion/imagen9.gif b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen9.gif
similarity index 100%
rename from backup/lectura9_normalizacion/imagen9.gif
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/imagen9.gif
diff --git a/lecturas/lectura9_normalizacion/normalizacion.html b/deprecated/content-duplicates/lecturas/lectura9_normalizacion/normalizacion.html
similarity index 100%
rename from lecturas/lectura9_normalizacion/normalizacion.html
rename to deprecated/content-duplicates/lecturas/lectura9_normalizacion/normalizacion.html
diff --git a/backup/ej4_mr_deprecated/Dibujo1.jpg b/deprecated/exercises-deprecated/ej4_mr/Dibujo1.jpg
similarity index 100%
rename from backup/ej4_mr_deprecated/Dibujo1.jpg
rename to deprecated/exercises-deprecated/ej4_mr/Dibujo1.jpg
diff --git a/backup/ej4_mr_deprecated/ej4.html b/deprecated/exercises-deprecated/ej4_mr/ej4.html
similarity index 100%
rename from backup/ej4_mr_deprecated/ej4.html
rename to deprecated/exercises-deprecated/ej4_mr/ej4.html
diff --git a/backup/ej5_ar_deprecated/Dibujo1.jpg b/deprecated/exercises-deprecated/ej5_ar/Dibujo1.jpg
similarity index 100%
rename from backup/ej5_ar_deprecated/Dibujo1.jpg
rename to deprecated/exercises-deprecated/ej5_ar/Dibujo1.jpg
diff --git a/backup/ej5_ar_deprecated/ej5.html b/deprecated/exercises-deprecated/ej5_ar/ej5.html
similarity index 100%
rename from backup/ej5_ar_deprecated/ej5.html
rename to deprecated/exercises-deprecated/ej5_ar/ej5.html
diff --git a/code_reviews.html b/deprecated/html-pages/code_reviews.html
similarity index 100%
rename from code_reviews.html
rename to deprecated/html-pages/code_reviews.html
diff --git a/grupo1.html b/deprecated/html-pages/grupo1.html
similarity index 100%
rename from grupo1.html
rename to deprecated/html-pages/grupo1.html
diff --git a/deprecated/grupo1_fj25.html b/deprecated/html-pages/grupo1_fj25.html
similarity index 100%
rename from deprecated/grupo1_fj25.html
rename to deprecated/html-pages/grupo1_fj25.html
diff --git a/grupo2-nuevo.html b/deprecated/html-pages/grupo2-nuevo.html
similarity index 100%
rename from grupo2-nuevo.html
rename to deprecated/html-pages/grupo2-nuevo.html
diff --git a/grupo2.html b/deprecated/html-pages/grupo2.html
similarity index 100%
rename from grupo2.html
rename to deprecated/html-pages/grupo2.html
diff --git a/deprecated/grupo2_fj25.html b/deprecated/html-pages/grupo2_fj25.html
similarity index 100%
rename from deprecated/grupo2_fj25.html
rename to deprecated/html-pages/grupo2_fj25.html
diff --git a/index.html b/deprecated/html-pages/index.html
similarity index 100%
rename from index.html
rename to deprecated/html-pages/index.html
diff --git a/css/calendario.css b/deprecated/legacy-assets/css/calendario.css
similarity index 100%
rename from css/calendario.css
rename to deprecated/legacy-assets/css/calendario.css
diff --git a/backup/css/daw.css b/deprecated/legacy-assets/css/daw.css
similarity index 100%
rename from backup/css/daw.css
rename to deprecated/legacy-assets/css/daw.css
diff --git a/backup/css/materialize.css b/deprecated/legacy-assets/css/materialize.css
similarity index 100%
rename from backup/css/materialize.css
rename to deprecated/legacy-assets/css/materialize.css
diff --git a/backup/css/materialize.min.css b/deprecated/legacy-assets/css/materialize.min.css
similarity index 100%
rename from backup/css/materialize.min.css
rename to deprecated/legacy-assets/css/materialize.min.css
diff --git a/backup/font/material-design-icons/LICENSE.txt b/deprecated/legacy-assets/font/material-design-icons/LICENSE.txt
similarity index 100%
rename from backup/font/material-design-icons/LICENSE.txt
rename to deprecated/legacy-assets/font/material-design-icons/LICENSE.txt
diff --git a/backup/font/material-design-icons/Material-Design-Icons.eot b/deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.eot
similarity index 100%
rename from backup/font/material-design-icons/Material-Design-Icons.eot
rename to deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.eot
diff --git a/backup/font/material-design-icons/Material-Design-Icons.svg b/deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.svg
similarity index 100%
rename from backup/font/material-design-icons/Material-Design-Icons.svg
rename to deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.svg
diff --git a/backup/font/material-design-icons/Material-Design-Icons.ttf b/deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.ttf
similarity index 100%
rename from backup/font/material-design-icons/Material-Design-Icons.ttf
rename to deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.ttf
diff --git a/backup/font/material-design-icons/Material-Design-Icons.woff b/deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.woff
similarity index 100%
rename from backup/font/material-design-icons/Material-Design-Icons.woff
rename to deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.woff
diff --git a/backup/font/material-design-icons/Material-Design-Icons.woff2 b/deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.woff2
similarity index 100%
rename from backup/font/material-design-icons/Material-Design-Icons.woff2
rename to deprecated/legacy-assets/font/material-design-icons/Material-Design-Icons.woff2
diff --git a/backup/font/roboto/Roboto-Bold.ttf b/deprecated/legacy-assets/font/roboto/Roboto-Bold.ttf
similarity index 100%
rename from backup/font/roboto/Roboto-Bold.ttf
rename to deprecated/legacy-assets/font/roboto/Roboto-Bold.ttf
diff --git a/backup/font/roboto/Roboto-Bold.woff b/deprecated/legacy-assets/font/roboto/Roboto-Bold.woff
similarity index 100%
rename from backup/font/roboto/Roboto-Bold.woff
rename to deprecated/legacy-assets/font/roboto/Roboto-Bold.woff
diff --git a/backup/font/roboto/Roboto-Bold.woff2 b/deprecated/legacy-assets/font/roboto/Roboto-Bold.woff2
similarity index 100%
rename from backup/font/roboto/Roboto-Bold.woff2
rename to deprecated/legacy-assets/font/roboto/Roboto-Bold.woff2
diff --git a/backup/font/roboto/Roboto-Light.ttf b/deprecated/legacy-assets/font/roboto/Roboto-Light.ttf
similarity index 100%
rename from backup/font/roboto/Roboto-Light.ttf
rename to deprecated/legacy-assets/font/roboto/Roboto-Light.ttf
diff --git a/backup/font/roboto/Roboto-Light.woff b/deprecated/legacy-assets/font/roboto/Roboto-Light.woff
similarity index 100%
rename from backup/font/roboto/Roboto-Light.woff
rename to deprecated/legacy-assets/font/roboto/Roboto-Light.woff
diff --git a/backup/font/roboto/Roboto-Light.woff2 b/deprecated/legacy-assets/font/roboto/Roboto-Light.woff2
similarity index 100%
rename from backup/font/roboto/Roboto-Light.woff2
rename to deprecated/legacy-assets/font/roboto/Roboto-Light.woff2
diff --git a/backup/font/roboto/Roboto-Medium.ttf b/deprecated/legacy-assets/font/roboto/Roboto-Medium.ttf
similarity index 100%
rename from backup/font/roboto/Roboto-Medium.ttf
rename to deprecated/legacy-assets/font/roboto/Roboto-Medium.ttf
diff --git a/backup/font/roboto/Roboto-Medium.woff b/deprecated/legacy-assets/font/roboto/Roboto-Medium.woff
similarity index 100%
rename from backup/font/roboto/Roboto-Medium.woff
rename to deprecated/legacy-assets/font/roboto/Roboto-Medium.woff
diff --git a/backup/font/roboto/Roboto-Medium.woff2 b/deprecated/legacy-assets/font/roboto/Roboto-Medium.woff2
similarity index 100%
rename from backup/font/roboto/Roboto-Medium.woff2
rename to deprecated/legacy-assets/font/roboto/Roboto-Medium.woff2
diff --git a/backup/font/roboto/Roboto-Regular.ttf b/deprecated/legacy-assets/font/roboto/Roboto-Regular.ttf
similarity index 100%
rename from backup/font/roboto/Roboto-Regular.ttf
rename to deprecated/legacy-assets/font/roboto/Roboto-Regular.ttf
diff --git a/backup/font/roboto/Roboto-Regular.woff b/deprecated/legacy-assets/font/roboto/Roboto-Regular.woff
similarity index 100%
rename from backup/font/roboto/Roboto-Regular.woff
rename to deprecated/legacy-assets/font/roboto/Roboto-Regular.woff
diff --git a/backup/font/roboto/Roboto-Regular.woff2 b/deprecated/legacy-assets/font/roboto/Roboto-Regular.woff2
similarity index 100%
rename from backup/font/roboto/Roboto-Regular.woff2
rename to deprecated/legacy-assets/font/roboto/Roboto-Regular.woff2
diff --git a/backup/font/roboto/Roboto-Thin.ttf b/deprecated/legacy-assets/font/roboto/Roboto-Thin.ttf
similarity index 100%
rename from backup/font/roboto/Roboto-Thin.ttf
rename to deprecated/legacy-assets/font/roboto/Roboto-Thin.ttf
diff --git a/backup/font/roboto/Roboto-Thin.woff b/deprecated/legacy-assets/font/roboto/Roboto-Thin.woff
similarity index 100%
rename from backup/font/roboto/Roboto-Thin.woff
rename to deprecated/legacy-assets/font/roboto/Roboto-Thin.woff
diff --git a/backup/font/roboto/Roboto-Thin.woff2 b/deprecated/legacy-assets/font/roboto/Roboto-Thin.woff2
similarity index 100%
rename from backup/font/roboto/Roboto-Thin.woff2
rename to deprecated/legacy-assets/font/roboto/Roboto-Thin.woff2
diff --git a/backup/js/materialize.js b/deprecated/legacy-assets/js/materialize.js
similarity index 100%
rename from backup/js/materialize.js
rename to deprecated/legacy-assets/js/materialize.js
diff --git a/backup/js/materialize.min.js b/deprecated/legacy-assets/js/materialize.min.js
similarity index 100%
rename from backup/js/materialize.min.js
rename to deprecated/legacy-assets/js/materialize.min.js
diff --git a/backup/js/script.js b/deprecated/legacy-assets/js/script.js
similarity index 100%
rename from backup/js/script.js
rename to deprecated/legacy-assets/js/script.js
diff --git a/avances/avance.html b/deprecated/legacy-viewers/avance.html
similarity index 100%
rename from avances/avance.html
rename to deprecated/legacy-viewers/avance.html
diff --git a/avances/css/avance.css b/deprecated/legacy-viewers/avances-css/avance.css
similarity index 100%
rename from avances/css/avance.css
rename to deprecated/legacy-viewers/avances-css/avance.css
diff --git a/avances/deprecated/av1.html b/deprecated/legacy-viewers/avances-deprecated/av1.html
similarity index 100%
rename from avances/deprecated/av1.html
rename to deprecated/legacy-viewers/avances-deprecated/av1.html
diff --git a/avances/deprecated/av3.html b/deprecated/legacy-viewers/avances-deprecated/av3.html
similarity index 100%
rename from avances/deprecated/av3.html
rename to deprecated/legacy-viewers/avances-deprecated/av3.html
diff --git a/avances/deprecated/av4.html b/deprecated/legacy-viewers/avances-deprecated/av4.html
similarity index 100%
rename from avances/deprecated/av4.html
rename to deprecated/legacy-viewers/avances-deprecated/av4.html
diff --git a/avances/deprecated/av5.html b/deprecated/legacy-viewers/avances-deprecated/av5.html
similarity index 100%
rename from avances/deprecated/av5.html
rename to deprecated/legacy-viewers/avances-deprecated/av5.html
diff --git a/avances/deprecated/av6.html b/deprecated/legacy-viewers/avances-deprecated/av6.html
similarity index 100%
rename from avances/deprecated/av6.html
rename to deprecated/legacy-viewers/avances-deprecated/av6.html
diff --git a/avances/deprecated/av7.html b/deprecated/legacy-viewers/avances-deprecated/av7.html
similarity index 100%
rename from avances/deprecated/av7.html
rename to deprecated/legacy-viewers/avances-deprecated/av7.html
diff --git a/labs/lab.html b/deprecated/legacy-viewers/lab.html
similarity index 92%
rename from labs/lab.html
rename to deprecated/legacy-viewers/lab.html
index 72a873c..3a2214e 100644
--- a/labs/lab.html
+++ b/deprecated/legacy-viewers/lab.html
@@ -140,6 +140,20 @@
html += '
';
}
+ // Practica section (link to Docusaurus)
+ if (LAB.practica) {
+ html += '
Comuníquese con el dueño del sitio que le proporcionó la URL original y hágale saber que su vínculo está roto.
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/css/styles.89787fc4.css b/deprecated/old-docs-build/docs/assets/css/styles.89787fc4.css
new file mode 100644
index 0000000..1ef44cb
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/css/styles.89787fc4.css
@@ -0,0 +1 @@
+.col,.container{padding:0 var(--ifm-spacing-horizontal);width:100%}.markdown>h2,.markdown>h3,.markdown>h4,.markdown>h5,.markdown>h6{margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown li,body{word-wrap:break-word}body,ol ol,ol ul,ul ol,ul ul{margin:0}pre,table{overflow:auto}blockquote,pre{margin:0 0 var(--ifm-spacing-vertical)}.breadcrumbs__link,.button{transition-timing-function:var(--ifm-transition-timing-default)}.button,code{vertical-align:middle}.button--outline.button--active,.button--outline:active,.button--outline:hover,:root{--ifm-button-color:var(--ifm-font-color-base-inverse)}.menu__link:hover,a{transition:color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.navbar--dark,:root{--ifm-navbar-link-hover-color:var(--ifm-color-primary)}.menu,.navbar-sidebar{overflow-x:hidden}:root,html[data-theme=dark]{--ifm-color-emphasis-500:var(--ifm-color-gray-500)}.toggleButton_gllP,html{-webkit-tap-highlight-color:transparent}.clean-list,.containsTaskList_mC6p,.details_lb9f>summary,.dropdown__menu,.menu__list{list-style:none}:root{--ifm-color-scheme:light;--ifm-dark-value:10%;--ifm-darker-value:15%;--ifm-darkest-value:30%;--ifm-light-value:15%;--ifm-lighter-value:30%;--ifm-lightest-value:50%;--ifm-contrast-background-value:90%;--ifm-contrast-foreground-value:70%;--ifm-contrast-background-dark-value:70%;--ifm-contrast-foreground-dark-value:90%;--ifm-color-primary:#3578e5;--ifm-color-secondary:#ebedf0;--ifm-color-success:#00a400;--ifm-color-info:#54c7ec;--ifm-color-warning:#ffba00;--ifm-color-danger:#fa383e;--ifm-color-primary-dark:#306cce;--ifm-color-primary-darker:#2d66c3;--ifm-color-primary-darkest:#2554a0;--ifm-color-primary-light:#538ce9;--ifm-color-primary-lighter:#72a1ed;--ifm-color-primary-lightest:#9abcf2;--ifm-color-primary-contrast-background:#ebf2fc;--ifm-color-primary-contrast-foreground:#102445;--ifm-color-secondary-dark:#d4d5d8;--ifm-color-secondary-darker:#c8c9cc;--ifm-color-secondary-darkest:#a4a6a8;--ifm-color-secondary-light:#eef0f2;--ifm-color-secondary-lighter:#f1f2f5;--ifm-color-secondary-lightest:#f5f6f8;--ifm-color-secondary-contrast-background:#fdfdfe;--ifm-color-secondary-contrast-foreground:#474748;--ifm-color-success-dark:#009400;--ifm-color-success-darker:#008b00;--ifm-color-success-darkest:#007300;--ifm-color-success-light:#26b226;--ifm-color-success-lighter:#4dbf4d;--ifm-color-success-lightest:#80d280;--ifm-color-success-contrast-background:#e6f6e6;--ifm-color-success-contrast-foreground:#003100;--ifm-color-info-dark:#4cb3d4;--ifm-color-info-darker:#47a9c9;--ifm-color-info-darkest:#3b8ba5;--ifm-color-info-light:#6ecfef;--ifm-color-info-lighter:#87d8f2;--ifm-color-info-lightest:#aae3f6;--ifm-color-info-contrast-background:#eef9fd;--ifm-color-info-contrast-foreground:#193c47;--ifm-color-warning-dark:#e6a700;--ifm-color-warning-darker:#d99e00;--ifm-color-warning-darkest:#b38200;--ifm-color-warning-light:#ffc426;--ifm-color-warning-lighter:#ffcf4d;--ifm-color-warning-lightest:#ffdd80;--ifm-color-warning-contrast-background:#fff8e6;--ifm-color-warning-contrast-foreground:#4d3800;--ifm-color-danger-dark:#e13238;--ifm-color-danger-darker:#d53035;--ifm-color-danger-darkest:#af272b;--ifm-color-danger-light:#fb565b;--ifm-color-danger-lighter:#fb7478;--ifm-color-danger-lightest:#fd9c9f;--ifm-color-danger-contrast-background:#ffebec;--ifm-color-danger-contrast-foreground:#4b1113;--ifm-color-white:#fff;--ifm-color-black:#000;--ifm-color-gray-0:var(--ifm-color-white);--ifm-color-gray-100:#f5f6f7;--ifm-color-gray-200:#ebedf0;--ifm-color-gray-300:#dadde1;--ifm-color-gray-400:#ccd0d5;--ifm-color-gray-500:#bec3c9;--ifm-color-gray-600:#8d949e;--ifm-color-gray-700:#606770;--ifm-color-gray-800:#444950;--ifm-color-gray-900:#1c1e21;--ifm-color-gray-1000:var(--ifm-color-black);--ifm-color-emphasis-0:var(--ifm-color-gray-0);--ifm-color-emphasis-100:var(--ifm-color-gray-100);--ifm-color-emphasis-200:var(--ifm-color-gray-200);--ifm-color-emphasis-300:var(--ifm-color-gray-300);--ifm-color-emphasis-400:var(--ifm-color-gray-400);--ifm-color-emphasis-600:var(--ifm-color-gray-600);--ifm-color-emphasis-700:var(--ifm-color-gray-700);--ifm-color-emphasis-800:var(--ifm-color-gray-800);--ifm-color-emphasis-900:var(--ifm-color-gray-900);--ifm-color-emphasis-1000:var(--ifm-color-gray-1000);--ifm-color-content:var(--ifm-color-emphasis-900);--ifm-color-content-inverse:var(--ifm-color-emphasis-0);--ifm-color-content-secondary:#525860;--ifm-background-color:#0000;--ifm-background-surface-color:var(--ifm-color-content-inverse);--ifm-global-border-width:1px;--ifm-global-radius:0.4rem;--ifm-hover-overlay:#0000000d;--ifm-font-color-base:var(--ifm-color-content);--ifm-font-color-base-inverse:var(--ifm-color-content-inverse);--ifm-font-color-secondary:var(--ifm-color-content-secondary);--ifm-font-family-base:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--ifm-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--ifm-font-size-base:100%;--ifm-font-weight-light:300;--ifm-font-weight-normal:400;--ifm-font-weight-semibold:500;--ifm-font-weight-bold:700;--ifm-font-weight-base:var(--ifm-font-weight-normal);--ifm-line-height-base:1.65;--ifm-global-spacing:1rem;--ifm-spacing-vertical:var(--ifm-global-spacing);--ifm-spacing-horizontal:var(--ifm-global-spacing);--ifm-transition-fast:200ms;--ifm-transition-slow:400ms;--ifm-transition-timing-default:cubic-bezier(0.08,0.52,0.52,1);--ifm-global-shadow-lw:0 1px 2px 0 #0000001a;--ifm-global-shadow-md:0 5px 40px #0003;--ifm-global-shadow-tl:0 12px 28px 0 #0003,0 2px 4px 0 #0000001a;--ifm-z-index-dropdown:100;--ifm-z-index-fixed:200;--ifm-z-index-overlay:400;--ifm-container-width:1140px;--ifm-container-width-xl:1320px;--ifm-code-background:#f6f7f8;--ifm-code-border-radius:var(--ifm-global-radius);--ifm-code-font-size:90%;--ifm-code-padding-horizontal:0.1rem;--ifm-code-padding-vertical:0.1rem;--ifm-pre-background:var(--ifm-code-background);--ifm-pre-border-radius:var(--ifm-code-border-radius);--ifm-pre-color:inherit;--ifm-pre-line-height:1.45;--ifm-pre-padding:1rem;--ifm-heading-color:inherit;--ifm-heading-margin-top:0;--ifm-heading-margin-bottom:var(--ifm-spacing-vertical);--ifm-heading-font-family:var(--ifm-font-family-base);--ifm-heading-font-weight:var(--ifm-font-weight-bold);--ifm-heading-line-height:1.25;--ifm-h1-font-size:2rem;--ifm-h2-font-size:1.5rem;--ifm-h3-font-size:1.25rem;--ifm-h4-font-size:1rem;--ifm-h5-font-size:0.875rem;--ifm-h6-font-size:0.85rem;--ifm-image-alignment-padding:1.25rem;--ifm-leading-desktop:1.25;--ifm-leading:calc(var(--ifm-leading-desktop)*1rem);--ifm-list-left-padding:2rem;--ifm-list-margin:1rem;--ifm-list-item-margin:0.25rem;--ifm-list-paragraph-margin:1rem;--ifm-table-cell-padding:0.75rem;--ifm-table-background:#0000;--ifm-table-stripe-background:#00000008;--ifm-table-border-width:1px;--ifm-table-border-color:var(--ifm-color-emphasis-300);--ifm-table-head-background:inherit;--ifm-table-head-color:inherit;--ifm-table-head-font-weight:var(--ifm-font-weight-bold);--ifm-table-cell-color:inherit;--ifm-link-color:var(--ifm-color-primary);--ifm-link-decoration:none;--ifm-link-hover-color:var(--ifm-link-color);--ifm-link-hover-decoration:underline;--ifm-paragraph-margin-bottom:var(--ifm-leading);--ifm-blockquote-font-size:var(--ifm-font-size-base);--ifm-blockquote-border-left-width:2px;--ifm-blockquote-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-blockquote-padding-vertical:0;--ifm-blockquote-shadow:none;--ifm-blockquote-color:var(--ifm-color-emphasis-800);--ifm-blockquote-border-color:var(--ifm-color-emphasis-300);--ifm-hr-background-color:var(--ifm-color-emphasis-500);--ifm-hr-height:1px;--ifm-hr-margin-vertical:1.5rem;--ifm-scrollbar-size:7px;--ifm-scrollbar-track-background-color:#f1f1f1;--ifm-scrollbar-thumb-background-color:silver;--ifm-scrollbar-thumb-hover-background-color:#a7a7a7;--ifm-alert-background-color:inherit;--ifm-alert-border-color:inherit;--ifm-alert-border-radius:var(--ifm-global-radius);--ifm-alert-border-width:0px;--ifm-alert-border-left-width:5px;--ifm-alert-color:var(--ifm-font-color-base);--ifm-alert-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-alert-padding-vertical:var(--ifm-spacing-vertical);--ifm-alert-shadow:var(--ifm-global-shadow-lw);--ifm-avatar-intro-margin:1rem;--ifm-avatar-intro-alignment:inherit;--ifm-avatar-photo-size:3rem;--ifm-badge-background-color:inherit;--ifm-badge-border-color:inherit;--ifm-badge-border-radius:var(--ifm-global-radius);--ifm-badge-border-width:var(--ifm-global-border-width);--ifm-badge-color:var(--ifm-color-white);--ifm-badge-padding-horizontal:calc(var(--ifm-spacing-horizontal)*0.5);--ifm-badge-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-breadcrumb-border-radius:1.5rem;--ifm-breadcrumb-spacing:0.5rem;--ifm-breadcrumb-color-active:var(--ifm-color-primary);--ifm-breadcrumb-item-background-active:var(--ifm-hover-overlay);--ifm-breadcrumb-padding-horizontal:0.8rem;--ifm-breadcrumb-padding-vertical:0.4rem;--ifm-breadcrumb-size-multiplier:1;--ifm-breadcrumb-separator:url('data:image/svg+xml;utf8,');--ifm-breadcrumb-separator-filter:none;--ifm-breadcrumb-separator-size:0.5rem;--ifm-breadcrumb-separator-size-multiplier:1.25;--ifm-button-background-color:inherit;--ifm-button-border-color:var(--ifm-button-background-color);--ifm-button-border-width:var(--ifm-global-border-width);--ifm-button-font-weight:var(--ifm-font-weight-bold);--ifm-button-padding-horizontal:1.5rem;--ifm-button-padding-vertical:0.375rem;--ifm-button-size-multiplier:1;--ifm-button-transition-duration:var(--ifm-transition-fast);--ifm-button-border-radius:calc(var(--ifm-global-radius)*var(--ifm-button-size-multiplier));--ifm-button-group-spacing:2px;--ifm-card-background-color:var(--ifm-background-surface-color);--ifm-card-border-radius:calc(var(--ifm-global-radius)*2);--ifm-card-horizontal-spacing:var(--ifm-global-spacing);--ifm-card-vertical-spacing:var(--ifm-global-spacing);--ifm-toc-border-color:var(--ifm-color-emphasis-300);--ifm-toc-link-color:var(--ifm-color-content-secondary);--ifm-toc-padding-vertical:0.5rem;--ifm-toc-padding-horizontal:0.5rem;--ifm-dropdown-background-color:var(--ifm-background-surface-color);--ifm-dropdown-font-weight:var(--ifm-font-weight-semibold);--ifm-dropdown-link-color:var(--ifm-font-color-base);--ifm-dropdown-hover-background-color:var(--ifm-hover-overlay);--ifm-footer-background-color:var(--ifm-color-emphasis-100);--ifm-footer-color:inherit;--ifm-footer-link-color:var(--ifm-color-emphasis-700);--ifm-footer-link-hover-color:var(--ifm-color-primary);--ifm-footer-link-horizontal-spacing:0.5rem;--ifm-footer-padding-horizontal:calc(var(--ifm-spacing-horizontal)*2);--ifm-footer-padding-vertical:calc(var(--ifm-spacing-vertical)*2);--ifm-footer-title-color:inherit;--ifm-footer-logo-max-width:min(30rem,90vw);--ifm-hero-background-color:var(--ifm-background-surface-color);--ifm-hero-text-color:var(--ifm-color-emphasis-800);--ifm-menu-color:var(--ifm-color-emphasis-700);--ifm-menu-color-active:var(--ifm-color-primary);--ifm-menu-color-background-active:var(--ifm-hover-overlay);--ifm-menu-color-background-hover:var(--ifm-hover-overlay);--ifm-menu-link-padding-horizontal:0.75rem;--ifm-menu-link-padding-vertical:0.375rem;--ifm-menu-link-sublist-icon:url('data:image/svg+xml;utf8,');--ifm-menu-link-sublist-icon-filter:none;--ifm-navbar-background-color:var(--ifm-background-surface-color);--ifm-navbar-height:3.75rem;--ifm-navbar-item-padding-horizontal:0.75rem;--ifm-navbar-item-padding-vertical:0.25rem;--ifm-navbar-link-color:var(--ifm-font-color-base);--ifm-navbar-link-active-color:var(--ifm-link-color);--ifm-navbar-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-navbar-padding-vertical:calc(var(--ifm-spacing-vertical)*0.5);--ifm-navbar-shadow:var(--ifm-global-shadow-lw);--ifm-navbar-search-input-background-color:var(--ifm-color-emphasis-200);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-800);--ifm-navbar-search-input-placeholder-color:var(--ifm-color-emphasis-500);--ifm-navbar-search-input-icon:url('data:image/svg+xml;utf8,');--ifm-navbar-sidebar-width:83vw;--ifm-pagination-border-radius:var(--ifm-global-radius);--ifm-pagination-color-active:var(--ifm-color-primary);--ifm-pagination-font-size:1rem;--ifm-pagination-item-active-background:var(--ifm-hover-overlay);--ifm-pagination-page-spacing:0.2em;--ifm-pagination-padding-horizontal:calc(var(--ifm-spacing-horizontal)*1);--ifm-pagination-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-pagination-nav-border-radius:var(--ifm-global-radius);--ifm-pagination-nav-color-hover:var(--ifm-color-primary);--ifm-pills-color-active:var(--ifm-color-primary);--ifm-pills-color-background-active:var(--ifm-hover-overlay);--ifm-pills-spacing:0.125rem;--ifm-tabs-color:var(--ifm-font-color-secondary);--ifm-tabs-color-active:var(--ifm-color-primary);--ifm-tabs-color-active-border:var(--ifm-tabs-color-active);--ifm-tabs-padding-horizontal:1rem;--ifm-tabs-padding-vertical:1rem;--docusaurus-progress-bar-color:var(--ifm-color-primary);--ifm-color-primary:#723e13;--ifm-color-primary-dark:#673811;--ifm-color-primary-darker:#613510;--ifm-color-primary-darkest:#502b0d;--ifm-color-primary-light:#7d4415;--ifm-color-primary-lighter:#834716;--ifm-color-primary-lightest:#945119;--ifm-code-font-size:95%;--docusaurus-highlighted-code-line-bg:#0000001a;--docusaurus-announcement-bar-height:auto;--docusaurus-collapse-button-bg:#0000;--docusaurus-collapse-button-bg-hover:#0000001a;--doc-sidebar-width:300px;--doc-sidebar-hidden-width:30px;--docusaurus-blog-social-icon-size:1rem;--docusaurus-tag-list-border:var(--ifm-color-emphasis-300)}.badge--danger,.badge--info,.badge--primary,.badge--secondary,.badge--success,.badge--warning{--ifm-badge-border-color:var(--ifm-badge-background-color)}.button--link,.button--outline{--ifm-button-background-color:#0000}*{box-sizing:border-box}html{background-color:var(--ifm-background-color);color:var(--ifm-font-color-base);color-scheme:var(--ifm-color-scheme);font:var(--ifm-font-size-base)/var(--ifm-line-height-base) var(--ifm-font-family-base);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;-webkit-text-size-adjust:100%;text-size-adjust:100%}iframe{border:0;color-scheme:auto}.container{margin:0 auto;max-width:var(--ifm-container-width)}.container--fluid{max-width:inherit}.row{display:flex;flex-wrap:wrap;margin:0 calc(var(--ifm-spacing-horizontal)*-1)}.margin-bottom--none,.margin-vert--none,.markdown>:last-child{margin-bottom:0!important}.margin-top--none,.margin-vert--none{margin-top:0!important}.row--no-gutters{margin-left:0;margin-right:0}.margin-horiz--none,.margin-right--none{margin-right:0!important}.row--no-gutters>.col{padding-left:0;padding-right:0}.row--align-top{align-items:flex-start}.row--align-bottom{align-items:flex-end}.menuExternalLink_NmtK,.row--align-center{align-items:center}.row--align-stretch{align-items:stretch}.row--align-baseline{align-items:baseline}.col{--ifm-col-width:100%;flex:1 0;margin-left:0;max-width:var(--ifm-col-width)}.padding-bottom--none,.padding-vert--none{padding-bottom:0!important}.padding-top--none,.padding-vert--none{padding-top:0!important}.padding-horiz--none,.padding-left--none{padding-left:0!important}.padding-horiz--none,.padding-right--none{padding-right:0!important}.col[class*=col--]{flex:0 0 var(--ifm-col-width)}.col--1{--ifm-col-width:8.33333%}.col--offset-1{margin-left:8.33333%}.col--2{--ifm-col-width:16.66667%}.col--offset-2{margin-left:16.66667%}.col--3{--ifm-col-width:25%}.col--offset-3{margin-left:25%}.col--4{--ifm-col-width:33.33333%}.col--offset-4{margin-left:33.33333%}.col--5{--ifm-col-width:41.66667%}.col--offset-5{margin-left:41.66667%}.col--6{--ifm-col-width:50%}.col--offset-6{margin-left:50%}.col--7{--ifm-col-width:58.33333%}.col--offset-7{margin-left:58.33333%}.col--8{--ifm-col-width:66.66667%}.col--offset-8{margin-left:66.66667%}.col--9{--ifm-col-width:75%}.col--offset-9{margin-left:75%}.col--10{--ifm-col-width:83.33333%}.col--offset-10{margin-left:83.33333%}.col--11{--ifm-col-width:91.66667%}.col--offset-11{margin-left:91.66667%}.col--12{--ifm-col-width:100%}.col--offset-12{margin-left:100%}.margin-horiz--none,.margin-left--none{margin-left:0!important}.margin--none{margin:0!important}.margin-bottom--xs,.margin-vert--xs{margin-bottom:.25rem!important}.margin-top--xs,.margin-vert--xs{margin-top:.25rem!important}.margin-horiz--xs,.margin-left--xs{margin-left:.25rem!important}.margin-horiz--xs,.margin-right--xs{margin-right:.25rem!important}.margin--xs{margin:.25rem!important}.margin-bottom--sm,.margin-vert--sm{margin-bottom:.5rem!important}.margin-top--sm,.margin-vert--sm{margin-top:.5rem!important}.margin-horiz--sm,.margin-left--sm{margin-left:.5rem!important}.margin-horiz--sm,.margin-right--sm{margin-right:.5rem!important}.margin--sm{margin:.5rem!important}.margin-bottom--md,.margin-vert--md{margin-bottom:1rem!important}.margin-top--md,.margin-vert--md{margin-top:1rem!important}.margin-horiz--md,.margin-left--md{margin-left:1rem!important}.margin-horiz--md,.margin-right--md{margin-right:1rem!important}.margin--md{margin:1rem!important}.margin-bottom--lg,.margin-vert--lg{margin-bottom:2rem!important}.margin-top--lg,.margin-vert--lg{margin-top:2rem!important}.margin-horiz--lg,.margin-left--lg{margin-left:2rem!important}.margin-horiz--lg,.margin-right--lg{margin-right:2rem!important}.margin--lg{margin:2rem!important}.margin-bottom--xl,.margin-vert--xl{margin-bottom:5rem!important}.margin-top--xl,.margin-vert--xl{margin-top:5rem!important}.margin-horiz--xl,.margin-left--xl{margin-left:5rem!important}.margin-horiz--xl,.margin-right--xl{margin-right:5rem!important}.margin--xl{margin:5rem!important}.padding--none{padding:0!important}.padding-bottom--xs,.padding-vert--xs{padding-bottom:.25rem!important}.padding-top--xs,.padding-vert--xs{padding-top:.25rem!important}.padding-horiz--xs,.padding-left--xs{padding-left:.25rem!important}.padding-horiz--xs,.padding-right--xs{padding-right:.25rem!important}.padding--xs{padding:.25rem!important}.padding-bottom--sm,.padding-vert--sm{padding-bottom:.5rem!important}.padding-top--sm,.padding-vert--sm{padding-top:.5rem!important}.padding-horiz--sm,.padding-left--sm{padding-left:.5rem!important}.padding-horiz--sm,.padding-right--sm{padding-right:.5rem!important}.padding--sm{padding:.5rem!important}.padding-bottom--md,.padding-vert--md{padding-bottom:1rem!important}.padding-top--md,.padding-vert--md{padding-top:1rem!important}.padding-horiz--md,.padding-left--md{padding-left:1rem!important}.padding-horiz--md,.padding-right--md{padding-right:1rem!important}.padding--md{padding:1rem!important}.padding-bottom--lg,.padding-vert--lg{padding-bottom:2rem!important}.padding-top--lg,.padding-vert--lg{padding-top:2rem!important}.padding-horiz--lg,.padding-left--lg{padding-left:2rem!important}.padding-horiz--lg,.padding-right--lg{padding-right:2rem!important}.padding--lg{padding:2rem!important}.padding-bottom--xl,.padding-vert--xl{padding-bottom:5rem!important}.padding-top--xl,.padding-vert--xl{padding-top:5rem!important}.padding-horiz--xl,.padding-left--xl{padding-left:5rem!important}.padding-horiz--xl,.padding-right--xl{padding-right:5rem!important}.padding--xl{padding:5rem!important}code{background-color:var(--ifm-code-background);border:.1rem solid #0000001a;border-radius:var(--ifm-code-border-radius);font-family:var(--ifm-font-family-monospace);font-size:var(--ifm-code-font-size);padding:var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal)}a code{color:inherit}pre{background-color:var(--ifm-pre-background);border-radius:var(--ifm-pre-border-radius);color:var(--ifm-pre-color);font:var(--ifm-code-font-size)/var(--ifm-pre-line-height) var(--ifm-font-family-monospace);padding:var(--ifm-pre-padding)}pre code{background-color:initial;border:none;font-size:100%;line-height:inherit;padding:0}kbd{background-color:var(--ifm-color-emphasis-0);border:1px solid var(--ifm-color-emphasis-400);border-radius:.2rem;box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-400);color:var(--ifm-color-emphasis-800);font:80% var(--ifm-font-family-monospace);padding:.15rem .3rem}h1,h2,h3,h4,h5,h6{color:var(--ifm-heading-color);font-family:var(--ifm-heading-font-family);font-weight:var(--ifm-heading-font-weight);line-height:var(--ifm-heading-line-height);margin:var(--ifm-heading-margin-top) 0 var(--ifm-heading-margin-bottom) 0}h1{font-size:var(--ifm-h1-font-size)}h2{font-size:var(--ifm-h2-font-size)}h3{font-size:var(--ifm-h3-font-size)}h4{font-size:var(--ifm-h4-font-size)}h5{font-size:var(--ifm-h5-font-size)}h6{font-size:var(--ifm-h6-font-size)}img{max-width:100%}img[align=right]{padding-left:var(--image-alignment-padding)}img[align=left]{padding-right:var(--image-alignment-padding)}.markdown{--ifm-h1-vertical-rhythm-top:3;--ifm-h2-vertical-rhythm-top:2;--ifm-h3-vertical-rhythm-top:1.5;--ifm-heading-vertical-rhythm-top:1.25;--ifm-h1-vertical-rhythm-bottom:1.25;--ifm-heading-vertical-rhythm-bottom:1}.markdown:after,.markdown:before{content:"";display:table}.markdown:after{clear:both}.markdown h1:first-child{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-h1-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown>h2{--ifm-h2-font-size:2rem;margin-top:calc(var(--ifm-h2-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h3{--ifm-h3-font-size:1.5rem;margin-top:calc(var(--ifm-h3-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h4,.markdown>h5,.markdown>h6{margin-top:calc(var(--ifm-heading-vertical-rhythm-top)*var(--ifm-leading))}.markdown>p,.markdown>pre,.markdown>ul{margin-bottom:var(--ifm-leading)}.markdown li>p{margin-top:var(--ifm-list-paragraph-margin)}.markdown li+li{margin-top:var(--ifm-list-item-margin)}ol,ul{margin:0 0 var(--ifm-list-margin);padding-left:var(--ifm-list-left-padding)}ol ol,ul ol{list-style-type:lower-roman}ol ol ol,ol ul ol,ul ol ol,ul ul ol{list-style-type:lower-alpha}table{border-collapse:collapse;display:block;margin-bottom:var(--ifm-spacing-vertical)}table thead tr{border-bottom:2px solid var(--ifm-table-border-color)}table thead,table tr:nth-child(2n){background-color:var(--ifm-table-stripe-background)}table tr{background-color:var(--ifm-table-background);border-top:var(--ifm-table-border-width) solid var(--ifm-table-border-color)}table td,table th{border:var(--ifm-table-border-width) solid var(--ifm-table-border-color);padding:var(--ifm-table-cell-padding)}table th{background-color:var(--ifm-table-head-background);color:var(--ifm-table-head-color);font-weight:var(--ifm-table-head-font-weight)}table td{color:var(--ifm-table-cell-color)}strong{font-weight:var(--ifm-font-weight-bold)}a{color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}a:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.button:hover,.text--no-decoration,.text--no-decoration:hover,a:not([href]){-webkit-text-decoration:none;text-decoration:none}p{margin:0 0 var(--ifm-paragraph-margin-bottom)}blockquote{border-left:var(--ifm-blockquote-border-left-width) solid var(--ifm-blockquote-border-color);box-shadow:var(--ifm-blockquote-shadow);color:var(--ifm-blockquote-color);font-size:var(--ifm-blockquote-font-size);padding:var(--ifm-blockquote-padding-vertical) var(--ifm-blockquote-padding-horizontal)}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}hr{background-color:var(--ifm-hr-background-color);border:0;height:var(--ifm-hr-height);margin:var(--ifm-hr-margin-vertical) 0}.shadow--lw{box-shadow:var(--ifm-global-shadow-lw)!important}.shadow--md{box-shadow:var(--ifm-global-shadow-md)!important}.shadow--tl{box-shadow:var(--ifm-global-shadow-tl)!important}.text--primary,.wordWrapButtonEnabled_uzNF .wordWrapButtonIcon_b1P5{color:var(--ifm-color-primary)}.text--secondary{color:var(--ifm-color-secondary)}.text--success{color:var(--ifm-color-success)}.text--info{color:var(--ifm-color-info)}.text--warning{color:var(--ifm-color-warning)}.text--danger{color:var(--ifm-color-danger)}.text--center{text-align:center}.text--left{text-align:left}.text--justify{text-align:justify}.text--right{text-align:right}.text--capitalize{text-transform:capitalize}.text--lowercase{text-transform:lowercase}.admonitionHeading_Gvgb,.alert__heading,.text--uppercase{text-transform:uppercase}.text--light{font-weight:var(--ifm-font-weight-light)}.text--normal{font-weight:var(--ifm-font-weight-normal)}.text--semibold{font-weight:var(--ifm-font-weight-semibold)}.text--bold{font-weight:var(--ifm-font-weight-bold)}.text--italic{font-style:italic}.text--truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text--break{word-wrap:break-word!important;word-break:break-word!important}.clean-btn{background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:0}.alert,.alert .close{color:var(--ifm-alert-foreground-color)}.clean-list{padding-left:0}.alert--primary{--ifm-alert-background-color:var(--ifm-color-primary-contrast-background);--ifm-alert-background-color-highlight:#3578e526;--ifm-alert-foreground-color:var(--ifm-color-primary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-primary-dark)}.alert--secondary{--ifm-alert-background-color:var(--ifm-color-secondary-contrast-background);--ifm-alert-background-color-highlight:#ebedf026;--ifm-alert-foreground-color:var(--ifm-color-secondary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-secondary-dark)}.alert--success{--ifm-alert-background-color:var(--ifm-color-success-contrast-background);--ifm-alert-background-color-highlight:#00a40026;--ifm-alert-foreground-color:var(--ifm-color-success-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-success-dark)}.alert--info{--ifm-alert-background-color:var(--ifm-color-info-contrast-background);--ifm-alert-background-color-highlight:#54c7ec26;--ifm-alert-foreground-color:var(--ifm-color-info-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-info-dark)}.alert--warning{--ifm-alert-background-color:var(--ifm-color-warning-contrast-background);--ifm-alert-background-color-highlight:#ffba0026;--ifm-alert-foreground-color:var(--ifm-color-warning-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-warning-dark)}.alert--danger{--ifm-alert-background-color:var(--ifm-color-danger-contrast-background);--ifm-alert-background-color-highlight:#fa383e26;--ifm-alert-foreground-color:var(--ifm-color-danger-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-danger-dark)}.alert{--ifm-code-background:var(--ifm-alert-background-color-highlight);--ifm-link-color:var(--ifm-alert-foreground-color);--ifm-link-hover-color:var(--ifm-alert-foreground-color);--ifm-link-decoration:underline;--ifm-tabs-color:var(--ifm-alert-foreground-color);--ifm-tabs-color-active:var(--ifm-alert-foreground-color);--ifm-tabs-color-active-border:var(--ifm-alert-border-color);background-color:var(--ifm-alert-background-color);border:var(--ifm-alert-border-width) solid var(--ifm-alert-border-color);border-left-width:var(--ifm-alert-border-left-width);border-radius:var(--ifm-alert-border-radius);box-shadow:var(--ifm-alert-shadow);padding:var(--ifm-alert-padding-vertical) var(--ifm-alert-padding-horizontal)}.alert__heading{align-items:center;display:flex;font:700 var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);margin-bottom:.5rem}.alert__icon{display:inline-flex;margin-right:.4em}.alert__icon svg{fill:var(--ifm-alert-foreground-color);stroke:var(--ifm-alert-foreground-color);stroke-width:0}.alert .close{margin:calc(var(--ifm-alert-padding-vertical)*-1) calc(var(--ifm-alert-padding-horizontal)*-1) 0 0;opacity:.75}.alert .close:focus,.alert .close:hover{opacity:1}.alert a{text-decoration-color:var(--ifm-alert-border-color)}.alert a:hover{text-decoration-thickness:2px}.avatar{column-gap:var(--ifm-avatar-intro-margin);display:flex}.avatar__photo{border-radius:50%;display:block;height:var(--ifm-avatar-photo-size);overflow:hidden;width:var(--ifm-avatar-photo-size)}.avatar__photo--sm{--ifm-avatar-photo-size:2rem}.avatar__photo--lg{--ifm-avatar-photo-size:4rem}.avatar__photo--xl{--ifm-avatar-photo-size:6rem}.avatar__intro{display:flex;flex:1 1;flex-direction:column;justify-content:center;text-align:var(--ifm-avatar-intro-alignment)}.badge,.breadcrumbs__item,.breadcrumbs__link,.button,.dropdown>.navbar__link:after{display:inline-block}.avatar__name{font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base)}.avatar__subtitle{margin-top:.25rem}.avatar--vertical{--ifm-avatar-intro-alignment:center;--ifm-avatar-intro-margin:0.5rem;align-items:center;flex-direction:column}.badge{background-color:var(--ifm-badge-background-color);border:var(--ifm-badge-border-width) solid var(--ifm-badge-border-color);border-radius:var(--ifm-badge-border-radius);color:var(--ifm-badge-color);font-size:75%;font-weight:var(--ifm-font-weight-bold);line-height:1;padding:var(--ifm-badge-padding-vertical) var(--ifm-badge-padding-horizontal)}.badge--primary{--ifm-badge-background-color:var(--ifm-color-primary)}.badge--secondary{--ifm-badge-background-color:var(--ifm-color-secondary);color:var(--ifm-color-black)}.breadcrumbs__link,.button.button--secondary.button--outline:not(.button--active):not(:hover){color:var(--ifm-font-color-base)}.badge--success{--ifm-badge-background-color:var(--ifm-color-success)}.badge--info{--ifm-badge-background-color:var(--ifm-color-info)}.badge--warning{--ifm-badge-background-color:var(--ifm-color-warning)}.badge--danger{--ifm-badge-background-color:var(--ifm-color-danger)}.breadcrumbs{margin-bottom:0;padding-left:0}.breadcrumbs__item:not(:last-child):after{background:var(--ifm-breadcrumb-separator) center;content:" ";display:inline-block;filter:var(--ifm-breadcrumb-separator-filter);height:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier));margin:0 var(--ifm-breadcrumb-spacing);opacity:.5;width:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier))}.breadcrumbs__item--active .breadcrumbs__link{background:var(--ifm-breadcrumb-item-background-active);color:var(--ifm-breadcrumb-color-active)}.breadcrumbs__link{border-radius:var(--ifm-breadcrumb-border-radius);font-size:calc(1rem*var(--ifm-breadcrumb-size-multiplier));padding:calc(var(--ifm-breadcrumb-padding-vertical)*var(--ifm-breadcrumb-size-multiplier)) calc(var(--ifm-breadcrumb-padding-horizontal)*var(--ifm-breadcrumb-size-multiplier));transition-duration:var(--ifm-transition-fast);transition-property:background,color}.breadcrumbs__link:any-link:hover,.breadcrumbs__link:link:hover,.breadcrumbs__link:visited:hover,area[href].breadcrumbs__link:hover{background:var(--ifm-breadcrumb-item-background-active);-webkit-text-decoration:none;text-decoration:none}.breadcrumbs--sm{--ifm-breadcrumb-size-multiplier:0.8}.breadcrumbs--lg{--ifm-breadcrumb-size-multiplier:1.2}.button{background-color:var(--ifm-button-background-color);border:var(--ifm-button-border-width) solid var(--ifm-button-border-color);border-radius:var(--ifm-button-border-radius);cursor:pointer;font-size:calc(.875rem*var(--ifm-button-size-multiplier));font-weight:var(--ifm-button-font-weight);line-height:1.5;padding:calc(var(--ifm-button-padding-vertical)*var(--ifm-button-size-multiplier)) calc(var(--ifm-button-padding-horizontal)*var(--ifm-button-size-multiplier));text-align:center;transition-duration:var(--ifm-button-transition-duration);transition-property:color,background,border-color;-webkit-user-select:none;user-select:none;white-space:nowrap}.button,.button:hover{color:var(--ifm-button-color)}.button--outline{--ifm-button-color:var(--ifm-button-border-color)}.button--outline:hover{--ifm-button-background-color:var(--ifm-button-border-color)}.button--link{--ifm-button-border-color:#0000;color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}.button--link.button--active,.button--link:active,.button--link:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.dropdown__link--active,.dropdown__link:hover,.menu__link:hover,.navbar__brand:hover,.navbar__link--active,.navbar__link:hover,.pagination-nav__link:hover,.pagination__link:hover,.sidebarItemLink_mo7H:hover,.tag_zVej:hover{-webkit-text-decoration:none;text-decoration:none}.button.disabled,.button:disabled,.button[disabled]{opacity:.65;pointer-events:none}.button--sm{--ifm-button-size-multiplier:0.8}.button--lg{--ifm-button-size-multiplier:1.35}.button--block{display:block;width:100%}.button.button--secondary{color:var(--ifm-color-gray-900)}:where(.button--primary){--ifm-button-background-color:var(--ifm-color-primary);--ifm-button-border-color:var(--ifm-color-primary)}:where(.button--primary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-primary-dark);--ifm-button-border-color:var(--ifm-color-primary-dark)}.button--primary.button--active,.button--primary:active{--ifm-button-background-color:var(--ifm-color-primary-darker);--ifm-button-border-color:var(--ifm-color-primary-darker)}:where(.button--secondary){--ifm-button-background-color:var(--ifm-color-secondary);--ifm-button-border-color:var(--ifm-color-secondary)}:where(.button--secondary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-secondary-dark);--ifm-button-border-color:var(--ifm-color-secondary-dark)}.button--secondary.button--active,.button--secondary:active{--ifm-button-background-color:var(--ifm-color-secondary-darker);--ifm-button-border-color:var(--ifm-color-secondary-darker)}:where(.button--success){--ifm-button-background-color:var(--ifm-color-success);--ifm-button-border-color:var(--ifm-color-success)}:where(.button--success):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-success-dark);--ifm-button-border-color:var(--ifm-color-success-dark)}.button--success.button--active,.button--success:active{--ifm-button-background-color:var(--ifm-color-success-darker);--ifm-button-border-color:var(--ifm-color-success-darker)}:where(.button--info){--ifm-button-background-color:var(--ifm-color-info);--ifm-button-border-color:var(--ifm-color-info)}:where(.button--info):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-info-dark);--ifm-button-border-color:var(--ifm-color-info-dark)}.button--info.button--active,.button--info:active{--ifm-button-background-color:var(--ifm-color-info-darker);--ifm-button-border-color:var(--ifm-color-info-darker)}:where(.button--warning){--ifm-button-background-color:var(--ifm-color-warning);--ifm-button-border-color:var(--ifm-color-warning)}:where(.button--warning):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-warning-dark);--ifm-button-border-color:var(--ifm-color-warning-dark)}.button--warning.button--active,.button--warning:active{--ifm-button-background-color:var(--ifm-color-warning-darker);--ifm-button-border-color:var(--ifm-color-warning-darker)}:where(.button--danger){--ifm-button-background-color:var(--ifm-color-danger);--ifm-button-border-color:var(--ifm-color-danger)}:where(.button--danger):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-danger-dark);--ifm-button-border-color:var(--ifm-color-danger-dark)}.button--danger.button--active,.button--danger:active{--ifm-button-background-color:var(--ifm-color-danger-darker);--ifm-button-border-color:var(--ifm-color-danger-darker)}.button-group{display:inline-flex;gap:var(--ifm-button-group-spacing)}.button-group>.button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.button-group>.button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.button-group--block{display:flex;justify-content:stretch}.button-group--block>.button{flex-grow:1}.card{background-color:var(--ifm-card-background-color);border-radius:var(--ifm-card-border-radius);box-shadow:var(--ifm-global-shadow-lw);display:flex;flex-direction:column;overflow:hidden}.card--full-height{height:100%}.card__image{padding-top:var(--ifm-card-vertical-spacing)}.card__image:first-child{padding-top:0}.card__body,.card__footer,.card__header{padding:var(--ifm-card-vertical-spacing) var(--ifm-card-horizontal-spacing)}.card__body:not(:last-child),.card__footer:not(:last-child),.card__header:not(:last-child){padding-bottom:0}.card__body>:last-child,.card__footer>:last-child,.card__header>:last-child{margin-bottom:0}.card__footer{margin-top:auto}.table-of-contents{font-size:.8rem;margin-bottom:0;padding:var(--ifm-toc-padding-vertical) 0}.table-of-contents,.table-of-contents ul{list-style:none;padding-left:var(--ifm-toc-padding-horizontal)}.table-of-contents li{margin:var(--ifm-toc-padding-vertical) var(--ifm-toc-padding-horizontal)}.table-of-contents__left-border{border-left:1px solid var(--ifm-toc-border-color)}.table-of-contents__link{color:var(--ifm-toc-link-color);display:block}.table-of-contents__link--active,.table-of-contents__link--active code,.table-of-contents__link:hover,.table-of-contents__link:hover code{color:var(--ifm-color-primary);-webkit-text-decoration:none;text-decoration:none}.close{color:var(--ifm-color-black);float:right;font-size:1.5rem;font-weight:var(--ifm-font-weight-bold);line-height:1;opacity:.5;padding:1rem;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.close:hover{opacity:.7}.close:focus,.theme-code-block-highlighted-line .codeLineNumber_Tfdd:before{opacity:.8}.dropdown{display:inline-flex;font-weight:var(--ifm-dropdown-font-weight);position:relative;vertical-align:top}.dropdown--hoverable:hover .dropdown__menu,.dropdown--show .dropdown__menu{opacity:1;pointer-events:all;transform:translateY(-1px);visibility:visible}#nprogress,.dropdown__menu,.navbar__item.dropdown .navbar__link:not([href]){pointer-events:none}.dropdown--right .dropdown__menu{left:inherit;right:0}.dropdown--nocaret .navbar__link:after{content:none!important}.dropdown__menu{background-color:var(--ifm-dropdown-background-color);border-radius:var(--ifm-global-radius);box-shadow:var(--ifm-global-shadow-md);left:0;max-height:80vh;min-width:10rem;opacity:0;overflow-y:auto;padding:.5rem;position:absolute;top:calc(100% - var(--ifm-navbar-item-padding-vertical) + .3rem);transform:translateY(-.625rem);transition-duration:var(--ifm-transition-fast);transition-property:opacity,transform,visibility;transition-timing-function:var(--ifm-transition-timing-default);visibility:hidden;z-index:var(--ifm-z-index-dropdown)}.sidebar_re4s,.tableOfContents_bqdL{max-height:calc(100vh - var(--ifm-navbar-height) - 2rem)}.menu__caret,.menu__link,.menu__list-item-collapsible{border-radius:.25rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.dropdown__link{border-radius:.25rem;color:var(--ifm-dropdown-link-color);display:block;font-size:.875rem;margin-top:.2rem;padding:.25rem .5rem;white-space:nowrap}.dropdown__link--active,.dropdown__link:hover{background-color:var(--ifm-dropdown-hover-background-color);color:var(--ifm-dropdown-link-color)}.dropdown__link--active,.dropdown__link--active:hover{--ifm-dropdown-link-color:var(--ifm-link-color)}.dropdown>.navbar__link:after{border-color:currentcolor #0000;border-style:solid;border-width:.4em .4em 0;content:"";margin-left:.3em;position:relative;top:2px;transform:translateY(-50%)}.footer{background-color:var(--ifm-footer-background-color);color:var(--ifm-footer-color);padding:var(--ifm-footer-padding-vertical) var(--ifm-footer-padding-horizontal)}.footer--dark{--ifm-footer-background-color:#303846;--ifm-footer-color:var(--ifm-footer-link-color);--ifm-footer-link-color:var(--ifm-color-secondary);--ifm-footer-title-color:var(--ifm-color-white)}.footer__links{margin-bottom:1rem}.footer__link-item{color:var(--ifm-footer-link-color);line-height:2}.footer__link-item:hover{color:var(--ifm-footer-link-hover-color)}.footer__link-separator{margin:0 var(--ifm-footer-link-horizontal-spacing)}.footer__logo{margin-top:1rem;max-width:var(--ifm-footer-logo-max-width)}.footer__title{color:var(--ifm-footer-title-color);font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base);margin-bottom:var(--ifm-heading-margin-bottom)}.menu,.navbar__link{font-weight:var(--ifm-font-weight-semibold)}.docItemContainer_Djhp article>:first-child,.docItemContainer_Djhp header+*,.footer__item{margin-top:0}.admonitionContent_BuS1>:last-child,.cardContainer_fWXF :last-child,.collapsibleContent_i85q p:last-child,.details_lb9f>summary>p:last-child,.footer__items{margin-bottom:0}.codeBlockStandalone_MEMb,[type=checkbox]{padding:0}.hero{align-items:center;background-color:var(--ifm-hero-background-color);color:var(--ifm-hero-text-color);display:flex;padding:4rem 2rem}.hero--primary{--ifm-hero-background-color:var(--ifm-color-primary);--ifm-hero-text-color:var(--ifm-font-color-base-inverse)}.hero--dark{--ifm-hero-background-color:#303846;--ifm-hero-text-color:var(--ifm-color-white)}.hero__title,.title_f1Hy{font-size:3rem}.hero__subtitle{font-size:1.5rem}.menu__list{margin:0;padding-left:0}.menu__caret,.menu__link{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu__list .menu__list{flex:0 0 100%;margin-top:.25rem;padding-left:var(--ifm-menu-link-padding-horizontal)}.menu__list-item:not(:first-child){margin-top:.25rem}.menu__list-item--collapsed .menu__list{height:0;overflow:hidden}.details_lb9f[data-collapsed=false].isBrowser_bmU9>summary:before,.details_lb9f[open]:not(.isBrowser_bmU9)>summary:before,.menu__list-item--collapsed .menu__caret:before,.menu__list-item--collapsed .menu__link--sublist:after{transform:rotate(90deg)}.menu__list-item-collapsible{display:flex;flex-wrap:wrap;position:relative}.menu__caret:hover,.menu__link:hover,.menu__list-item-collapsible--active,.menu__list-item-collapsible:hover{background:var(--ifm-menu-color-background-hover)}.menu__list-item-collapsible .menu__link--active,.menu__list-item-collapsible .menu__link:hover{background:none!important}.menu__caret,.menu__link{align-items:center;display:flex}.menu__link{color:var(--ifm-menu-color);flex:1;line-height:1.25}.menu__link:hover{color:var(--ifm-menu-color)}.menu__caret:before,.menu__link--sublist-caret:after{content:"";height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast) linear;width:1.25rem;filter:var(--ifm-menu-link-sublist-icon-filter)}.menu__link--sublist-caret:after{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem;margin-left:auto;min-width:1.25rem}.menu__link--active,.menu__link--active:hover{color:var(--ifm-menu-color-active)}.navbar__brand,.navbar__link{color:var(--ifm-navbar-link-color)}.menu__link--active:not(.menu__link--sublist){background-color:var(--ifm-menu-color-background-active)}.menu__caret:before{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem}.navbar--dark,html[data-theme=dark]{--ifm-menu-link-sublist-icon-filter:invert(100%) sepia(94%) saturate(17%) hue-rotate(223deg) brightness(104%) contrast(98%)}.navbar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-navbar-shadow);height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar,.navbar>.container,.navbar>.container-fluid{display:flex}.navbar--fixed-top{position:sticky;top:0;z-index:var(--ifm-z-index-fixed)}.navbar-sidebar,.navbar-sidebar__backdrop{bottom:0;opacity:0;position:fixed;transition-duration:var(--ifm-transition-fast);transition-timing-function:ease-in-out;left:0;top:0;visibility:hidden}.navbar__inner{display:flex;flex-wrap:wrap;justify-content:space-between;width:100%}.navbar__brand{align-items:center;display:flex;margin-right:1rem;min-width:0}.navbar__brand:hover{color:var(--ifm-navbar-link-hover-color)}.announcementBarContent_xLdY,.navbar__title{flex:1 1 auto}.navbar__toggle{display:none;margin-right:.5rem}.navbar__logo{flex:0 0 auto;height:2rem;margin-right:.5rem}.docCardListItem_W1sv>*,.navbar__logo img,body,html{height:100%}.navbar__items{align-items:center;display:flex;flex:1;min-width:0}.navbar__items--center{flex:0 0 auto}.navbar__items--center .navbar__brand{margin:0}.navbar__items--center+.navbar__items--right{flex:1}.navbar__items--right{flex:0 0 auto;justify-content:flex-end}.navbar__item{display:inline-block;padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}.navbar__link--active,.navbar__link:hover{color:var(--ifm-navbar-link-hover-color)}.navbar--dark,.navbar--primary{--ifm-menu-color:var(--ifm-color-gray-300);--ifm-navbar-link-color:var(--ifm-color-gray-100);--ifm-navbar-search-input-background-color:#ffffff1a;--ifm-navbar-search-input-placeholder-color:#ffffff80;color:var(--ifm-color-white)}.navbar--dark{--ifm-navbar-background-color:#242526;--ifm-menu-color-background-active:#ffffff0d;--ifm-navbar-search-input-color:var(--ifm-color-white)}.navbar--primary{--ifm-navbar-background-color:var(--ifm-color-primary);--ifm-navbar-link-hover-color:var(--ifm-color-white);--ifm-menu-color-active:var(--ifm-color-white);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-500)}.navbar__search-input{appearance:none;background:var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat .75rem center/1rem 1rem;border:none;border-radius:2rem;color:var(--ifm-navbar-search-input-color);cursor:text;display:inline-block;font-size:1rem;height:2rem;padding:0 .5rem 0 2.25rem;width:12.5rem}.navbar__search-input::placeholder{color:var(--ifm-navbar-search-input-placeholder-color)}.navbar-sidebar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-global-shadow-md);transform:translate3d(-100%,0,0);transition-property:opacity,visibility,transform;width:var(--ifm-navbar-sidebar-width)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar__items{transform:translateZ(0)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar--show .navbar-sidebar__backdrop{opacity:1;visibility:visible}.navbar-sidebar__backdrop{background-color:#0009;right:0;transition-property:opacity,visibility}.navbar-sidebar__brand{align-items:center;box-shadow:var(--ifm-navbar-shadow);display:flex;flex:1;height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar-sidebar__items{display:flex;height:calc(100% - var(--ifm-navbar-height));transition:transform var(--ifm-transition-fast) ease-in-out}.navbar-sidebar__items--show-secondary{transform:translate3d(calc((var(--ifm-navbar-sidebar-width))*-1),0,0)}.navbar-sidebar__item{flex-shrink:0;padding:.5rem;width:calc(var(--ifm-navbar-sidebar-width))}.navbar-sidebar__back{background:var(--ifm-menu-color-background-active);font-size:15px;font-weight:var(--ifm-button-font-weight);margin:0 0 .2rem -.5rem;padding:.6rem 1.5rem;position:relative;text-align:left;top:-.5rem;width:calc(100% + 1rem)}.navbar-sidebar__close{display:flex;margin-left:auto}.pagination{column-gap:var(--ifm-pagination-page-spacing);display:flex;font-size:var(--ifm-pagination-font-size);padding-left:0}.pagination--sm{--ifm-pagination-font-size:0.8rem;--ifm-pagination-padding-horizontal:0.8rem;--ifm-pagination-padding-vertical:0.2rem}.pagination--lg{--ifm-pagination-font-size:1.2rem;--ifm-pagination-padding-horizontal:1.2rem;--ifm-pagination-padding-vertical:0.3rem}.pagination__item{display:inline-flex}.pagination__item>span{padding:var(--ifm-pagination-padding-vertical)}.pagination__item--active .pagination__link{color:var(--ifm-pagination-color-active)}.pagination__item--active .pagination__link,.pagination__item:not(.pagination__item--active):hover .pagination__link{background:var(--ifm-pagination-item-active-background)}.pagination__item--disabled,.pagination__item[disabled]{opacity:.25;pointer-events:none}.pagination__link{border-radius:var(--ifm-pagination-border-radius);color:var(--ifm-font-color-base);display:inline-block;padding:var(--ifm-pagination-padding-vertical) var(--ifm-pagination-padding-horizontal);transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav{display:grid;grid-gap:var(--ifm-spacing-horizontal);gap:var(--ifm-spacing-horizontal);grid-template-columns:repeat(2,1fr)}.pagination-nav__link{border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-pagination-nav-border-radius);display:block;height:100%;line-height:var(--ifm-heading-line-height);padding:var(--ifm-global-spacing);transition:border-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav__link:hover{border-color:var(--ifm-pagination-nav-color-hover)}.pagination-nav__link--next{grid-column:2/3;text-align:right}.pagination-nav__label{font-size:var(--ifm-h4-font-size);font-weight:var(--ifm-heading-font-weight);word-break:break-word}.pagination-nav__link--prev .pagination-nav__label:before{content:"« "}.pagination-nav__link--next .pagination-nav__label:after{content:" »"}.pagination-nav__sublabel{color:var(--ifm-color-content-secondary);font-size:var(--ifm-h5-font-size);font-weight:var(--ifm-font-weight-semibold);margin-bottom:.25rem}.pills__item,.sidebarItemTitle_pO2u,.tabs{font-weight:var(--ifm-font-weight-bold)}.pills{display:flex;gap:var(--ifm-pills-spacing);padding-left:0}.pills__item{border-radius:.5rem;cursor:pointer;display:inline-block;padding:.25rem 1rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs,:not(.containsTaskList_mC6p>li)>.containsTaskList_mC6p{padding-left:0}.pills__item--active{color:var(--ifm-pills-color-active)}.pills__item--active,.pills__item:not(.pills__item--active):hover{background:var(--ifm-pills-color-background-active)}.pills--block{justify-content:stretch}.pills--block .pills__item{flex-grow:1;text-align:center}.tabs{color:var(--ifm-tabs-color);display:flex;margin-bottom:0;overflow-x:auto}.tabs__item{border-bottom:3px solid #0000;border-radius:var(--ifm-global-radius);cursor:pointer;display:inline-flex;padding:var(--ifm-tabs-padding-vertical) var(--ifm-tabs-padding-horizontal);transition:background-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs__item--active{border-bottom-color:var(--ifm-tabs-color-active-border);border-bottom-left-radius:0;border-bottom-right-radius:0;color:var(--ifm-tabs-color-active)}.tabs__item:hover{background-color:var(--ifm-hover-overlay)}.tabs--block{justify-content:stretch}.tabs--block .tabs__item{flex-grow:1;justify-content:center}html[data-theme=dark]{--ifm-color-scheme:dark;--ifm-color-emphasis-0:var(--ifm-color-gray-1000);--ifm-color-emphasis-100:var(--ifm-color-gray-900);--ifm-color-emphasis-200:var(--ifm-color-gray-800);--ifm-color-emphasis-300:var(--ifm-color-gray-700);--ifm-color-emphasis-400:var(--ifm-color-gray-600);--ifm-color-emphasis-600:var(--ifm-color-gray-400);--ifm-color-emphasis-700:var(--ifm-color-gray-300);--ifm-color-emphasis-800:var(--ifm-color-gray-200);--ifm-color-emphasis-900:var(--ifm-color-gray-100);--ifm-color-emphasis-1000:var(--ifm-color-gray-0);--ifm-background-color:#1b1b1d;--ifm-background-surface-color:#242526;--ifm-hover-overlay:#ffffff0d;--ifm-color-content:#e3e3e3;--ifm-color-content-secondary:#fff;--ifm-breadcrumb-separator-filter:invert(64%) sepia(11%) saturate(0%) hue-rotate(149deg) brightness(99%) contrast(95%);--ifm-code-background:#ffffff1a;--ifm-scrollbar-track-background-color:#444;--ifm-scrollbar-thumb-background-color:#686868;--ifm-scrollbar-thumb-hover-background-color:#7a7a7a;--ifm-table-stripe-background:#ffffff12;--ifm-toc-border-color:var(--ifm-color-emphasis-200);--ifm-color-primary-contrast-background:#102445;--ifm-color-primary-contrast-foreground:#ebf2fc;--ifm-color-secondary-contrast-background:#474748;--ifm-color-secondary-contrast-foreground:#fdfdfe;--ifm-color-success-contrast-background:#003100;--ifm-color-success-contrast-foreground:#e6f6e6;--ifm-color-info-contrast-background:#193c47;--ifm-color-info-contrast-foreground:#eef9fd;--ifm-color-warning-contrast-background:#4d3800;--ifm-color-warning-contrast-foreground:#fff8e6;--ifm-color-danger-contrast-background:#4b1113;--ifm-color-danger-contrast-foreground:#ffebec}#nprogress .bar{background:var(--docusaurus-progress-bar-color);height:2px;left:0;position:fixed;top:0;width:100%;z-index:1031}#nprogress .peg{box-shadow:0 0 10px var(--docusaurus-progress-bar-color),0 0 5px var(--docusaurus-progress-bar-color);height:100%;opacity:1;position:absolute;right:0;transform:rotate(3deg) translateY(-4px);width:100px}[data-theme=dark]{--ifm-color-primary:#ffb866;--ifm-color-primary-dark:#ffa742;--ifm-color-primary-darker:#ff9f30;--ifm-color-primary-darkest:#fa8600;--ifm-color-primary-light:#ffc98a;--ifm-color-primary-lighter:#ffd19c;--ifm-color-primary-lightest:#ffead1;--docusaurus-highlighted-code-line-bg:#0000004d}.backToTopButton_sjWU{background-color:var(--ifm-color-emphasis-200);border-radius:50%;bottom:1.3rem;box-shadow:var(--ifm-global-shadow-lw);height:3rem;opacity:0;position:fixed;right:1.3rem;transform:scale(0);transition:all var(--ifm-transition-fast) var(--ifm-transition-timing-default);visibility:hidden;width:3rem;z-index:calc(var(--ifm-z-index-fixed) - 1)}.backToTopButton_sjWU:after{background-color:var(--ifm-color-emphasis-1000);content:" ";display:inline-block;height:100%;-webkit-mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;width:100%}.backToTopButtonShow_xfvO{opacity:1;transform:scale(1);visibility:visible}.skipToContent_fXgn{background-color:var(--ifm-background-surface-color);color:var(--ifm-color-emphasis-900);left:100%;padding:calc(var(--ifm-global-spacing)/2) var(--ifm-global-spacing);position:fixed;top:1rem;z-index:calc(var(--ifm-z-index-fixed) + 1)}.skipToContent_fXgn:focus{box-shadow:var(--ifm-global-shadow-md);left:1rem}.closeButton_CVFx{line-height:0;padding:0}.content_knG7{font-size:85%;padding:5px 0;text-align:center}.content_knG7 a{color:inherit;-webkit-text-decoration:underline;text-decoration:underline}.announcementBar_mb4j{align-items:center;background-color:var(--ifm-color-white);border-bottom:1px solid var(--ifm-color-emphasis-100);color:var(--ifm-color-black);display:flex;height:var(--docusaurus-announcement-bar-height)}#__docusaurus-base-url-issue-banner-container,.docSidebarContainer_YfHR,.navbarSearchContainer_Bca1:empty,.sidebarLogo_isFc,.themedComponent_mlkZ,.toggleIcon_g3eP,html[data-announcement-bar-initially-dismissed=true] .announcementBar_mb4j{display:none}.announcementBarPlaceholder_vyr4{flex:0 0 10px}.announcementBarClose_gvF7{align-self:stretch;flex:0 0 30px}.toggle_vylO{height:2rem;width:2rem}.toggleButton_gllP{align-items:center;border-radius:50%;display:flex;height:100%;justify-content:center;transition:background var(--ifm-transition-fast);width:100%}.authorSocialIcon_XYv3,.authorSocialLink_owbf,.authorSocials_rSDt{height:var(--docusaurus-blog-social-icon-size)}.toggleButton_gllP:hover{background:var(--ifm-color-emphasis-200)}[data-theme-choice=dark] .darkToggleIcon_wfgR,[data-theme-choice=light] .lightToggleIcon_pyhR,[data-theme-choice=system] .systemToggleIcon_QzmC,[data-theme=dark] .themedComponent--dark_xIcU,[data-theme=light] .themedComponent--light_NVdE,html:not([data-theme]) .themedComponent--light_NVdE{display:initial}.toggleButtonDisabled_aARS{cursor:not-allowed}.darkNavbarColorModeToggle_X3D1:hover{background:var(--ifm-color-gray-800)}[data-theme=dark]:root{--docusaurus-collapse-button-bg:#ffffff0d;--docusaurus-collapse-button-bg-hover:#ffffff1a}.collapseSidebarButton_PEFL{display:none;margin:0}.categoryLinkLabel_W154,.linkLabel_WmDU{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical}.iconExternalLink_nPIU{margin-left:.3rem}.linkLabel_WmDU{line-clamp:2;-webkit-line-clamp:2}.categoryLink_byQd{overflow:hidden}.menu__link--sublist-caret:after{margin-left:var(--ifm-menu-link-padding-vertical)}.categoryLinkLabel_W154{flex:1;line-clamp:2;-webkit-line-clamp:2}.docMainContainer_TBSr,.docRoot_UBD9{display:flex;width:100%}.authorSocialIcon_XYv3,.authorSocialLink_owbf{width:var(--docusaurus-blog-social-icon-size)}.docsWrapper_hBAB{display:flex;flex:1 0 auto}.dropdownNavbarItemMobile_J0Sd{cursor:pointer}.iconLanguage_nlXk{margin-right:5px;vertical-align:text-bottom}.navbarHideable_m1mJ{transition:transform var(--ifm-transition-fast) ease}.navbarHidden_jGov{transform:translate3d(0,calc(-100% - 2px),0)}.navbar__items--right>:last-child{padding-right:0}.errorBoundaryError_a6uf{color:red;white-space:pre-wrap}.errorBoundaryFallback_VBag{color:red;padding:.55rem}.footerLogoLink_BH7S{opacity:.5;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.footerLogoLink_BH7S:hover,.hash-link:focus,:hover>.hash-link{opacity:1}body:not(.navigation-with-keyboard) :not(input):focus{outline:0}.hash-link{opacity:0;padding-left:.5rem;transition:opacity var(--ifm-transition-fast);-webkit-user-select:none;user-select:none}.hash-link:before{content:"#"}.anchorTargetStickyNavbar_Vzrq{scroll-margin-top:calc(var(--ifm-navbar-height) + .5rem)}.anchorTargetHideOnScrollNavbar_vjPI{scroll-margin-top:.5rem}.mainWrapper_z2l0{display:flex;flex:1 0 auto;flex-direction:column}.docusaurus-mt-lg{margin-top:3rem}#__docusaurus{display:flex;flex-direction:column;min-height:100%}.sidebar_re4s{overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 2rem)}.sidebarItemTitle_pO2u{font-size:var(--ifm-h3-font-size)}.container_mt6G,.sidebarItemList_Yudw{font-size:.9rem}.sidebarItem__DBe{margin-top:.7rem}.sidebarItemLink_mo7H{color:var(--ifm-font-color-base);display:block}.sidebarItemLinkActive_I1ZP{color:var(--ifm-color-primary)!important}.yearGroupHeading_rMGB{margin-bottom:.4rem;margin-top:1.6rem}.yearGroupHeading_QT03{margin:1rem .75rem .5rem}.cardContainer_fWXF{--ifm-link-color:var(--ifm-color-emphasis-800);--ifm-link-hover-color:var(--ifm-color-emphasis-700);--ifm-link-hover-decoration:none;border:1px solid var(--ifm-color-emphasis-200);box-shadow:0 1.5px 3px 0 #00000026;transition:all var(--ifm-transition-fast) ease;transition-property:border,box-shadow}.cardContainer_fWXF:hover{border-color:var(--ifm-color-primary);box-shadow:0 3px 6px 0 #0003}.cardTitle_rnsV{font-size:1.2rem}.cardDescription_PWke{font-size:.8rem}.docCardListItem_W1sv{margin-bottom:2rem}[data-theme=dark] .blueskySvg_AzZw,[data-theme=dark] .githubSvg_Uu4N,[data-theme=dark] .instagramSvg_YC40,[data-theme=dark] .linkedinSvg_FCgI,[data-theme=dark] .threadsSvg_PTXY,[data-theme=dark] .xSvg_y3PF{fill:var(--light)}[data-theme=light] .blueskySvg_AzZw,[data-theme=light] .githubSvg_Uu4N,[data-theme=light] .instagramSvg_YC40,[data-theme=light] .linkedinSvg_FCgI,[data-theme=light] .threadsSvg_PTXY,[data-theme=light] .xSvg_y3PF{fill:var(--dark)}.authorSocials_rSDt{align-items:center;display:flex;flex-wrap:wrap;line-clamp:1;-webkit-line-clamp:1;overflow:hidden;-webkit-box-orient:vertical}.authorSocialLink_owbf,.authorSocials_rSDt{line-height:0}.authorSocialLink_owbf{margin-right:.4rem}.authorImage_XqGP{--ifm-avatar-photo-size:3.6rem}.author-as-h1_n9oJ .authorImage_XqGP{--ifm-avatar-photo-size:7rem}.author-as-h2_gXvM .authorImage_XqGP{--ifm-avatar-photo-size:5.4rem}.authorDetails_lV9A{align-items:flex-start;display:flex;flex-direction:column;justify-content:space-around}.authorName_yefp{display:flex;flex-direction:row;font-size:1.1rem;line-height:1.1rem}.author-as-h1_n9oJ .authorName_yefp{display:inline;font-size:2.4rem;line-height:2.4rem}.author-as-h2_gXvM .authorName_yefp{display:inline;font-size:1.4rem;line-height:1.4rem}.authorTitle_nd0D{display:-webkit-box;font-size:.8rem;line-clamp:1;-webkit-line-clamp:1;line-height:1rem;overflow:hidden;-webkit-box-orient:vertical}.author-as-h1_n9oJ .authorTitle_nd0D{font-size:1.2rem;line-height:1.6rem}.author-as-h2_gXvM .authorTitle_nd0D{font-size:1rem;line-height:1.3rem}.authorBlogPostCount_iiJ5{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.8rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.buttonGroup_M5ko button,.codeBlockContainer_Ckt0{background:var(--prism-background-color);color:var(--prism-color)}.authorCol_Hf19{max-width:inherit!important}.imageOnlyAuthorRow_pa_O{display:flex;flex-flow:row wrap}.buttons_AeoN,.features_t9lD{align-items:center;display:flex}.imageOnlyAuthorCol_G86a{margin-left:.3rem;margin-right:.3rem}.authorListItem_n3yI{list-style-type:none;margin-bottom:2rem}.features_t9lD{padding:2rem 0;width:100%}.featureSvg_GfXr{height:200px;width:200px}.heroBanner_qdFl{overflow:hidden;padding:4rem 0;position:relative;text-align:center}.buttons_AeoN{justify-content:center}.codeBlockContainer_Ckt0{border-radius:var(--ifm-code-border-radius);box-shadow:var(--ifm-global-shadow-lw);margin-bottom:var(--ifm-leading)}.codeBlock_bY9V{--ifm-pre-background:var(--prism-background-color);margin:0;padding:0}.codeBlockLines_e6Vv{float:left;font:inherit;min-width:100%;padding:var(--ifm-pre-padding)}.codeBlockLinesWithNumbering_o6Pm{display:table;padding:var(--ifm-pre-padding) 0}:where(:root){--docusaurus-highlighted-code-line-bg:#484d5b}:where([data-theme=dark]){--docusaurus-highlighted-code-line-bg:#646464}.theme-code-block-highlighted-line{background-color:var(--docusaurus-highlighted-code-line-bg);display:block;margin:0 calc(var(--ifm-pre-padding)*-1);padding:0 var(--ifm-pre-padding)}.codeLine_lJS_{counter-increment:line-count;display:table-row}.codeLineNumber_Tfdd{background:var(--ifm-pre-background);display:table-cell;left:0;overflow-wrap:normal;padding:0 var(--ifm-pre-padding);position:sticky;text-align:right;width:1%}.codeLineNumber_Tfdd:before{content:counter(line-count);opacity:.4}.codeLineContent_feaV{padding-right:var(--ifm-pre-padding)}.theme-code-block:hover .copyButtonCopied_Vdqa{opacity:1!important}.copyButtonIcons_IEyt{height:1.125rem;position:relative;width:1.125rem}.copyButtonIcon_TrPX,.copyButtonSuccessIcon_cVMy{fill:currentColor;height:inherit;left:0;opacity:inherit;position:absolute;top:0;transition:all var(--ifm-transition-fast) ease;width:inherit}.copyButtonSuccessIcon_cVMy{color:#00d600;left:50%;opacity:0;top:50%;transform:translate(-50%,-50%) scale(.33)}.copyButtonCopied_Vdqa .copyButtonIcon_TrPX{opacity:0;transform:scale(.33)}.copyButtonCopied_Vdqa .copyButtonSuccessIcon_cVMy{opacity:1;transform:translate(-50%,-50%) scale(1);transition-delay:75ms}.wordWrapButtonIcon_b1P5{height:1.2rem;width:1.2rem}.buttonGroup_M5ko{column-gap:.2rem;display:flex;position:absolute;right:calc(var(--ifm-pre-padding)/2);top:calc(var(--ifm-pre-padding)/2)}.buttonGroup_M5ko button{align-items:center;border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-global-radius);display:flex;line-height:0;opacity:0;padding:.4rem;transition:opacity var(--ifm-transition-fast) ease-in-out}.buttonGroup_M5ko button:focus-visible,.buttonGroup_M5ko button:hover{opacity:1!important}.theme-code-block:hover .buttonGroup_M5ko button{opacity:.4}.tag_zVej{border:1px solid var(--docusaurus-tag-list-border);transition:border var(--ifm-transition-fast)}.tag_zVej:hover{--docusaurus-tag-list-border:var(--ifm-link-color)}.tagRegular_sFm0{border-radius:var(--ifm-global-radius);font-size:90%;padding:.2rem .5rem .3rem}.tagWithCount_h2kH{align-items:center;border-left:0;display:flex;padding:0 .5rem 0 1rem;position:relative}.tagWithCount_h2kH:after,.tagWithCount_h2kH:before{border:1px solid var(--docusaurus-tag-list-border);content:"";position:absolute;top:50%;transition:inherit}.tagWithCount_h2kH:before{border-bottom:0;border-right:0;height:1.18rem;right:100%;transform:translate(50%,-50%) rotate(-45deg);width:1.18rem}.tagWithCount_h2kH:after{border-radius:50%;height:.5rem;left:0;transform:translateY(-50%);width:.5rem}.tagWithCount_h2kH span{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.7rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.tag_Nnez{display:inline-block;margin:.5rem .5rem 0 1rem}.codeBlockContent_QJqH{border-radius:inherit;direction:ltr;position:relative}.codeBlockTitle_OeMC{border-bottom:1px solid var(--ifm-color-emphasis-300);border-top-left-radius:inherit;border-top-right-radius:inherit;font-size:var(--ifm-code-font-size);font-weight:500;padding:.75rem var(--ifm-pre-padding)}.codeBlockTitle_OeMC+.codeBlockContent_QJqH .codeBlock_a8dz{border-top-left-radius:0;border-top-right-radius:0}.tags_jXut{display:inline}.tag_QGVx{display:inline-block;margin:0 .4rem .5rem 0}.iconEdit_Z9Sw{margin-right:.3em;vertical-align:sub}.details_lb9f{--docusaurus-details-summary-arrow-size:0.38rem;--docusaurus-details-transition:transform 200ms ease;--docusaurus-details-decoration-color:grey}.details_lb9f>summary{cursor:pointer;padding-left:1rem;position:relative}.details_lb9f>summary::-webkit-details-marker{display:none}.details_lb9f>summary:before{border-color:#0000 #0000 #0000 var(--docusaurus-details-decoration-color);border-style:solid;border-width:var(--docusaurus-details-summary-arrow-size);content:"";left:0;position:absolute;top:.45rem;transform:rotate(0);transform-origin:calc(var(--docusaurus-details-summary-arrow-size)/2) 50%;transition:var(--docusaurus-details-transition)}.collapsibleContent_i85q{border-top:1px solid var(--docusaurus-details-decoration-color);margin-top:1rem;padding-top:1rem}.lastUpdated_JAkA{font-size:smaller;font-style:italic;margin-top:.2rem}.tocCollapsibleButton_TO0P{align-items:center;display:flex;font-size:inherit;justify-content:space-between;padding:.4rem .8rem;width:100%}.tocCollapsibleButton_TO0P:after{background:var(--ifm-menu-link-sublist-icon) 50% 50%/2rem 2rem no-repeat;content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast);width:1.25rem}.tocCollapsibleButtonExpanded_MG3E:after,.tocCollapsibleExpanded_sAul{transform:none}.tocCollapsible_ETCw{background-color:var(--ifm-menu-color-background-active);border-radius:var(--ifm-global-radius);margin:1rem 0}.tocCollapsibleContent_vkbj>ul{border-left:none;border-top:1px solid var(--ifm-color-emphasis-300);font-size:15px;padding:.2rem 0}.tocCollapsibleContent_vkbj ul li{margin:.4rem .8rem}.tocCollapsibleContent_vkbj a{display:block}.details_b_Ee{--docusaurus-details-decoration-color:var(--ifm-alert-border-color);--docusaurus-details-transition:transform var(--ifm-transition-fast) ease;border:1px solid var(--ifm-alert-border-color);margin:0 0 var(--ifm-spacing-vertical)}.img_ev3q{height:auto}.tableOfContents_bqdL{overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 1rem)}.admonition_xJq3{margin-bottom:1em}.admonitionHeading_Gvgb{font:var(--ifm-heading-font-weight) var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family)}.admonitionHeading_Gvgb:not(:last-child){margin-bottom:.3rem}.admonitionHeading_Gvgb code{text-transform:none}.admonitionIcon_Rf37{display:inline-block;margin-right:.4em;vertical-align:middle}.admonitionIcon_Rf37 svg{display:inline-block;fill:var(--ifm-alert-foreground-color);height:1.6em;width:1.6em}.breadcrumbHomeIcon_YNFT{height:1.1rem;position:relative;top:1px;vertical-align:top;width:1.1rem}.breadcrumbsContainer_Z_bl{--ifm-breadcrumb-size-multiplier:0.8;margin-bottom:.8rem}.title_kItE{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-leading)*1.25)}.mdxPageWrapper_j9I6{justify-content:center}@media (min-width:997px){.collapseSidebarButton_PEFL,.expandButton_TmdG{background-color:var(--docusaurus-collapse-button-bg)}:root{--docusaurus-announcement-bar-height:30px}.announcementBarClose_gvF7,.announcementBarPlaceholder_vyr4{flex-basis:50px}.collapseSidebarButton_PEFL{border:1px solid var(--ifm-toc-border-color);border-radius:0;bottom:0;display:block!important;height:40px;position:sticky}.collapseSidebarButtonIcon_kv0_{margin-top:4px;transform:rotate(180deg)}.expandButtonIcon_i1dp,[dir=rtl] .collapseSidebarButtonIcon_kv0_{transform:rotate(0)}.collapseSidebarButton_PEFL:focus,.collapseSidebarButton_PEFL:hover,.expandButton_TmdG:focus,.expandButton_TmdG:hover{background-color:var(--docusaurus-collapse-button-bg-hover)}.menuHtmlItem_M9Kj{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu_SIkG{flex-grow:1;padding:.5rem}@supports (scrollbar-gutter:stable){.menu_SIkG{padding:.5rem 0 .5rem .5rem;scrollbar-gutter:stable}}.menuWithAnnouncementBar_GW3s{margin-bottom:var(--docusaurus-announcement-bar-height)}.sidebar_njMd{display:flex;flex-direction:column;height:100%;padding-top:var(--ifm-navbar-height);width:var(--doc-sidebar-width)}.sidebarWithHideableNavbar_wUlq{padding-top:0}.sidebarHidden_VK0M{opacity:0;visibility:hidden}.sidebarLogo_isFc{align-items:center;color:inherit!important;display:flex!important;margin:0 var(--ifm-navbar-padding-horizontal);max-height:var(--ifm-navbar-height);min-height:var(--ifm-navbar-height);-webkit-text-decoration:none!important;text-decoration:none!important}.sidebarLogo_isFc img{height:2rem;margin-right:.5rem}.expandButton_TmdG{align-items:center;display:flex;height:100%;justify-content:center;position:absolute;right:0;top:0;transition:background-color var(--ifm-transition-fast) ease;width:100%}[dir=rtl] .expandButtonIcon_i1dp{transform:rotate(180deg)}.docSidebarContainer_YfHR{border-right:1px solid var(--ifm-toc-border-color);clip-path:inset(0);display:block;margin-top:calc(var(--ifm-navbar-height)*-1);transition:width var(--ifm-transition-fast) ease;width:var(--doc-sidebar-width);will-change:width}.docSidebarContainerHidden_DPk8{cursor:pointer;width:var(--doc-sidebar-hidden-width)}.sidebarViewport_aRkj{height:100%;max-height:100vh;position:sticky;top:0}.docMainContainer_TBSr{flex-grow:1;max-width:calc(100% - var(--doc-sidebar-width))}.docMainContainerEnhanced_lQrH{max-width:calc(100% - var(--doc-sidebar-hidden-width))}.docItemWrapperEnhanced_JWYK{max-width:calc(var(--ifm-container-width) + var(--doc-sidebar-width))!important}.navbarSearchContainer_Bca1{padding:0 var(--ifm-navbar-item-padding-horizontal)}.lastUpdated_JAkA{text-align:right}.tocMobile_ITEo{display:none}.docItemCol_VOVn,.generatedIndexPage_vN6x{max-width:75%!important}}@media (min-width:1440px){.container{max-width:var(--ifm-container-width-xl)}}@media (max-width:996px){.col{--ifm-col-width:100%;flex-basis:var(--ifm-col-width);margin-left:0}.footer{--ifm-footer-padding-horizontal:0}.colorModeToggle_DEke,.footer__link-separator,.navbar__item,.sidebar_re4s,.tableOfContents_bqdL{display:none}.footer__col{margin-bottom:calc(var(--ifm-spacing-vertical)*3)}.footer__link-item{display:block;width:max-content}.hero{padding-left:0;padding-right:0}.navbar>.container,.navbar>.container-fluid{padding:0}.navbar__toggle{display:inherit}.navbar__search-input{width:9rem}.pills--block,.tabs--block{flex-direction:column}.navbarSearchContainer_Bca1{position:absolute;right:var(--ifm-navbar-padding-horizontal)}.docItemContainer_F8PC{padding:0 .3rem}}@media screen and (max-width:996px){.heroBanner_qdFl{padding:2rem}}@media (max-width:576px){.markdown h1:first-child{--ifm-h1-font-size:2rem}.markdown>h2{--ifm-h2-font-size:1.5rem}.markdown>h3{--ifm-h3-font-size:1.25rem}.title_f1Hy{font-size:2rem}}@media (hover:hover){.backToTopButton_sjWU:hover{background-color:var(--ifm-color-emphasis-300)}}@media (pointer:fine){.thin-scrollbar{scrollbar-width:thin}.thin-scrollbar::-webkit-scrollbar{height:var(--ifm-scrollbar-size);width:var(--ifm-scrollbar-size)}.thin-scrollbar::-webkit-scrollbar-track{background:var(--ifm-scrollbar-track-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb{background:var(--ifm-scrollbar-thumb-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb:hover{background:var(--ifm-scrollbar-thumb-hover-background-color)}}@media (prefers-reduced-motion:reduce){:root{--ifm-transition-fast:0ms;--ifm-transition-slow:0ms}}@media print{.announcementBar_mb4j,.footer,.menu,.navbar,.noPrint_WFHX,.pagination-nav,.table-of-contents,.tocMobile_ITEo{display:none}.tabs{page-break-inside:avoid}.codeBlockLines_e6Vv{white-space:pre-wrap}}
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/files/10Conditionals-3e24c68dfe8243c740742b827d334632.zip b/deprecated/old-docs-build/docs/assets/files/10Conditionals-3e24c68dfe8243c740742b827d334632.zip
new file mode 100644
index 0000000..0e0afa8
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/10Conditionals-3e24c68dfe8243c740742b827d334632.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/11Loops-6621ed9548c6634d4ee22ef3586fc240.zip b/deprecated/old-docs-build/docs/assets/files/11Loops-6621ed9548c6634d4ee22ef3586fc240.zip
new file mode 100644
index 0000000..494292e
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/11Loops-6621ed9548c6634d4ee22ef3586fc240.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/1SeparateFiles-7d9f7713a106a2eec74d62419765e290.zip b/deprecated/old-docs-build/docs/assets/files/1SeparateFiles-7d9f7713a106a2eec74d62419765e290.zip
new file mode 100644
index 0000000..1fe1a23
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/1SeparateFiles-7d9f7713a106a2eec74d62419765e290.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/2MultipleScripts-4e548fd7a16e4ddcc624cbe3490294db.zip b/deprecated/old-docs-build/docs/assets/files/2MultipleScripts-4e548fd7a16e4ddcc624cbe3490294db.zip
new file mode 100644
index 0000000..96c9384
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/2MultipleScripts-4e548fd7a16e4ddcc624cbe3490294db.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/3CombinedScripting-52ed0c5e387ff77b9eea12aa0cfee9b7.zip b/deprecated/old-docs-build/docs/assets/files/3CombinedScripting-52ed0c5e387ff77b9eea12aa0cfee9b7.zip
new file mode 100644
index 0000000..f00cd80
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/3CombinedScripting-52ed0c5e387ff77b9eea12aa0cfee9b7.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/3UseStrict-20ee21a9e0ee6960cda4d1bbf78b6d75.zip b/deprecated/old-docs-build/docs/assets/files/3UseStrict-20ee21a9e0ee6960cda4d1bbf78b6d75.zip
new file mode 100644
index 0000000..9215d43
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/3UseStrict-20ee21a9e0ee6960cda4d1bbf78b6d75.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/4CodeStructure-cabc22cf2a79ef3d713506dac8093d22.zip b/deprecated/old-docs-build/docs/assets/files/4CodeStructure-cabc22cf2a79ef3d713506dac8093d22.zip
new file mode 100644
index 0000000..d432c7d
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/4CodeStructure-cabc22cf2a79ef3d713506dac8093d22.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/4Variables-a27d05750cf66d481c93d758b3a92892.zip b/deprecated/old-docs-build/docs/assets/files/4Variables-a27d05750cf66d481c93d758b3a92892.zip
new file mode 100644
index 0000000..7428e44
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/4Variables-a27d05750cf66d481c93d758b3a92892.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/5DataTypes-ca67cdf95a284dcd80dfa572a2167cdc.zip b/deprecated/old-docs-build/docs/assets/files/5DataTypes-ca67cdf95a284dcd80dfa572a2167cdc.zip
new file mode 100644
index 0000000..7af84c4
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/5DataTypes-ca67cdf95a284dcd80dfa572a2167cdc.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/6Interaction-9b264ebdac882611fe1770a420ecdad3.zip b/deprecated/old-docs-build/docs/assets/files/6Interaction-9b264ebdac882611fe1770a420ecdad3.zip
new file mode 100644
index 0000000..fc37090
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/6Interaction-9b264ebdac882611fe1770a420ecdad3.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/7TypeConversions-d80142a78a3fe3d16434d6ea86d44b76.zip b/deprecated/old-docs-build/docs/assets/files/7TypeConversions-d80142a78a3fe3d16434d6ea86d44b76.zip
new file mode 100644
index 0000000..de933ac
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/7TypeConversions-d80142a78a3fe3d16434d6ea86d44b76.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/8BasicOperators-0b7f8a2f44b47e7145ca2c32184d58ae.zip b/deprecated/old-docs-build/docs/assets/files/8BasicOperators-0b7f8a2f44b47e7145ca2c32184d58ae.zip
new file mode 100644
index 0000000..64d7839
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/8BasicOperators-0b7f8a2f44b47e7145ca2c32184d58ae.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/9Comparisons-dfd05ab4017eec98df51ef1d3a8b3b61.zip b/deprecated/old-docs-build/docs/assets/files/9Comparisons-dfd05ab4017eec98df51ef1d3a8b3b61.zip
new file mode 100644
index 0000000..40c1dc0
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/9Comparisons-dfd05ab4017eec98df51ef1d3a8b3b61.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/MVC-0866959835c9fba51f614b92bff06a29.zip b/deprecated/old-docs-build/docs/assets/files/MVC-0866959835c9fba51f614b92bff06a29.zip
new file mode 100644
index 0000000..d9bbed7
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/MVC-0866959835c9fba51f614b92bff06a29.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/MVC-57bb2864c9b7650f9c85d41a1c57ca2b.zip b/deprecated/old-docs-build/docs/assets/files/MVC-57bb2864c9b7650f9c85d41a1c57ca2b.zip
new file mode 100644
index 0000000..c90fd4b
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/MVC-57bb2864c9b7650f9c85d41a1c57ca2b.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/ejemplo-0ec1798119c771fc9a270fb77c889ee5.zip b/deprecated/old-docs-build/docs/assets/files/ejemplo-0ec1798119c771fc9a270fb77c889ee5.zip
new file mode 100644
index 0000000..bae4951
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/ejemplo-0ec1798119c771fc9a270fb77c889ee5.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/poe-90411ffd3a7b883c98fc3bce1a2fb2fd.zip b/deprecated/old-docs-build/docs/assets/files/poe-90411ffd3a7b883c98fc3bce1a2fb2fd.zip
new file mode 100644
index 0000000..2e9efdf
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/poe-90411ffd3a7b883c98fc3bce1a2fb2fd.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/template-b46cf49dd64f75c450e5d84f6561c390.zip b/deprecated/old-docs-build/docs/assets/files/template-b46cf49dd64f75c450e5d84f6561c390.zip
new file mode 100644
index 0000000..d6eed40
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/template-b46cf49dd64f75c450e5d84f6561c390.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/test-project-1478b9059d95c18c839b6f2cb2e933a7.zip b/deprecated/old-docs-build/docs/assets/files/test-project-1478b9059d95c18c839b6f2cb2e933a7.zip
new file mode 100644
index 0000000..1497ac6
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/test-project-1478b9059d95c18c839b6f2cb2e933a7.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/test-project-3aaf4dcbda2c9e242c15a57f21d4cefe.zip b/deprecated/old-docs-build/docs/assets/files/test-project-3aaf4dcbda2c9e242c15a57f21d4cefe.zip
new file mode 100644
index 0000000..727bea2
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/test-project-3aaf4dcbda2c9e242c15a57f21d4cefe.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/test-project-6ec78231f370807b71357e1ab869f78e.zip b/deprecated/old-docs-build/docs/assets/files/test-project-6ec78231f370807b71357e1ab869f78e.zip
new file mode 100644
index 0000000..097a1ce
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/test-project-6ec78231f370807b71357e1ab869f78e.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/test-project-7557f0bb1f8f083b819d1274f1d144f1.zip b/deprecated/old-docs-build/docs/assets/files/test-project-7557f0bb1f8f083b819d1274f1d144f1.zip
new file mode 100644
index 0000000..3935963
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/test-project-7557f0bb1f8f083b819d1274f1d144f1.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/test-project-93dfd1ac3e02260661c2fa2328fcab51.zip b/deprecated/old-docs-build/docs/assets/files/test-project-93dfd1ac3e02260661c2fa2328fcab51.zip
new file mode 100644
index 0000000..f684643
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/test-project-93dfd1ac3e02260661c2fa2328fcab51.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/test-project-a2495a9885fcfe28526249bfa4de39e5.zip b/deprecated/old-docs-build/docs/assets/files/test-project-a2495a9885fcfe28526249bfa4de39e5.zip
new file mode 100644
index 0000000..7fbc16c
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/test-project-a2495a9885fcfe28526249bfa4de39e5.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/test-project-ad8e257bd3935517b3f0025decf0410d.zip b/deprecated/old-docs-build/docs/assets/files/test-project-ad8e257bd3935517b3f0025decf0410d.zip
new file mode 100644
index 0000000..a4f9a66
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/test-project-ad8e257bd3935517b3f0025decf0410d.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/test-project-c3a39324c488a247d61b849904ae3583.zip b/deprecated/old-docs-build/docs/assets/files/test-project-c3a39324c488a247d61b849904ae3583.zip
new file mode 100644
index 0000000..5ed923d
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/test-project-c3a39324c488a247d61b849904ae3583.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/files/test_project-00723fe135cbc1b27127c3738eef4b91.zip b/deprecated/old-docs-build/docs/assets/files/test_project-00723fe135cbc1b27127c3738eef4b91.zip
new file mode 100644
index 0000000..2acf5fd
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/files/test_project-00723fe135cbc1b27127c3738eef4b91.zip differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-3e2c963edc90047d71412dbd4379d6c5.jpg b/deprecated/old-docs-build/docs/assets/images/001-3e2c963edc90047d71412dbd4379d6c5.jpg
new file mode 100644
index 0000000..34acadd
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-3e2c963edc90047d71412dbd4379d6c5.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-41fb52258fc994d1542e350e6ede1ff2.jpg b/deprecated/old-docs-build/docs/assets/images/001-41fb52258fc994d1542e350e6ede1ff2.jpg
new file mode 100644
index 0000000..de8635b
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-41fb52258fc994d1542e350e6ede1ff2.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-59d862491e089b8900a1a1302b38e2b1.jpg b/deprecated/old-docs-build/docs/assets/images/001-59d862491e089b8900a1a1302b38e2b1.jpg
new file mode 100644
index 0000000..c0c6ed9
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-59d862491e089b8900a1a1302b38e2b1.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-67c7acb3213beb7aee3f4e35f1956226.png b/deprecated/old-docs-build/docs/assets/images/001-67c7acb3213beb7aee3f4e35f1956226.png
new file mode 100644
index 0000000..8ece476
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-67c7acb3213beb7aee3f4e35f1956226.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-6b13b05c99f24659c0fdd6aa9cb9a54e.png b/deprecated/old-docs-build/docs/assets/images/001-6b13b05c99f24659c0fdd6aa9cb9a54e.png
new file mode 100644
index 0000000..ab46858
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-6b13b05c99f24659c0fdd6aa9cb9a54e.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-75f2387dcda210b321d472bf18f27e1b.png b/deprecated/old-docs-build/docs/assets/images/001-75f2387dcda210b321d472bf18f27e1b.png
new file mode 100644
index 0000000..f8d9b16
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-75f2387dcda210b321d472bf18f27e1b.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-864e0ff8193a5e9a52b48ffa27cb8ee8.jpg b/deprecated/old-docs-build/docs/assets/images/001-864e0ff8193a5e9a52b48ffa27cb8ee8.jpg
new file mode 100644
index 0000000..dce575a
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-864e0ff8193a5e9a52b48ffa27cb8ee8.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-8733c34016cb64ea12c57d9e23d8092d.jpg b/deprecated/old-docs-build/docs/assets/images/001-8733c34016cb64ea12c57d9e23d8092d.jpg
new file mode 100644
index 0000000..518f5d0
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-8733c34016cb64ea12c57d9e23d8092d.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-c4c8bb48c59869dd4df769f3cf51f993.png b/deprecated/old-docs-build/docs/assets/images/001-c4c8bb48c59869dd4df769f3cf51f993.png
new file mode 100644
index 0000000..15842b5
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-c4c8bb48c59869dd4df769f3cf51f993.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-dfc72d155586acb0bbb8a539328c1525.jpg b/deprecated/old-docs-build/docs/assets/images/001-dfc72d155586acb0bbb8a539328c1525.jpg
new file mode 100644
index 0000000..c2a129d
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-dfc72d155586acb0bbb8a539328c1525.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/001-e8d8da9c001bc730af376f342275ca20.png b/deprecated/old-docs-build/docs/assets/images/001-e8d8da9c001bc730af376f342275ca20.png
new file mode 100644
index 0000000..2f86bf5
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/001-e8d8da9c001bc730af376f342275ca20.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-3a446ae820869bf0a88fc5cd917aea5e.jpg b/deprecated/old-docs-build/docs/assets/images/002-3a446ae820869bf0a88fc5cd917aea5e.jpg
new file mode 100644
index 0000000..42bde1a
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-3a446ae820869bf0a88fc5cd917aea5e.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-3cc849032ec5f502d29309fd431a23c3.jpg b/deprecated/old-docs-build/docs/assets/images/002-3cc849032ec5f502d29309fd431a23c3.jpg
new file mode 100644
index 0000000..916c97c
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-3cc849032ec5f502d29309fd431a23c3.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-44e2ea506ec6e7165a42d8ec04c58498.jpg b/deprecated/old-docs-build/docs/assets/images/002-44e2ea506ec6e7165a42d8ec04c58498.jpg
new file mode 100644
index 0000000..3df3aa0
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-44e2ea506ec6e7165a42d8ec04c58498.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-80e8ce5941836a2fe5960d3818199ce1.jpg b/deprecated/old-docs-build/docs/assets/images/002-80e8ce5941836a2fe5960d3818199ce1.jpg
new file mode 100644
index 0000000..8f9d309
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-80e8ce5941836a2fe5960d3818199ce1.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-8e9638747692a350bf89d807c88aeb73.jpg b/deprecated/old-docs-build/docs/assets/images/002-8e9638747692a350bf89d807c88aeb73.jpg
new file mode 100644
index 0000000..7d8e8fb
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-8e9638747692a350bf89d807c88aeb73.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-8f6221fff8e7cd43fb3b7c38c36f6165.jpg b/deprecated/old-docs-build/docs/assets/images/002-8f6221fff8e7cd43fb3b7c38c36f6165.jpg
new file mode 100644
index 0000000..7acce51
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-8f6221fff8e7cd43fb3b7c38c36f6165.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-977da9cd060630ebc7e572ae1aacf534.jpg b/deprecated/old-docs-build/docs/assets/images/002-977da9cd060630ebc7e572ae1aacf534.jpg
new file mode 100644
index 0000000..10e967e
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-977da9cd060630ebc7e572ae1aacf534.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-a4abd6774a3b9bef75ca42cd0c0ecfc3.png b/deprecated/old-docs-build/docs/assets/images/002-a4abd6774a3b9bef75ca42cd0c0ecfc3.png
new file mode 100644
index 0000000..af92c37
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-a4abd6774a3b9bef75ca42cd0c0ecfc3.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-a777fb84a9d8038397af5173e0b73125.jpg b/deprecated/old-docs-build/docs/assets/images/002-a777fb84a9d8038397af5173e0b73125.jpg
new file mode 100644
index 0000000..4ed6ae3
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-a777fb84a9d8038397af5173e0b73125.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-d781507945c4d13b9105492d8b628b06.png b/deprecated/old-docs-build/docs/assets/images/002-d781507945c4d13b9105492d8b628b06.png
new file mode 100644
index 0000000..d68ba57
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-d781507945c4d13b9105492d8b628b06.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-de77d224db294bd3da92a04bb9e7e09b.jpg b/deprecated/old-docs-build/docs/assets/images/002-de77d224db294bd3da92a04bb9e7e09b.jpg
new file mode 100644
index 0000000..18a4c02
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-de77d224db294bd3da92a04bb9e7e09b.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/002-ff95109502443f22a69afe6ff06a8557.jpg b/deprecated/old-docs-build/docs/assets/images/002-ff95109502443f22a69afe6ff06a8557.jpg
new file mode 100644
index 0000000..51b1946
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/002-ff95109502443f22a69afe6ff06a8557.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/003-3a342e9fa8651e7c4517cb548a8a3f35.jpg b/deprecated/old-docs-build/docs/assets/images/003-3a342e9fa8651e7c4517cb548a8a3f35.jpg
new file mode 100644
index 0000000..39b28c4
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/003-3a342e9fa8651e7c4517cb548a8a3f35.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/003-62a04e670cd0199775a35f2fabd56a16.png b/deprecated/old-docs-build/docs/assets/images/003-62a04e670cd0199775a35f2fabd56a16.png
new file mode 100644
index 0000000..299814e
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/003-62a04e670cd0199775a35f2fabd56a16.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/003-690513153b9ef6265dfa7fc9df316889.jpg b/deprecated/old-docs-build/docs/assets/images/003-690513153b9ef6265dfa7fc9df316889.jpg
new file mode 100644
index 0000000..52c9efd
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/003-690513153b9ef6265dfa7fc9df316889.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/003-791e95ce5fdd169116aed4744a0bc1b0.jpg b/deprecated/old-docs-build/docs/assets/images/003-791e95ce5fdd169116aed4744a0bc1b0.jpg
new file mode 100644
index 0000000..f2b987f
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/003-791e95ce5fdd169116aed4744a0bc1b0.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/003-9b7aeb15694e138d72c15f122e1cb8fc.jpg b/deprecated/old-docs-build/docs/assets/images/003-9b7aeb15694e138d72c15f122e1cb8fc.jpg
new file mode 100644
index 0000000..4832c26
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/003-9b7aeb15694e138d72c15f122e1cb8fc.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/003-a1f15dfa9395429681f9cfab67adbefd.png b/deprecated/old-docs-build/docs/assets/images/003-a1f15dfa9395429681f9cfab67adbefd.png
new file mode 100644
index 0000000..8136792
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/003-a1f15dfa9395429681f9cfab67adbefd.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/003-bf928ab6db3d372988ae7e642549c833.jpg b/deprecated/old-docs-build/docs/assets/images/003-bf928ab6db3d372988ae7e642549c833.jpg
new file mode 100644
index 0000000..47dd403
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/003-bf928ab6db3d372988ae7e642549c833.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/003-e7e100cf33010282eadb59218305ecb2.jpg b/deprecated/old-docs-build/docs/assets/images/003-e7e100cf33010282eadb59218305ecb2.jpg
new file mode 100644
index 0000000..634d73a
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/003-e7e100cf33010282eadb59218305ecb2.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/004-063c82019383c5fed12564a3125b8976.jpg b/deprecated/old-docs-build/docs/assets/images/004-063c82019383c5fed12564a3125b8976.jpg
new file mode 100644
index 0000000..eb1ac13
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/004-063c82019383c5fed12564a3125b8976.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/004-4c8ee76b74750570c5a56690bd022766.jpg b/deprecated/old-docs-build/docs/assets/images/004-4c8ee76b74750570c5a56690bd022766.jpg
new file mode 100644
index 0000000..f9b5b9f
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/004-4c8ee76b74750570c5a56690bd022766.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/004-7f3a036fcfd42d7fe1a6ef7ab7f7a4ea.jpg b/deprecated/old-docs-build/docs/assets/images/004-7f3a036fcfd42d7fe1a6ef7ab7f7a4ea.jpg
new file mode 100644
index 0000000..ff00f62
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/004-7f3a036fcfd42d7fe1a6ef7ab7f7a4ea.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/004-7fbeea9dbd22ae641ce2345d92a2c53a.png b/deprecated/old-docs-build/docs/assets/images/004-7fbeea9dbd22ae641ce2345d92a2c53a.png
new file mode 100644
index 0000000..1bf3b25
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/004-7fbeea9dbd22ae641ce2345d92a2c53a.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/004-ae34f22f54818dfe150f6b9b07744bbc.jpg b/deprecated/old-docs-build/docs/assets/images/004-ae34f22f54818dfe150f6b9b07744bbc.jpg
new file mode 100644
index 0000000..97f2b0e
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/004-ae34f22f54818dfe150f6b9b07744bbc.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/004-ed010d2287d7f44a21fb50fd3e8918af.jpg b/deprecated/old-docs-build/docs/assets/images/004-ed010d2287d7f44a21fb50fd3e8918af.jpg
new file mode 100644
index 0000000..fe41820
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/004-ed010d2287d7f44a21fb50fd3e8918af.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/004-fa97a7bb6e8274cf0c9940eab63c37fe.jpg b/deprecated/old-docs-build/docs/assets/images/004-fa97a7bb6e8274cf0c9940eab63c37fe.jpg
new file mode 100644
index 0000000..28cb0cd
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/004-fa97a7bb6e8274cf0c9940eab63c37fe.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/004-fed1cd6f98337ad8c2a71c9d59339efa.jpg b/deprecated/old-docs-build/docs/assets/images/004-fed1cd6f98337ad8c2a71c9d59339efa.jpg
new file mode 100644
index 0000000..83e2e36
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/004-fed1cd6f98337ad8c2a71c9d59339efa.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/005-09b9043b60a7e157f84d756270f993b9.jpg b/deprecated/old-docs-build/docs/assets/images/005-09b9043b60a7e157f84d756270f993b9.jpg
new file mode 100644
index 0000000..dda239d
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/005-09b9043b60a7e157f84d756270f993b9.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/005-624205b9efb27d1e1315022fdbcd4dfd.jpg b/deprecated/old-docs-build/docs/assets/images/005-624205b9efb27d1e1315022fdbcd4dfd.jpg
new file mode 100644
index 0000000..1d34846
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/005-624205b9efb27d1e1315022fdbcd4dfd.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/005-7fd930bf5918339f48b89ad24ed61356.jpg b/deprecated/old-docs-build/docs/assets/images/005-7fd930bf5918339f48b89ad24ed61356.jpg
new file mode 100644
index 0000000..6d07f91
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/005-7fd930bf5918339f48b89ad24ed61356.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/005-8261e9d5635c419f644fa5923013fd95.jpg b/deprecated/old-docs-build/docs/assets/images/005-8261e9d5635c419f644fa5923013fd95.jpg
new file mode 100644
index 0000000..662e30b
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/005-8261e9d5635c419f644fa5923013fd95.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/005-8346cc8f71af047c4680fbae9752b632.png b/deprecated/old-docs-build/docs/assets/images/005-8346cc8f71af047c4680fbae9752b632.png
new file mode 100644
index 0000000..a3e7a72
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/005-8346cc8f71af047c4680fbae9752b632.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/005-b5a104764f561cb7257c8e8b5d8e69ab.png b/deprecated/old-docs-build/docs/assets/images/005-b5a104764f561cb7257c8e8b5d8e69ab.png
new file mode 100644
index 0000000..eee43c9
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/005-b5a104764f561cb7257c8e8b5d8e69ab.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/005-bf928ab6db3d372988ae7e642549c833.jpg b/deprecated/old-docs-build/docs/assets/images/005-bf928ab6db3d372988ae7e642549c833.jpg
new file mode 100644
index 0000000..47dd403
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/005-bf928ab6db3d372988ae7e642549c833.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/005-f25afe1a69de7091f65965db8442f40d.jpg b/deprecated/old-docs-build/docs/assets/images/005-f25afe1a69de7091f65965db8442f40d.jpg
new file mode 100644
index 0000000..ef6651e
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/005-f25afe1a69de7091f65965db8442f40d.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/006-8719a064b950651f84ba95f9ad9f1349.jpg b/deprecated/old-docs-build/docs/assets/images/006-8719a064b950651f84ba95f9ad9f1349.jpg
new file mode 100644
index 0000000..0aac090
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/006-8719a064b950651f84ba95f9ad9f1349.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/006-8dc421f29c0dbcad2342debf66d4eb32.jpg b/deprecated/old-docs-build/docs/assets/images/006-8dc421f29c0dbcad2342debf66d4eb32.jpg
new file mode 100644
index 0000000..2120ecc
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/006-8dc421f29c0dbcad2342debf66d4eb32.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/006-bdfccd7f337da89ca1d358cf3378a88a.jpg b/deprecated/old-docs-build/docs/assets/images/006-bdfccd7f337da89ca1d358cf3378a88a.jpg
new file mode 100644
index 0000000..08ad20b
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/006-bdfccd7f337da89ca1d358cf3378a88a.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/006-cd27bfc06a442a783b74b7aa586fb2c4.jpg b/deprecated/old-docs-build/docs/assets/images/006-cd27bfc06a442a783b74b7aa586fb2c4.jpg
new file mode 100644
index 0000000..587af91
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/006-cd27bfc06a442a783b74b7aa586fb2c4.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/006-d82f01f59619f1a762705ed84e059200.jpg b/deprecated/old-docs-build/docs/assets/images/006-d82f01f59619f1a762705ed84e059200.jpg
new file mode 100644
index 0000000..1701d86
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/006-d82f01f59619f1a762705ed84e059200.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/007-3d98613d3269feb47ae3f1ba2a464098.jpg b/deprecated/old-docs-build/docs/assets/images/007-3d98613d3269feb47ae3f1ba2a464098.jpg
new file mode 100644
index 0000000..fdb41c6
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/007-3d98613d3269feb47ae3f1ba2a464098.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/007-5307721d86e5069ac4c0d62fc18f86d8.jpg b/deprecated/old-docs-build/docs/assets/images/007-5307721d86e5069ac4c0d62fc18f86d8.jpg
new file mode 100644
index 0000000..4fcf4f4
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/007-5307721d86e5069ac4c0d62fc18f86d8.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/007-ac68faec4af496658cdeed48e1feed67.png b/deprecated/old-docs-build/docs/assets/images/007-ac68faec4af496658cdeed48e1feed67.png
new file mode 100644
index 0000000..2b8a821
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/007-ac68faec4af496658cdeed48e1feed67.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/007-b3c837fff0b375ea5ba41b75e9755e88.jpg b/deprecated/old-docs-build/docs/assets/images/007-b3c837fff0b375ea5ba41b75e9755e88.jpg
new file mode 100644
index 0000000..9aabcb1
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/007-b3c837fff0b375ea5ba41b75e9755e88.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/007-dcd3357c1f000f34c925baffe56ee997.png b/deprecated/old-docs-build/docs/assets/images/007-dcd3357c1f000f34c925baffe56ee997.png
new file mode 100644
index 0000000..6583e58
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/007-dcd3357c1f000f34c925baffe56ee997.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/007-e2631519df7f5cabb3ee8ee4782d487b.jpg b/deprecated/old-docs-build/docs/assets/images/007-e2631519df7f5cabb3ee8ee4782d487b.jpg
new file mode 100644
index 0000000..fc1053b
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/007-e2631519df7f5cabb3ee8ee4782d487b.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/008-0d8fd3623d884617f67f792fa92c55e7.png b/deprecated/old-docs-build/docs/assets/images/008-0d8fd3623d884617f67f792fa92c55e7.png
new file mode 100644
index 0000000..9079e88
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/008-0d8fd3623d884617f67f792fa92c55e7.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/008-42cdce2f4faa84d4d7c8ab61cee8f4bb.jpg b/deprecated/old-docs-build/docs/assets/images/008-42cdce2f4faa84d4d7c8ab61cee8f4bb.jpg
new file mode 100644
index 0000000..b0c71c5
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/008-42cdce2f4faa84d4d7c8ab61cee8f4bb.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/008-506041cbfdd95b89c2368d15091d5f8f.jpg b/deprecated/old-docs-build/docs/assets/images/008-506041cbfdd95b89c2368d15091d5f8f.jpg
new file mode 100644
index 0000000..176d2c4
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/008-506041cbfdd95b89c2368d15091d5f8f.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/008-b0ee74a0b67f743eb287c346a351646d.png b/deprecated/old-docs-build/docs/assets/images/008-b0ee74a0b67f743eb287c346a351646d.png
new file mode 100644
index 0000000..97d7c7b
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/008-b0ee74a0b67f743eb287c346a351646d.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/009-44a21b420a3f41e2b71d9d10b76a2cd4.jpg b/deprecated/old-docs-build/docs/assets/images/009-44a21b420a3f41e2b71d9d10b76a2cd4.jpg
new file mode 100644
index 0000000..e07097c
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/009-44a21b420a3f41e2b71d9d10b76a2cd4.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/009-9bac7c54dd6b559f29546b19e899d1c6.jpg b/deprecated/old-docs-build/docs/assets/images/009-9bac7c54dd6b559f29546b19e899d1c6.jpg
new file mode 100644
index 0000000..2ef0374
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/009-9bac7c54dd6b559f29546b19e899d1c6.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/009-ce55baf5a1d41ca489bb24ffdec2d6d5.png b/deprecated/old-docs-build/docs/assets/images/009-ce55baf5a1d41ca489bb24ffdec2d6d5.png
new file mode 100644
index 0000000..9a9558b
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/009-ce55baf5a1d41ca489bb24ffdec2d6d5.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/009-eb8aee2cfb9b2bd75e1551e6194d0045.png b/deprecated/old-docs-build/docs/assets/images/009-eb8aee2cfb9b2bd75e1551e6194d0045.png
new file mode 100644
index 0000000..b13e6e6
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/009-eb8aee2cfb9b2bd75e1551e6194d0045.png differ
diff --git a/deprecated/old-docs-build/docs/assets/images/010-19823a9ea9fb962cef52f055824dafe4.jpg b/deprecated/old-docs-build/docs/assets/images/010-19823a9ea9fb962cef52f055824dafe4.jpg
new file mode 100644
index 0000000..765e110
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/010-19823a9ea9fb962cef52f055824dafe4.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/010-b32818c34451ddecfd6b0a8103bad972.jpg b/deprecated/old-docs-build/docs/assets/images/010-b32818c34451ddecfd6b0a8103bad972.jpg
new file mode 100644
index 0000000..f20bbb6
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/010-b32818c34451ddecfd6b0a8103bad972.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/010-dff77be3a80a00a3bb11e6f049ba89dd.jpg b/deprecated/old-docs-build/docs/assets/images/010-dff77be3a80a00a3bb11e6f049ba89dd.jpg
new file mode 100644
index 0000000..742a69d
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/010-dff77be3a80a00a3bb11e6f049ba89dd.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/011-6c6325fe8db81be071bc42ca947da56e.jpg b/deprecated/old-docs-build/docs/assets/images/011-6c6325fe8db81be071bc42ca947da56e.jpg
new file mode 100644
index 0000000..e76cff2
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/011-6c6325fe8db81be071bc42ca947da56e.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/011-e1d8198702551e0512c13780cb7f6e19.jpg b/deprecated/old-docs-build/docs/assets/images/011-e1d8198702551e0512c13780cb7f6e19.jpg
new file mode 100644
index 0000000..0c5188a
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/011-e1d8198702551e0512c13780cb7f6e19.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/011-f8b5b5e454db30405ab330cc51005db1.jpg b/deprecated/old-docs-build/docs/assets/images/011-f8b5b5e454db30405ab330cc51005db1.jpg
new file mode 100644
index 0000000..52ebbdd
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/011-f8b5b5e454db30405ab330cc51005db1.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/012-66358757929d94954675898a4de78bba.jpg b/deprecated/old-docs-build/docs/assets/images/012-66358757929d94954675898a4de78bba.jpg
new file mode 100644
index 0000000..ca2294c
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/012-66358757929d94954675898a4de78bba.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/012-699132f0289f41241779be59f7f02da5.jpg b/deprecated/old-docs-build/docs/assets/images/012-699132f0289f41241779be59f7f02da5.jpg
new file mode 100644
index 0000000..1e2653d
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/012-699132f0289f41241779be59f7f02da5.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/013-87b66efdece1bb13be4376ff3e3286d6.jpg b/deprecated/old-docs-build/docs/assets/images/013-87b66efdece1bb13be4376ff3e3286d6.jpg
new file mode 100644
index 0000000..90a9c39
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/013-87b66efdece1bb13be4376ff3e3286d6.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/014-7ad768fc20ce6e9781bb3498c76a5047.jpg b/deprecated/old-docs-build/docs/assets/images/014-7ad768fc20ce6e9781bb3498c76a5047.jpg
new file mode 100644
index 0000000..30111c3
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/014-7ad768fc20ce6e9781bb3498c76a5047.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/015-6a5878e5cf340a85320aa91f9d48fc0f.jpg b/deprecated/old-docs-build/docs/assets/images/015-6a5878e5cf340a85320aa91f9d48fc0f.jpg
new file mode 100644
index 0000000..11b675f
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/015-6a5878e5cf340a85320aa91f9d48fc0f.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/016-4a6ae0573d30b4eaf2c9488f9d293cd9.jpg b/deprecated/old-docs-build/docs/assets/images/016-4a6ae0573d30b4eaf2c9488f9d293cd9.jpg
new file mode 100644
index 0000000..32a0b86
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/016-4a6ae0573d30b4eaf2c9488f9d293cd9.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/017-666ba3240286e6b2d1e60afbd7ef488b.jpg b/deprecated/old-docs-build/docs/assets/images/017-666ba3240286e6b2d1e60afbd7ef488b.jpg
new file mode 100644
index 0000000..bb06352
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/017-666ba3240286e6b2d1e60afbd7ef488b.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/018-6c87a682d72c84d09c7fc8bd81a45503.jpg b/deprecated/old-docs-build/docs/assets/images/018-6c87a682d72c84d09c7fc8bd81a45503.jpg
new file mode 100644
index 0000000..e0ede5f
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/018-6c87a682d72c84d09c7fc8bd81a45503.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/019-3da10fea53373ddc471ef6710f7a4206.jpg b/deprecated/old-docs-build/docs/assets/images/019-3da10fea53373ddc471ef6710f7a4206.jpg
new file mode 100644
index 0000000..7a99212
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/019-3da10fea53373ddc471ef6710f7a4206.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/020-e2b2ca9c2b342b4f039ce2a24cd7f514.jpg b/deprecated/old-docs-build/docs/assets/images/020-e2b2ca9c2b342b4f039ce2a24cd7f514.jpg
new file mode 100644
index 0000000..9a356bd
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/020-e2b2ca9c2b342b4f039ce2a24cd7f514.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/022-fe3bea07ec3fdbde5504672fbddc4098.jpg b/deprecated/old-docs-build/docs/assets/images/022-fe3bea07ec3fdbde5504672fbddc4098.jpg
new file mode 100644
index 0000000..2c84d4c
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/022-fe3bea07ec3fdbde5504672fbddc4098.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/023-ebf4f492e2976f0670093eb0702dc0df.jpg b/deprecated/old-docs-build/docs/assets/images/023-ebf4f492e2976f0670093eb0702dc0df.jpg
new file mode 100644
index 0000000..c995bdb
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/023-ebf4f492e2976f0670093eb0702dc0df.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/024-3e500b64e14be740bb749f43c05bf760.jpg b/deprecated/old-docs-build/docs/assets/images/024-3e500b64e14be740bb749f43c05bf760.jpg
new file mode 100644
index 0000000..acf10fb
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/024-3e500b64e14be740bb749f43c05bf760.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/025-dbf6bf07f5c73c31bf299557f9b04934.jpg b/deprecated/old-docs-build/docs/assets/images/025-dbf6bf07f5c73c31bf299557f9b04934.jpg
new file mode 100644
index 0000000..76e2f3d
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/025-dbf6bf07f5c73c31bf299557f9b04934.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/026-04a27daa04c64d5749c8e915f189ee27.jpg b/deprecated/old-docs-build/docs/assets/images/026-04a27daa04c64d5749c8e915f189ee27.jpg
new file mode 100644
index 0000000..8031032
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/026-04a27daa04c64d5749c8e915f189ee27.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/027-35b814b2d1adb978bbb8c368090ed0d2.jpg b/deprecated/old-docs-build/docs/assets/images/027-35b814b2d1adb978bbb8c368090ed0d2.jpg
new file mode 100644
index 0000000..117448a
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/027-35b814b2d1adb978bbb8c368090ed0d2.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/028-6fb3ad4c5218bc965764770b3a5c643c.jpg b/deprecated/old-docs-build/docs/assets/images/028-6fb3ad4c5218bc965764770b3a5c643c.jpg
new file mode 100644
index 0000000..d3b7de6
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/028-6fb3ad4c5218bc965764770b3a5c643c.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/029-218a81db3216b4db73776f0cd1108b67.jpg b/deprecated/old-docs-build/docs/assets/images/029-218a81db3216b4db73776f0cd1108b67.jpg
new file mode 100644
index 0000000..a1d2651
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/029-218a81db3216b4db73776f0cd1108b67.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/030-898b974be4ecae4a27795abee040f2b1.jpg b/deprecated/old-docs-build/docs/assets/images/030-898b974be4ecae4a27795abee040f2b1.jpg
new file mode 100644
index 0000000..0b67eb4
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/030-898b974be4ecae4a27795abee040f2b1.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/031-82e28bffea1b8ee5348763e1cca8abea.jpg b/deprecated/old-docs-build/docs/assets/images/031-82e28bffea1b8ee5348763e1cca8abea.jpg
new file mode 100644
index 0000000..6b192dc
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/031-82e28bffea1b8ee5348763e1cca8abea.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/032-a9a34f03854fc6bd6bc8e8e0091f8c5e.jpg b/deprecated/old-docs-build/docs/assets/images/032-a9a34f03854fc6bd6bc8e8e0091f8c5e.jpg
new file mode 100644
index 0000000..0ab4a4c
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/032-a9a34f03854fc6bd6bc8e8e0091f8c5e.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/033-8f5dceed634331ab23a723603672198a.jpg b/deprecated/old-docs-build/docs/assets/images/033-8f5dceed634331ab23a723603672198a.jpg
new file mode 100644
index 0000000..db977ef
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/033-8f5dceed634331ab23a723603672198a.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/034-eddd319ea950c3855b0ef006bda1520a.jpg b/deprecated/old-docs-build/docs/assets/images/034-eddd319ea950c3855b0ef006bda1520a.jpg
new file mode 100644
index 0000000..60c03bc
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/034-eddd319ea950c3855b0ef006bda1520a.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/035-3f2a61aca6bf94183a6dc7bd85ab9a3f.jpg b/deprecated/old-docs-build/docs/assets/images/035-3f2a61aca6bf94183a6dc7bd85ab9a3f.jpg
new file mode 100644
index 0000000..7ad6a8a
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/035-3f2a61aca6bf94183a6dc7bd85ab9a3f.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/036-a09333d67b803d7d065c29d4a5d3f5ee.jpg b/deprecated/old-docs-build/docs/assets/images/036-a09333d67b803d7d065c29d4a5d3f5ee.jpg
new file mode 100644
index 0000000..2f09a7f
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/036-a09333d67b803d7d065c29d4a5d3f5ee.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/037-013765d06627d37a9294a51110c6b254.jpeg b/deprecated/old-docs-build/docs/assets/images/037-013765d06627d37a9294a51110c6b254.jpeg
new file mode 100644
index 0000000..03b5c05
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/037-013765d06627d37a9294a51110c6b254.jpeg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/038-5feab98b3e27937021a3cd46c1df4f71.jpg b/deprecated/old-docs-build/docs/assets/images/038-5feab98b3e27937021a3cd46c1df4f71.jpg
new file mode 100644
index 0000000..597d50c
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/038-5feab98b3e27937021a3cd46c1df4f71.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/039-c76eb8fac7c1798c3dc4e434f500aba8.jpg b/deprecated/old-docs-build/docs/assets/images/039-c76eb8fac7c1798c3dc4e434f500aba8.jpg
new file mode 100644
index 0000000..d6ff559
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/039-c76eb8fac7c1798c3dc4e434f500aba8.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/040-2ff1cf7bb1e23ea9867b6aef88d74f68.jpg b/deprecated/old-docs-build/docs/assets/images/040-2ff1cf7bb1e23ea9867b6aef88d74f68.jpg
new file mode 100644
index 0000000..558a2ad
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/040-2ff1cf7bb1e23ea9867b6aef88d74f68.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/041-da276c0d104f5127fd2a83dbb4b79769.jpg b/deprecated/old-docs-build/docs/assets/images/041-da276c0d104f5127fd2a83dbb4b79769.jpg
new file mode 100644
index 0000000..c309278
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/041-da276c0d104f5127fd2a83dbb4b79769.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/042-0872e4cb73f9253e043f7f67d4136097.jpg b/deprecated/old-docs-build/docs/assets/images/042-0872e4cb73f9253e043f7f67d4136097.jpg
new file mode 100644
index 0000000..f4bb281
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/042-0872e4cb73f9253e043f7f67d4136097.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/043-98ff9ec104d12d90e5b03bad95b18af7.jpg b/deprecated/old-docs-build/docs/assets/images/043-98ff9ec104d12d90e5b03bad95b18af7.jpg
new file mode 100644
index 0000000..b017799
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/043-98ff9ec104d12d90e5b03bad95b18af7.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/044-bbc04b6dc348468cd72308c7e1922b46.jpg b/deprecated/old-docs-build/docs/assets/images/044-bbc04b6dc348468cd72308c7e1922b46.jpg
new file mode 100644
index 0000000..2289cb8
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/044-bbc04b6dc348468cd72308c7e1922b46.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/045-0d42d83889fe9c5ba570dd2bfa9c09e9.jpg b/deprecated/old-docs-build/docs/assets/images/045-0d42d83889fe9c5ba570dd2bfa9c09e9.jpg
new file mode 100644
index 0000000..efe673d
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/045-0d42d83889fe9c5ba570dd2bfa9c09e9.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/046-eacf6b0dfb43b3ce04bd20b3e31eb6f7.jpg b/deprecated/old-docs-build/docs/assets/images/046-eacf6b0dfb43b3ce04bd20b3e31eb6f7.jpg
new file mode 100644
index 0000000..d806344
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/046-eacf6b0dfb43b3ce04bd20b3e31eb6f7.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/047-6141e6576d5bc4270d1699f3ab08de87.jpg b/deprecated/old-docs-build/docs/assets/images/047-6141e6576d5bc4270d1699f3ab08de87.jpg
new file mode 100644
index 0000000..d4d533c
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/047-6141e6576d5bc4270d1699f3ab08de87.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/048-a24d8c355a1b81cfa1384a3db72ba384.jpg b/deprecated/old-docs-build/docs/assets/images/048-a24d8c355a1b81cfa1384a3db72ba384.jpg
new file mode 100644
index 0000000..d1bbf55
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/048-a24d8c355a1b81cfa1384a3db72ba384.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/049-f82cb1c0d409f16dc0ccb154656f63fa.jpg b/deprecated/old-docs-build/docs/assets/images/049-f82cb1c0d409f16dc0ccb154656f63fa.jpg
new file mode 100644
index 0000000..6c3340a
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/049-f82cb1c0d409f16dc0ccb154656f63fa.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/050-3869f07ba7a1c28e26e452b04254b632.jpg b/deprecated/old-docs-build/docs/assets/images/050-3869f07ba7a1c28e26e452b04254b632.jpg
new file mode 100644
index 0000000..b8918d5
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/050-3869f07ba7a1c28e26e452b04254b632.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/051-a65221a2f1ec4697daa4ce78d548e089.jpg b/deprecated/old-docs-build/docs/assets/images/051-a65221a2f1ec4697daa4ce78d548e089.jpg
new file mode 100644
index 0000000..6cc1745
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/051-a65221a2f1ec4697daa4ce78d548e089.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/052-132f153f2f9b628691897a6e6ba09faa.jpg b/deprecated/old-docs-build/docs/assets/images/052-132f153f2f9b628691897a6e6ba09faa.jpg
new file mode 100644
index 0000000..95cb24a
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/052-132f153f2f9b628691897a6e6ba09faa.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/053-bda67803b4b42bc649a44d931ebbf73f.jpg b/deprecated/old-docs-build/docs/assets/images/053-bda67803b4b42bc649a44d931ebbf73f.jpg
new file mode 100644
index 0000000..e6810ac
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/053-bda67803b4b42bc649a44d931ebbf73f.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/054-4cd98892a18341010d62f5563f170cb4.jpg b/deprecated/old-docs-build/docs/assets/images/054-4cd98892a18341010d62f5563f170cb4.jpg
new file mode 100644
index 0000000..47ef18b
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/054-4cd98892a18341010d62f5563f170cb4.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/055-062a2d24dda8d75d0476a6c9e3e3a2db.jpg b/deprecated/old-docs-build/docs/assets/images/055-062a2d24dda8d75d0476a6c9e3e3a2db.jpg
new file mode 100644
index 0000000..f9c0935
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/055-062a2d24dda8d75d0476a6c9e3e3a2db.jpg differ
diff --git a/deprecated/old-docs-build/docs/assets/images/docusaurus-plushie-banner-a60f7593abca1e3eef26a9afa244e4fb.jpeg b/deprecated/old-docs-build/docs/assets/images/docusaurus-plushie-banner-a60f7593abca1e3eef26a9afa244e4fb.jpeg
new file mode 100644
index 0000000..11bda09
Binary files /dev/null and b/deprecated/old-docs-build/docs/assets/images/docusaurus-plushie-banner-a60f7593abca1e3eef26a9afa244e4fb.jpeg differ
diff --git a/deprecated/old-docs-build/docs/assets/js/01a85c17.210c70c7.js b/deprecated/old-docs-build/docs/assets/js/01a85c17.210c70c7.js
new file mode 100644
index 0000000..ea9b5b1
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/01a85c17.210c70c7.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[8209],{4096:(e,t,a)=>{a.d(t,{in:()=>c,OU:()=>P,Ki:()=>A,kJ:()=>b,x:()=>l,e7:()=>m,J_:()=>f,Gx:()=>y});var s=a(6540),n=a(9532),i=a(6803),r=a(4848);function l(){const e=(0,i.A)(),t=e?.data?.blogMetadata;if(!t)throw new Error("useBlogMetadata() can't be called on the current route because the blog metadata could not be found in route context");return t}const o=s.createContext(null);function c({children:e,content:t,isBlogPostPage:a=!1}){const n=function({content:e,isBlogPostPage:t}){return(0,s.useMemo)((()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,toc:e.toc,isBlogPostPage:t})),[e,t])}({content:t,isBlogPostPage:a});return(0,r.jsx)(o.Provider,{value:n,children:e})}function m(){const e=(0,s.useContext)(o);if(null===e)throw new n.dV("BlogPostProvider");return e}var d=a(6025),u=a(4586);const g=e=>new Date(e).toISOString();function h(e){const t=e.map(x);return{author:1===t.length?t[0]:t}}function p(e,t,a){return e?{image:j({imageUrl:t(e,{absolute:!0}),caption:`title image for the blog post: ${a}`})}:{}}function b(e){const{siteConfig:t}=(0,u.A)(),{withBaseUrl:a}=(0,d.hH)(),{metadata:{blogDescription:s,blogTitle:n,permalink:i}}=e,r=`${t.url}${i}`;return{"@context":"https://schema.org","@type":"Blog","@id":r,mainEntityOfPage:r,headline:n,description:s,blogPost:e.items.map((e=>function(e,t,a){const{assets:s,frontMatter:n,metadata:i}=e,{date:r,title:l,description:o,lastUpdatedAt:c}=i,m=s.image??n.image,d=n.keywords??[],u=`${t.url}${i.permalink}`,b=c?g(c):void 0;return{"@type":"BlogPosting","@id":u,mainEntityOfPage:u,url:u,headline:l,name:l,description:o,datePublished:r,...b?{dateModified:b}:{},...h(i.authors),...p(m,a,l),...d?{keywords:d}:{}}}(e.content,t,a)))}}function f(){const e=l(),{assets:t,metadata:a}=m(),{siteConfig:s}=(0,u.A)(),{withBaseUrl:n}=(0,d.hH)(),{date:i,title:r,description:o,frontMatter:c,lastUpdatedAt:b}=a,f=t.image??c.image,x=c.keywords??[],j=b?g(b):void 0,N=`${s.url}${a.permalink}`;return{"@context":"https://schema.org","@type":"BlogPosting","@id":N,mainEntityOfPage:N,url:N,headline:r,name:r,description:o,datePublished:i,...j?{dateModified:j}:{},...h(a.authors),...p(f,n,r),...x?{keywords:x}:{},isPartOf:{"@type":"Blog","@id":`${s.url}${e.blogBasePath}`,name:e.blogTitle}}}function x(e){return{"@type":"Person",...e.name?{name:e.name}:{},...e.title?{description:e.title}:{},...e.url?{url:e.url}:{},...e.email?{email:e.email}:{},...e.imageURL?{image:e.imageURL}:{}}}function j({imageUrl:e,caption:t}){return{"@type":"ImageObject","@id":e,url:e,contentUrl:e,caption:t}}var N=a(6347),v=a(8774),C=a(1682),k=a(9169);function y(e){const{pathname:t}=(0,N.zy)();return(0,s.useMemo)((()=>e.filter((e=>function(e,t){return!(e.unlisted&&!(0,k.ys)(e.permalink,t))}(e,t)))),[e,t])}function A(e){const t=(0,C.$z)(e,(e=>`${new Date(e.date).getFullYear()}`)),a=Object.entries(t);return a.reverse(),a}function P({items:e,ulClassName:t,liClassName:a,linkClassName:s,linkActiveClassName:n}){return(0,r.jsx)("ul",{className:t,children:e.map((e=>(0,r.jsx)("li",{className:a,children:(0,r.jsx)(v.A,{isNavLink:!0,to:e.permalink,className:s,activeClassName:n,children:e.title})},e.permalink)))})}},6133:(e,t,a)=>{a.d(t,{A:()=>l});a(6540);var s=a(4164),n=a(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var r=a(4848);function l({permalink:e,label:t,count:a,description:l}){return(0,r.jsxs)(n.A,{rel:"tag",href:e,title:l,className:(0,s.A)(i.tag,a?i.tagWithCount:i.tagRegular),children:[t,a&&(0,r.jsx)("span",{children:a})]})}},8027:(e,t,a)=>{a.d(t,{A:()=>U});var s=a(6540),n=a(4164),i=a(1656),r=a(4581),l=a(1312),o=a(4096),c=a(6342),m=a(1107),d=a(4848);function u({year:e,yearGroupHeadingClassName:t,children:a}){return(0,d.jsxs)("div",{role:"group",children:[(0,d.jsx)(m.A,{as:"h3",className:t,children:e}),a]})}function g({items:e,yearGroupHeadingClassName:t,ListComponent:a}){if((0,c.p)().blog.sidebar.groupByYear){const s=(0,o.Ki)(e);return(0,d.jsx)(d.Fragment,{children:s.map((([e,s])=>(0,d.jsx)(u,{year:e,yearGroupHeadingClassName:t,children:(0,d.jsx)(a,{items:s})},e)))})}return(0,d.jsx)(a,{items:e})}const h=(0,s.memo)(g),p="sidebar_re4s",b="sidebarItemTitle_pO2u",f="sidebarItemList_Yudw",x="sidebarItem__DBe",j="sidebarItemLink_mo7H",N="sidebarItemLinkActive_I1ZP",v="yearGroupHeading_rMGB",C=({items:e})=>(0,d.jsx)(o.OU,{items:e,ulClassName:(0,n.A)(f,"clean-list"),liClassName:x,linkClassName:j,linkActiveClassName:N});function k({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,d.jsx)("aside",{className:"col col--3",children:(0,d.jsxs)("nav",{className:(0,n.A)(p,"thin-scrollbar"),"aria-label":(0,l.T)({id:"theme.blog.sidebar.navAriaLabel",message:"Blog recent posts navigation",description:"The ARIA label for recent posts in the blog sidebar"}),children:[(0,d.jsx)("div",{className:(0,n.A)(b,"margin-bottom--md"),children:e.title}),(0,d.jsx)(h,{items:t,ListComponent:C,yearGroupHeadingClassName:v})]})})}const y=(0,s.memo)(k);var A=a(5600);const P="yearGroupHeading_QT03",_=({items:e})=>(0,d.jsx)(o.OU,{items:e,ulClassName:"menu__list",liClassName:"menu__list-item",linkClassName:"menu__link",linkActiveClassName:"menu__link--active"});function w({sidebar:e}){const t=(0,o.Gx)(e.items);return(0,d.jsx)(h,{items:t,ListComponent:_,yearGroupHeadingClassName:P})}function B(e){return(0,d.jsx)(A.GX,{component:w,props:e})}const G=(0,s.memo)(B);function O({sidebar:e}){const t=(0,r.l)();return e?.items.length?"mobile"===t?(0,d.jsx)(G,{sidebar:e}):(0,d.jsx)(y,{sidebar:e}):null}function U(e){const{sidebar:t,toc:a,children:s,...r}=e,l=t&&t.items.length>0;return(0,d.jsx)(i.A,{...r,children:(0,d.jsx)("div",{className:"container margin-vert--lg",children:(0,d.jsxs)("div",{className:"row",children:[(0,d.jsx)(O,{sidebar:t}),(0,d.jsx)("main",{className:(0,n.A)("col",{"col--7":l,"col--9 col--offset-1":!l}),children:s}),a&&(0,d.jsx)("div",{className:"col col--2",children:a})]})})})}},9158:(e,t,a)=>{a.r(t),a.d(t,{default:()=>b});a(6540);var s=a(4164),n=a(1312);const i=()=>(0,n.T)({id:"theme.tags.tagsPageTitle",message:"Tags",description:"The title of the tag list page"});var r=a(5500),l=a(7559),o=a(8027),c=a(6133),m=a(1107);const d={tag:"tag_Nnez"};var u=a(4848);function g({letterEntry:e}){return(0,u.jsxs)("article",{children:[(0,u.jsx)(m.A,{as:"h2",id:e.letter,children:e.letter}),(0,u.jsx)("ul",{className:"padding--none",children:e.tags.map((e=>(0,u.jsx)("li",{className:d.tag,children:(0,u.jsx)(c.A,{...e})},e.permalink)))}),(0,u.jsx)("hr",{})]})}function h({tags:e}){const t=function(e){const t={};return Object.values(e).forEach((e=>{const a=function(e){return e[0].toUpperCase()}(e.label);t[a]??=[],t[a].push(e)})),Object.entries(t).sort((([e],[t])=>e.localeCompare(t))).map((([e,t])=>({letter:e,tags:t.sort(((e,t)=>e.label.localeCompare(t.label)))})))}(e);return(0,u.jsx)("section",{className:"margin-vert--lg",children:t.map((e=>(0,u.jsx)(g,{letterEntry:e},e.letter)))})}var p=a(1463);function b({tags:e,sidebar:t}){const a=i();return(0,u.jsxs)(r.e3,{className:(0,s.A)(l.G.wrapper.blogPages,l.G.page.blogTagsListPage),children:[(0,u.jsx)(r.be,{title:a}),(0,u.jsx)(p.A,{tag:"blog_tags_list"}),(0,u.jsxs)(o.A,{sidebar:t,children:[(0,u.jsx)(m.A,{as:"h1",children:a}),(0,u.jsx)(h,{tags:e})]})]})}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/033da2f6.2f396644.js b/deprecated/old-docs-build/docs/assets/js/033da2f6.2f396644.js
new file mode 100644
index 0000000..ed11676
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/033da2f6.2f396644.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2248],{1888:(e,n,o)=>{o.d(n,{A:()=>a});const a=o.p+"assets/images/001-67c7acb3213beb7aee3f4e35f1956226.png"},5089:(e,n,o)=>{o.r(n),o.d(n,{assets:()=>c,contentTitle:()=>t,default:()=>u,frontMatter:()=>i,metadata:()=>a,toc:()=>d});const a=JSON.parse('{"id":"backend/node/tutorials/intro_web/Lab17BD/readme","title":"Interacci\xf3n con la Base de Datos","description":"Para este laboratorio vamos a comenzar con el trabajo de conectarnos con una base de datos relacional.","source":"@site/docs/backend/node/tutorials/intro_web/Lab17BD/readme.md","sourceDirName":"backend/node/tutorials/intro_web/Lab17BD","slug":"/backend/node/tutorials/intro_web/Lab17BD/","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab17BD/","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/backend/node/tutorials/intro_web/Lab17BD/readme.md","tags":[],"version":"current","sidebarPosition":17,"frontMatter":{"sidebar_position":17},"sidebar":"tutorialSidebar","previous":{"title":"Sesiones","permalink":"/docs/docs/backend/node/tutorials/intro_web/LAB14Sesiones/"},"next":{"title":"Autenticaci\xf3n","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab18Autenticacion/"}}');var s=o(4848),r=o(8453);const i={sidebar_position:17},t="Interacci\xf3n con la Base de Datos",c={},d=[{value:"Funciones as\xedncronas",id:"funciones-as\xedncronas",level:2}];function l(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"interacci\xf3n-con-la-base-de-datos",children:"Interacci\xf3n con la Base de Datos"})}),"\n",(0,s.jsx)(n.p,{children:"Para este laboratorio vamos a comenzar con el trabajo de conectarnos con una base de datos relacional."}),"\n",(0,s.jsx)(n.p,{children:"El objetivo de este laboratorio es demostrar como realizar f\xe1cilmente la conexi\xf3n y poder interactuar con una base de datos."}),"\n",(0,s.jsx)(n.p,{children:"El laboratorio asume varios conocimientos que deben de tenerse a este punto por lo que si tienes dudas previo a, o desconoces de los temas te invito a que los revises:"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Pre-requisitos"}),":"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Conocimiento general de bases de datos relacionales."}),"\n",(0,s.jsx)(n.li,{children:"Conocimiento b\xe1sico del lenguaje SQL."}),"\n",(0,s.jsx)(n.li,{children:"Tener instalado una versi\xf3n de MariaDB en tu computadora."}),"\n",(0,s.jsx)(n.li,{children:"De preferencia tener una peque\xf1a base de datos para poder hacer la conexi\xf3n y pruebas fuera del laboratorio."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Si bien estaremos utilizando MariaDB para la realizaci\xf3n de este laboratorio es importante mencionar que solo es por usar una base de datos, en la realidad el conocimiento aplicado puede aplicarse a cualquier tipo de base de datos relacional, solo deber\xedas poder encontrar el conector correspondiente a tu base de datos de elecci\xf3n."}),"\n",(0,s.jsx)(n.h1,{id:"conexi\xf3n-con-mariadb",children:"Conexi\xf3n con MariaDB"}),"\n",(0,s.jsxs)(n.p,{children:["Vamos a iniciar un nuevo proyecto con ",(0,s.jsx)(n.strong,{children:"npm init"})," y vamos a instalar las librer\xedas de express y el body-parser, as\xed como vamos a agregar una nueva librer\xeda."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"npm i mariadb\n"})}),"\n",(0,s.jsx)(n.p,{children:"Una vez tengamos nuestro package.json preparado vamos a crear la plantilla base de nuestro servidor."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"const http = require('http');\nconst express = require('express');\nconst path = require('path');\nconst fs = require('fs');\nconst app = express();\n\nconst bodyParser = require('body-parser');\napp.use(bodyParser.urlencoded({extended: false}));\napp.use(express.static(path.join(__dirname, 'public')));\n\napp.get('/', (request, response, next) => {\n response.setHeader('Content-Type', 'text/plain');\n response.send(\"Hola Mundo\");\n response.end(); \n});\n\nconst server = http.createServer( (request, response) => { \n console.log(request.url);\n});\napp.listen(3000);\n"})}),"\n",(0,s.jsx)(n.p,{children:"Y vamos a correrlo como siempre:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"pm2 start index.js --watch\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Tambi\xe9n no olvides que si trabajas con un repositorio debes agregar el archivo ",(0,s.jsx)(n.strong,{children:".gitignore"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Ahora bien, vamos a comenzar creando nuestra configuraci\xf3n para MariaDB, de momento lo realizar\xe9 directamente desde la ra\xedz del proyecto, pero lo mismo que estoy haciendo debe hacer desde la carpeta de modelos de tu proyecto, esto cuando estemos trabajando con MVC."}),"\n",(0,s.jsxs)(n.p,{children:["Para comenzar con el c\xf3digo vamos a crear una conexi\xf3n con la base de datos, sobre la ruta ",(0,s.jsx)(n.strong,{children:"/"})," vamos a a\xf1adir lo siguiente:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'const mariadb = require("mariadb");\nconst pool = mariadb.createPool({\n host:"localhost",\n user:"root",\n password:"root",\n connectionLimit:5\n});\n\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Deber\xe1s sustituir los valores de ",(0,s.jsx)(n.strong,{children:"user"})," y ",(0,s.jsx)(n.strong,{children:"pass"})," por los que hayas configurado en tu base de datos al momento de instalarla. Recuerda que para entornos de producci\xf3n lo ideal es tener contrase\xf1as largas para a\xf1adirle dificultad al acceso de la base de datos."]}),"\n",(0,s.jsx)(n.p,{children:"Ahora antes de hacer cualquier cosa vamos a a\xf1adir unos datos dentro de MariaDB para poder probarlo."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"lab_17",src:o(1888).A+"",width:"578",height:"293"})}),"\n",(0,s.jsxs)(n.p,{children:["La base de datos que vamos a crear se llamar\xe1 ",(0,s.jsx)(n.strong,{children:"test"})]}),"\n",(0,s.jsx)(n.p,{children:"Desde tu DBMS favorito ejecuta los siguientes comandos:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"CREATE DATABASE IF NOT EXISTS test;\n\nUSE test;\n\nCREATE TABLE IF NOT EXISTS books (\n BookID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, \n Title VARCHAR(100) NOT NULL, \n SeriesID INT, AuthorID INT);\n\nCREATE TABLE IF NOT EXISTS authors \n(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT);\n\nCREATE TABLE IF NOT EXISTS series \n(id INT NOT NULL PRIMARY KEY AUTO_INCREMENT);\n\nINSERT INTO books (Title,SeriesID,AuthorID) \nVALUES('The Fellowship of the Ring',1,1), \n ('The Two Towers',1,1), ('The Return of the King',1,1), \n ('The Sum of All Men',2,2), ('Brotherhood of the Wolf',2,2), \n ('Wizardborn',2,2), ('The Hobbbit',0,1);\n"})}),"\n",(0,s.jsx)(n.p,{children:"Para validar que se ejecut\xf3 correctamente la creaci\xf3n de la base de datos revisa con lo siguiente:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"SHOW TABLES;\n"})}),"\n",(0,s.jsx)(n.p,{children:"El resultado deber\xeda ser algo como lo siguiente:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"+----------------+\n| Tables_in_test |\n+----------------+\n| authors |\n| books |\n| series |\n+----------------+\n"})}),"\n",(0,s.jsx)(n.p,{children:"Tambi\xe9n podemos ver el contenido de books por ejemplo:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"DESCRIBE books;\n"})}),"\n",(0,s.jsx)(n.p,{children:"El resultado ser\xeda:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"+----------+--------------+------+-----+---------+----------------+\n| Field | Type | Null | Key | Default | Extra |\n+----------+--------------+------+-----+---------+----------------+\n| BookID | int(11) | NO | PRI | NULL | auto_increment |\n| Title | varchar(100) | NO | | NULL | |\n| SeriesID | int(11) | YES | | NULL | |\n| AuthorID | int(11) | YES | | NULL | |\n+----------+--------------+------+-----+---------+----------------+\n"})}),"\n",(0,s.jsx)(n.p,{children:"Por \xfaltimo hagamos una consulta de prueba para estar seguros que todo funciona correctamente:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"SELECT * FROM books;\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"+--------+----------------------------+----------+----------+\n| BookID | Title | SeriesID | AuthorID |\n+--------+----------------------------+----------+----------+\n| 1 | The Fellowship of the Ring | 1 | 1 |\n| 2 | The Two Towers | 1 | 1 |\n| 3 | The Return of the King | 1 | 1 |\n| 4 | The Sum of All Men | 2 | 2 |\n| 5 | Brotherhood of the Wolf | 2 | 2 |\n| 6 | Wizardborn | 2 | 2 |\n| 7 | The Hobbbit | 0 | 1 |\n+--------+----------------------------+----------+----------+\n"})}),"\n",(0,s.jsx)(n.h2,{id:"funciones-as\xedncronas",children:"Funciones as\xedncronas"}),"\n",(0,s.jsxs)(n.p,{children:["Ahora vamos a regresar a nuestro archivo ",(0,s.jsx)(n.strong,{children:"index.js"}),", en donde vamos a crear una url que se llame ",(0,s.jsx)(n.strong,{children:"/test_db"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Para ello vamos a introducir lo siguiente:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"app.get('/test_db', (request, response, next) => {\n let conn;\n\n try{\n conn = await pool.getConnection();\n const rows = await conn.query(\"SELECT * FROM books\")\n console.log(rows);\n const jsonS = JSON.stringify(rows);\n response.writeHead(200, {'Content-type':'text/html'});\n response.end(jsonS);\n }catch(e){\n\n }\n});\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Antes de guardar ",(0,s.jsx)(n.strong,{children:"index.js"})," es probable que veas 2 errores en el c\xf3digo, y si no lo remarca tu editor, no te preocupes aqu\xed te digo que el c\xf3digo no va a funcionar."]}),"\n",(0,s.jsxs)(n.p,{children:["Lo que sucede es la forma en que estamos a\xf1adiendo la funci\xf3n que resuelve ",(0,s.jsx)(n.strong,{children:"/test_db"}),", si podemos simplificar la funci\xf3n tendr\xedamos lo siguiente:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"(request, response, next) => {})\n"})}),"\n",(0,s.jsx)(n.p,{children:"La anterior es lo que ya vimos como una funci\xf3n an\xf3nima, pues al momento no le hemos generado un nombre, y como ya hemos visto es la forma en que podemos resolver las funciones dentro de nuestras rutas."}),"\n",(0,s.jsx)(n.p,{children:"Ahora debemos entender un poco como funcionan las Bases de Datos. Independientemente de donde nos conectemos sea en la misma computadora con localhost o a otro servidor, el tiempo que tarda la base de datos en responder es desconocido, y esto es importante que lo entendamos pues al conectarte a cualquier otro espacio no controlamos este tiempo. Esto pueden ser segundos o milisegundos pero al final no es un tiempo constante."}),"\n",(0,s.jsx)(n.p,{children:"Cuando ejecutamos un programa la secuencia de instrucciones del c\xf3digo son secuenciales, pero el problema de ciertas llamadas a conexiones externas al no controlar el tiempo de respuesta puede provocar que se tarde mucho en responder."}),"\n",(0,s.jsx)(n.p,{children:"En programaci\xf3n general, simplemente escribimos el c\xf3digo y no nos preocupamos por esto pero ya en un desarrollo m\xe1s avanzado como desarrollo web y conexi\xf3n entre servidores veremos que tenemos que adecuarnos a otro estilo de programaci\xf3n."}),"\n",(0,s.jsx)(n.p,{children:"Aqu\xed es donde surgen las funciones as\xedncronas las cuales su objetivo es espec\xedficamente resolver el conflicto de tiempo de espera con c\xf3digo que puede tardar mucho tiempo, algunas acciones que pueden generar un tiempo de espera por mencionar algunas ser\xeda:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Conexi\xf3n o ejecuci\xf3n de comandos en una BD."}),"\n",(0,s.jsx)(n.li,{children:"Escritura/lectura de un archivo"}),"\n",(0,s.jsx)(n.li,{children:"Conexi\xf3n a internet"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Pueden existir m\xe1s casos pero al menos estos son los m\xe1s representativos."}),"\n",(0,s.jsx)(n.p,{children:"Ahora, para definir una funci\xf3n as\xedncrona es muy sencillo, vamos a actualizar nuestra funci\xf3n a lo siguiente:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"async (request, response, next) => {})\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Dentro de esto vamos a tener que agregar la palabra reservada ",(0,s.jsx)(n.strong,{children:"async"})," que le permitir\xe1 a nuestra funci\xf3n saber que puede ejecutar c\xf3digo as\xedncrono."]}),"\n",(0,s.jsx)(n.p,{children:"Si volvemos el c\xf3digo completo de nuestra ruta se ver\xeda de la siguiente manera:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"app.get('/test_db', async(request, response, next) => {\n let conn;\n\n try{\n conn = await pool.getConnection();\n const rows = await conn.query(\"SELECT * FROM books\");\n console.log(rows);\n const jsonS = JSON.stringify(rows);\n response.writeHead(200, {'Content-type':'text/html'});\n response.end(jsonS);\n }catch(e){\n\n }\n});\n"})}),"\n",(0,s.jsx)(n.p,{children:"Veremos entonces, que ahora s\xed se resuelve el conflicto, y este ven\xeda espec\xedficamente en la secci\xf3n:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:'await conn.query("SELECT * FROM books");\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Aqu\xed estamos haciendo uso de la palabra reservada ",(0,s.jsx)(n.strong,{children:"await"}),", para poder usar el await, es necesario hacer uso del async, por tanto se complementan la una a la otra en el formato de ",(0,s.jsx)(n.strong,{children:"async/await"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Si bien al usar el async, estamos diciendo que nuestra funci\xf3n es as\xedncrona, puede o no utilizarse el await internamente."}),"\n",(0,s.jsxs)(n.p,{children:["Como resultado y conclusi\xf3n te recomiendo que todas las funciones de tu proyecto las definas como ",(0,s.jsx)(n.strong,{children:"async"})," para que en cualquier momento puedas hacer uso del ",(0,s.jsx)(n.strong,{children:"await"})," si lo necesitas, y en caso de que no, pues tu c\xf3digo ya estar\xeda preparado para ello."]}),"\n",(0,s.jsxs)(n.p,{children:["Tambi\xe9n debemos resaltar que sucede cuando el c\xf3digo llega a la l\xednea del ",(0,s.jsx)(n.strong,{children:"await"}),", lo que pasa es que como la palabra reservada nos dice, el c\xf3digo entra en un modo espera a que se ejecute la instrucci\xf3n externa, en este caso el query a la base de datos y hasta que no termine podemos continuar donde nos quedamos."]}),"\n",(0,s.jsxs)(n.p,{children:["Esto nos permite mantener un c\xf3digo secuencial para ello. Algo que mencionaremos pero no implementaremos, es que antes de que existiera ",(0,s.jsx)(n.strong,{children:"async/await"})," utiliz\xe1bamos algo conocido como promesas y callbacks, en ocasiones se puede utilizar pero el c\xf3digo se vuelve m\xe1s dif\xedcil de leer."]}),"\n",(0,s.jsxs)(n.p,{children:["Sin embargo todo el c\xf3digo en formato de promesas puede convertirse en ",(0,s.jsx)(n.strong,{children:"async/await"})," y viceversa."]}),"\n",(0,s.jsxs)(n.p,{children:["Ahora s\xed vamos a guardar el archivo ",(0,s.jsx)(n.strong,{children:"index.js"}),". Vamos a ver en el navegador el resultado:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"lab_17",src:o(9011).A+"",width:"1920",height:"425"})}),"\n",(0,s.jsx)(n.p,{children:"\xc9xito, hemos conectado nuestro proyecto con nuestra base de datos y ahora podemos ejecutar comandos a la base de datos."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"Siguientes pasos:"})}),"\n",(0,s.jsx)(n.p,{children:"Lo siguiente es que pases tu conexi\xf3n de este laboratorio a tu modelo usando la arquitectura Modelo-Vista-Controlador."}),"\n",(0,s.jsx)(n.p,{children:"Tambi\xe9n necesitas cuidar mucho como proteger la informaci\xf3n, pues existen varios puntos de seguridad a cuidar, el m\xe1s elemental es la inyecci\xf3n de sql, la cual te pido investigues y para validar el conocimiento de este laboratorio, tomes conciencia de la implicaci\xf3n que tendr\xeda."}),"\n",(0,s.jsx)(n.p,{children:"Hemos abierto una puerta a mucho poder dentro de nuestras aplicaciones web, pero eso tambi\xe9n nos lleva a tener que proteger mucho la informaci\xf3n que estamos obteniendo."}),"\n",(0,s.jsxs)(n.p,{children:["La documentaci\xf3n que puedes consultar para entender que otros comandos contiene la librer\xeda de mariadb puedes encontrarlos ",(0,s.jsx)(n.a,{href:"https://mariadb.com/docs/server/connect/programming-languages/nodejs/",children:"desde aqu\xed."})]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.a,{target:"_blank","data-noBrokenLinkCheck":!0,href:o(9540).A+"",children:"Ver ejemplo completo"})})]})}function u(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(l,{...e})}):l(e)}},8453:(e,n,o)=>{o.d(n,{R:()=>i,x:()=>t});var a=o(6540);const s={},r=a.createContext(s);function i(e){const n=a.useContext(r);return a.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function t(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),a.createElement(r.Provider,{value:n},e.children)}},9011:(e,n,o)=>{o.d(n,{A:()=>a});const a=o.p+"assets/images/002-977da9cd060630ebc7e572ae1aacf534.jpg"},9540:(e,n,o)=>{o.d(n,{A:()=>a});const a=o.p+"assets/files/test-project-ad8e257bd3935517b3f0025decf0410d.zip"}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/0e384e19.d99ff741.js b/deprecated/old-docs-build/docs/assets/js/0e384e19.d99ff741.js
new file mode 100644
index 0000000..ba1e917
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/0e384e19.d99ff741.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3976],{2053:(e,a,n)=>{n.r(a),n.d(a,{assets:()=>t,contentTitle:()=>l,default:()=>u,frontMatter:()=>s,metadata:()=>i,toc:()=>c});const i=JSON.parse('{"id":"intro","title":"TC2005B","description":"Este repositorio esta pensado como centro de informaci\xf3n y documentaci\xf3n sobre los temas vistos en clase.","source":"@site/docs/intro.md","sourceDirName":".","slug":"/intro","permalink":"/docs/docs/intro","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/intro.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"sidebar_position":0},"sidebar":"tutorialSidebar","next":{"title":"Desarrollo Web","permalink":"/docs/docs/backend/"}}');var o=n(4848),r=n(8453);const s={sidebar_position:0},l="TC2005B",t={},c=[{value:"\xbfC\xf3mo esta organizada la documentaci\xf3n?",id:"c\xf3mo-esta-organizada-la-documentaci\xf3n",level:2},{value:"Recomendaciones",id:"recomendaciones",level:2},{value:"Git para el d\xeda a d\xeda",id:"git-para-el-d\xeda-a-d\xeda",level:3},{value:"Al inicio del d\xeda o al iniciar trabajo",id:"al-inicio-del-d\xeda-o-al-iniciar-trabajo",level:4},{value:"Al empezar el d\xeda",id:"al-empezar-el-d\xeda",level:3},{value:"Al empezar una nueva funcionalidad",id:"al-empezar-una-nueva-funcionalidad",level:3},{value:"Al final del d\xeda o terminar trabajo",id:"al-final-del-d\xeda-o-terminar-trabajo",level:4},{value:"Eliminar branches locales que ya no existan en el repo remoto",id:"eliminar-branches-locales-que-ya-no-existan-en-el-repo-remoto",level:3}];function d(e){const a={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,r.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(a.header,{children:(0,o.jsx)(a.h1,{id:"tc2005b",children:"TC2005B"})}),"\n",(0,o.jsx)(a.p,{children:"Este repositorio esta pensado como centro de informaci\xf3n y documentaci\xf3n sobre los temas vistos en clase."}),"\n",(0,o.jsxs)(a.ul,{children:["\n",(0,o.jsx)(a.li,{children:(0,o.jsx)(a.a,{href:"https://img.shields.io/badge/HTML-orange.svg?logo=html5&style=flat",children:"HTML"})}),"\n",(0,o.jsx)(a.li,{children:(0,o.jsx)(a.a,{href:"https://img.shields.io/badge/CSS-red.svg?logo=css3&style=flat",children:"CSS"})}),"\n",(0,o.jsx)(a.li,{children:(0,o.jsx)(a.a,{href:"https://img.shields.io/badge/javascript-blue.svg?logo=javascript&style=flat",children:"JS"})}),"\n",(0,o.jsx)(a.li,{children:(0,o.jsx)(a.a,{href:"https://img.shields.io/badge/NodeJS-green.svg?logo=node.js&style=flat",children:"NodeJS"})}),"\n",(0,o.jsx)(a.li,{children:(0,o.jsx)(a.a,{href:"https://img.shields.io/badge/MariaDB-purple.svg?logo=mariadb&style=flat",children:"MariaDB"})}),"\n",(0,o.jsx)(a.li,{children:(0,o.jsx)(a.a,{href:"https://img.shields.io/badge/Git-black.svg?logo=git&style=flat",children:"Git"})}),"\n"]}),"\n",(0,o.jsx)(a.h2,{id:"c\xf3mo-esta-organizada-la-documentaci\xf3n",children:"\xbfC\xf3mo esta organizada la documentaci\xf3n?"}),"\n",(0,o.jsxs)(a.p,{children:[(0,o.jsx)(a.a,{href:"#",children:"Tutoriales"})," - te toman de la mano a trav\xe9s de una serie de pasos para crear un proyecto o entender un tema completo. Inicia aqu\xed si eres nuevo al tema que est\xe1s buscando."]}),"\n",(0,o.jsx)(a.p,{children:"Gu\xedas por Tema - discuten temas clave o particulares y conceptos a un alto nivel y proveen informaci\xf3n de trasfondo y explicaci\xf3n."}),"\n",(0,o.jsxs)(a.p,{children:[(0,o.jsx)(a.a,{href:"#",children:"Gu\xedas de Referencia"})," - contienen referencias t\xe9cnicas para el tema revisado. Describen como funciona y como usarlo pero asumen que tienes un conocimiento b\xe1sico de entendimiento para los conceptos clave."]}),"\n",(0,o.jsxs)(a.p,{children:[(0,o.jsx)(a.a,{href:"#",children:"Gu\xedas How-To son recetas"})," - Te gu\xedan a trav\xe9s de pasos para resolver problemas concretos y casos de uso. Son m\xe1s avanzados que los tutoriales y asumen que tienes conocimiento del tema buscado."]}),"\n",(0,o.jsx)(a.h2,{id:"recomendaciones",children:"Recomendaciones"}),"\n",(0,o.jsx)(a.h3,{id:"git-para-el-d\xeda-a-d\xeda",children:"Git para el d\xeda a d\xeda"}),"\n",(0,o.jsx)(a.p,{children:"Sigue la convenci\xf3n de ramas que vayas estableciendo en tus equipos de trabajo, una vez que la tengas te recomiendo la siguiente convenci\xf3n de comandos para un d\xeda de trabajo."}),"\n",(0,o.jsx)(a.h4,{id:"al-inicio-del-d\xeda-o-al-iniciar-trabajo",children:"Al inicio del d\xeda o al iniciar trabajo"}),"\n",(0,o.jsx)(a.pre,{children:(0,o.jsx)(a.code,{children:"git checkout //Bajar los nuevos branches remotos\ngit pull origin\n"})}),"\n",(0,o.jsx)(a.h3,{id:"al-empezar-el-d\xeda",children:"Al empezar el d\xeda"}),"\n",(0,o.jsx)(a.pre,{children:(0,o.jsx)(a.code,{children:"git pull origin\n"})}),"\n",(0,o.jsx)(a.h3,{id:"al-empezar-una-nueva-funcionalidad",children:"Al empezar una nueva funcionalidad"}),"\n",(0,o.jsx)(a.pre,{children:(0,o.jsx)(a.code,{children:"git pull origin\ngit checkout -b {{nombre-rama}}\n"})}),"\n",(0,o.jsx)(a.h4,{id:"al-final-del-d\xeda-o-terminar-trabajo",children:"Al final del d\xeda o terminar trabajo"}),"\n",(0,o.jsx)(a.pre,{children:(0,o.jsx)(a.code,{children:'git pull origin\ngit add -A\ngit commit -m "Mensaje significativo de lo que incluye el commit"\ngit push origin\n'})}),"\n",(0,o.jsx)(a.h3,{id:"eliminar-branches-locales-que-ya-no-existan-en-el-repo-remoto",children:"Eliminar branches locales que ya no existan en el repo remoto"}),"\n",(0,o.jsx)(a.pre,{children:(0,o.jsx)(a.code,{children:'git fetch\ngit remote prune origin\ngit branch | grep -v "main" | xargs git branch -D\n'})})]})}function u(e={}){const{wrapper:a}={...(0,r.R)(),...e.components};return a?(0,o.jsx)(a,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},8453:(e,a,n)=>{n.d(a,{R:()=>s,x:()=>l});var i=n(6540);const o={},r=i.createContext(o);function s(e){const a=i.useContext(r);return i.useMemo((function(){return"function"==typeof e?e(a):{...a,...e}}),[a,e])}function l(e){let a;return a=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),i.createElement(r.Provider,{value:a},e.children)}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/14eb3368.f622b146.js b/deprecated/old-docs-build/docs/assets/js/14eb3368.f622b146.js
new file mode 100644
index 0000000..978e585
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/14eb3368.f622b146.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6969],{477:(e,s,n)=>{n.r(s),n.d(s,{default:()=>C});n(6540);var t=n(5500),r=n(6972),a=n(6025),i=n(4164),c=n(8774),l=n(5846),o=n(6654),d=n(1312),u=n(1107);const m={cardContainer:"cardContainer_fWXF",cardTitle:"cardTitle_rnsV",cardDescription:"cardDescription_PWke"};var h=n(4848);function b({className:e,href:s,children:n}){return(0,h.jsx)(c.A,{href:s,className:(0,i.A)("card padding--lg",m.cardContainer,e),children:n})}function x({className:e,href:s,icon:n,title:t,description:r}){return(0,h.jsxs)(b,{href:s,className:e,children:[(0,h.jsxs)(u.A,{as:"h2",className:(0,i.A)("text--truncate",m.cardTitle),title:t,children:[n," ",t]}),r&&(0,h.jsx)("p",{className:(0,i.A)("text--truncate",m.cardDescription),title:r,children:r})]})}function p({item:e}){const s=(0,r.Nr)(e),n=function(){const{selectMessage:e}=(0,l.W)();return s=>e(s,(0,d.T)({message:"1 item|{count} items",id:"theme.docs.DocCard.categoryDescription.plurals",description:"The default description for a category card in the generated index about how many items this category includes"},{count:s}))}();return s?(0,h.jsx)(x,{className:e.className,href:s,icon:"\ud83d\uddc3\ufe0f",title:e.label,description:e.description??n(e.items.length)}):null}function v({item:e}){const s=(0,o.A)(e.href)?"\ud83d\udcc4\ufe0f":"\ud83d\udd17",n=(0,r.cC)(e.docId??void 0);return(0,h.jsx)(x,{className:e.className,href:e.href,icon:s,title:e.label,description:e.description??n?.description})}function g({item:e}){switch(e.type){case"link":return(0,h.jsx)(v,{item:e});case"category":return(0,h.jsx)(p,{item:e});default:throw new Error(`unknown item type ${JSON.stringify(e)}`)}}const f={docCardListItem:"docCardListItem_W1sv"};function j({className:e}){const s=(0,r.a4)();return(0,h.jsx)(N,{items:s,className:e})}function A({item:e}){return(0,h.jsx)("article",{className:(0,i.A)(f.docCardListItem,"col col--6"),children:(0,h.jsx)(g,{item:e})})}function N(e){const{items:s,className:n}=e;if(!s)return(0,h.jsx)(j,{...e});const t=(0,r.d1)(s);return(0,h.jsx)("section",{className:(0,i.A)("row",n),children:t.map(((e,s)=>(0,h.jsx)(A,{item:e},s)))})}var L=n(7719),_=n(1878),T=n(4267),k=n(594);const y={generatedIndexPage:"generatedIndexPage_vN6x",title:"title_kItE"};function w({categoryGeneratedIndex:e}){return(0,h.jsx)(t.be,{title:e.title,description:e.description,keywords:e.keywords,image:(0,a.Ay)(e.image)})}function I({categoryGeneratedIndex:e}){const s=(0,r.$S)();return(0,h.jsxs)("div",{className:y.generatedIndexPage,children:[(0,h.jsx)(_.A,{}),(0,h.jsx)(k.A,{}),(0,h.jsx)(T.A,{}),(0,h.jsxs)("header",{children:[(0,h.jsx)(u.A,{as:"h1",className:y.title,children:e.title}),e.description&&(0,h.jsx)("p",{children:e.description})]}),(0,h.jsx)("article",{className:"margin-top--lg",children:(0,h.jsx)(N,{items:s.items,className:y.list})}),(0,h.jsx)("footer",{className:"margin-top--md",children:(0,h.jsx)(L.A,{previous:e.navigation.previous,next:e.navigation.next})})]})}function C(e){return(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(w,{...e}),(0,h.jsx)(I,{...e})]})}},594:(e,s,n)=>{n.d(s,{A:()=>j});n(6540);var t=n(4164),r=n(7559),a=n(6972),i=n(9169),c=n(8774),l=n(1312),o=n(6025),d=n(4848);function u(e){return(0,d.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,d.jsx)("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"})})}const m={breadcrumbHomeIcon:"breadcrumbHomeIcon_YNFT"};function h(){const e=(0,o.Ay)("/");return(0,d.jsx)("li",{className:"breadcrumbs__item",children:(0,d.jsx)(c.A,{"aria-label":(0,l.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e,children:(0,d.jsx)(u,{className:m.breadcrumbHomeIcon})})})}var b=n(5260),x=n(4586);function p(e){const s=function({breadcrumbs:e}){const{siteConfig:s}=(0,x.A)();return{"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:e.filter((e=>e.href)).map(((e,n)=>({"@type":"ListItem",position:n+1,name:e.label,item:`${s.url}${e.href}`})))}}({breadcrumbs:e.breadcrumbs});return(0,d.jsx)(b.A,{children:(0,d.jsx)("script",{type:"application/ld+json",children:JSON.stringify(s)})})}const v={breadcrumbsContainer:"breadcrumbsContainer_Z_bl"};function g({children:e,href:s,isLast:n}){const t="breadcrumbs__link";return n?(0,d.jsx)("span",{className:t,children:e}):s?(0,d.jsx)(c.A,{className:t,href:s,children:(0,d.jsx)("span",{children:e})}):(0,d.jsx)("span",{className:t,children:e})}function f({children:e,active:s}){return(0,d.jsx)("li",{className:(0,t.A)("breadcrumbs__item",{"breadcrumbs__item--active":s}),children:e})}function j(){const e=(0,a.OF)(),s=(0,i.Dt)();return e?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(p,{breadcrumbs:e}),(0,d.jsx)("nav",{className:(0,t.A)(r.G.docs.docBreadcrumbs,v.breadcrumbsContainer),"aria-label":(0,l.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"}),children:(0,d.jsxs)("ul",{className:"breadcrumbs",children:[s&&(0,d.jsx)(h,{}),e.map(((s,n)=>{const t=n===e.length-1,r="category"===s.type&&s.linkUnlisted?void 0:s.href;return(0,d.jsx)(f,{active:t,children:(0,d.jsx)(g,{href:r,isLast:t,children:s.label})},n)}))]})})]}):null}},1878:(e,s,n)=>{n.d(s,{A:()=>p});n(6540);var t=n(4164),r=n(4586),a=n(8774),i=n(1312),c=n(4070),l=n(7559),o=n(3886),d=n(3025),u=n(4848);const m={unreleased:function({siteTitle:e,versionMetadata:s}){return(0,u.jsx)(i.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:s.label})},children:"This is unreleased documentation for {siteTitle} {versionLabel} version."})},unmaintained:function({siteTitle:e,versionMetadata:s}){return(0,u.jsx)(i.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:s.label})},children:"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained."})}};function h(e){const s=m[e.versionMetadata.banner];return(0,u.jsx)(s,{...e})}function b({versionLabel:e,to:s,onClick:n}){return(0,u.jsx)(i.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:e,latestVersionLink:(0,u.jsx)("b",{children:(0,u.jsx)(a.A,{to:s,onClick:n,children:(0,u.jsx)(i.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label",children:"latest version"})})})},children:"For up-to-date documentation, see the {latestVersionLink} ({versionLabel})."})}function x({className:e,versionMetadata:s}){const{siteConfig:{title:n}}=(0,r.A)(),{pluginId:a}=(0,c.vT)({failfast:!0}),{savePreferredVersionName:i}=(0,o.g1)(a),{latestDocSuggestion:d,latestVersionSuggestion:m}=(0,c.HW)(a),x=d??(p=m).docs.find((e=>e.id===p.mainDocId));var p;return(0,u.jsxs)("div",{className:(0,t.A)(e,l.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert",children:[(0,u.jsx)("div",{children:(0,u.jsx)(h,{siteTitle:n,versionMetadata:s})}),(0,u.jsx)("div",{className:"margin-top--md",children:(0,u.jsx)(b,{versionLabel:m.label,to:x.path,onClick:()=>i(m.name)})})]})}function p({className:e}){const s=(0,d.r)();return s.banner?(0,u.jsx)(x,{className:e,versionMetadata:s}):null}},4267:(e,s,n)=>{n.d(s,{A:()=>l});n(6540);var t=n(4164),r=n(1312),a=n(7559),i=n(3025),c=n(4848);function l({className:e}){const s=(0,i.r)();return s.badge?(0,c.jsx)("span",{className:(0,t.A)(e,a.G.docs.docVersionBadge,"badge badge--secondary"),children:(0,c.jsx)(r.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:s.label},children:"Version: {versionLabel}"})}):null}},5846:(e,s,n)=>{n.d(s,{W:()=>o});var t=n(6540),r=n(4586);const a=["zero","one","two","few","many","other"];function i(e){return a.filter((s=>e.includes(s)))}const c={locale:"en",pluralForms:i(["one","other"]),select:e=>1===e?"one":"other"};function l(){const{i18n:{currentLocale:e}}=(0,r.A)();return(0,t.useMemo)((()=>{try{return function(e){const s=new Intl.PluralRules(e);return{locale:e,pluralForms:i(s.resolvedOptions().pluralCategories),select:e=>s.select(e)}}(e)}catch(s){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${s.message}\n`),c}}),[e])}function o(){const e=l();return{selectMessage:(s,n)=>function(e,s,n){const t=e.split("|");if(1===t.length)return t[0];t.length>n.pluralForms.length&&console.error(`For locale=${n.locale}, a maximum of ${n.pluralForms.length} plural forms are expected (${n.pluralForms.join(",")}), but the message contains ${t.length}: ${e}`);const r=n.select(s),a=n.pluralForms.indexOf(r);return t[Math.min(a,t.length-1)]}(n,s,e)}}},7719:(e,s,n)=>{n.d(s,{A:()=>c});n(6540);var t=n(4164),r=n(1312),a=n(9022),i=n(4848);function c(e){const{className:s,previous:n,next:c}=e;return(0,i.jsxs)("nav",{className:(0,t.A)(s,"pagination-nav"),"aria-label":(0,r.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages",description:"The ARIA label for the docs pagination"}),children:[n&&(0,i.jsx)(a.A,{...n,subLabel:(0,i.jsx)(r.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc",children:"Previous"})}),c&&(0,i.jsx)(a.A,{...c,subLabel:(0,i.jsx)(r.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc",children:"Next"}),isNext:!0})]})}},9022:(e,s,n)=>{n.d(s,{A:()=>i});n(6540);var t=n(4164),r=n(8774),a=n(4848);function i(e){const{permalink:s,title:n,subLabel:i,isNext:c}=e;return(0,a.jsxs)(r.A,{className:(0,t.A)("pagination-nav__link",c?"pagination-nav__link--next":"pagination-nav__link--prev"),to:s,children:[i&&(0,a.jsx)("div",{className:"pagination-nav__sublabel",children:i}),(0,a.jsx)("div",{className:"pagination-nav__label",children:n})]})}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/17896441.add60b5f.js b/deprecated/old-docs-build/docs/assets/js/17896441.add60b5f.js
new file mode 100644
index 0000000..0a64247
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/17896441.add60b5f.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[8401],{594:(e,n,t)=>{t.d(n,{A:()=>j});t(6540);var s=t(4164),a=t(7559),i=t(6972),l=t(9169),o=t(8774),r=t(1312),c=t(6025),d=t(4848);function u(e){return(0,d.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,d.jsx)("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"})})}const m={breadcrumbHomeIcon:"breadcrumbHomeIcon_YNFT"};function h(){const e=(0,c.Ay)("/");return(0,d.jsx)("li",{className:"breadcrumbs__item",children:(0,d.jsx)(o.A,{"aria-label":(0,r.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e,children:(0,d.jsx)(u,{className:m.breadcrumbHomeIcon})})})}var b=t(5260),v=t(4586);function x(e){const n=function({breadcrumbs:e}){const{siteConfig:n}=(0,v.A)();return{"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:e.filter((e=>e.href)).map(((e,t)=>({"@type":"ListItem",position:t+1,name:e.label,item:`${n.url}${e.href}`})))}}({breadcrumbs:e.breadcrumbs});return(0,d.jsx)(b.A,{children:(0,d.jsx)("script",{type:"application/ld+json",children:JSON.stringify(n)})})}const g={breadcrumbsContainer:"breadcrumbsContainer_Z_bl"};function f({children:e,href:n,isLast:t}){const s="breadcrumbs__link";return t?(0,d.jsx)("span",{className:s,children:e}):n?(0,d.jsx)(o.A,{className:s,href:n,children:(0,d.jsx)("span",{children:e})}):(0,d.jsx)("span",{className:s,children:e})}function p({children:e,active:n}){return(0,d.jsx)("li",{className:(0,s.A)("breadcrumbs__item",{"breadcrumbs__item--active":n}),children:e})}function j(){const e=(0,i.OF)(),n=(0,l.Dt)();return e?(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(x,{breadcrumbs:e}),(0,d.jsx)("nav",{className:(0,s.A)(a.G.docs.docBreadcrumbs,g.breadcrumbsContainer),"aria-label":(0,r.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"}),children:(0,d.jsxs)("ul",{className:"breadcrumbs",children:[n&&(0,d.jsx)(h,{}),e.map(((n,t)=>{const s=t===e.length-1,a="category"===n.type&&n.linkUnlisted?void 0:n.href;return(0,d.jsx)(p,{active:s,children:(0,d.jsx)(f,{href:a,isLast:s,children:n.label})},t)}))]})})]}):null}},833:(e,n,t)=>{t.r(n),t.d(n,{default:()=>F});var s=t(6540),a=t(5500),i=t(9532),l=t(4848);const o=s.createContext(null);function r({children:e,content:n}){const t=function(e){return(0,s.useMemo)((()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,contentTitle:e.contentTitle,toc:e.toc})),[e])}(n);return(0,l.jsx)(o.Provider,{value:t,children:e})}function c(){const e=(0,s.useContext)(o);if(null===e)throw new i.dV("DocProvider");return e}function d(){const{metadata:e,frontMatter:n,assets:t}=c();return(0,l.jsx)(a.be,{title:e.title,description:e.description,keywords:n.keywords,image:t.image??n.image})}var u=t(4164),m=t(4581),h=t(7719);function b(){const{metadata:e}=c();return(0,l.jsx)(h.A,{className:"docusaurus-mt-lg",previous:e.previous,next:e.next})}var v=t(1878),x=t(4267),g=t(7559),f=t(4434),p=t(4336);function j(){const{metadata:e}=c(),{editUrl:n,lastUpdatedAt:t,lastUpdatedBy:s,tags:a}=e,i=a.length>0,o=!!(n||t||s);return i||o?(0,l.jsxs)("footer",{className:(0,u.A)(g.G.docs.docFooter,"docusaurus-mt-lg"),children:[i&&(0,l.jsx)("div",{className:(0,u.A)("row margin-top--sm",g.G.docs.docFooterTagsRow),children:(0,l.jsx)("div",{className:"col",children:(0,l.jsx)(f.A,{tags:a})})}),o&&(0,l.jsx)(p.A,{className:(0,u.A)("margin-top--sm",g.G.docs.docFooterEditMetaRow),editUrl:n,lastUpdatedAt:t,lastUpdatedBy:s})]}):null}var A=t(1422),N=t(5195),C=t(1312);const L={tocCollapsibleButton:"tocCollapsibleButton_TO0P",tocCollapsibleButtonExpanded:"tocCollapsibleButtonExpanded_MG3E"};function _({collapsed:e,...n}){return(0,l.jsx)("button",{type:"button",...n,className:(0,u.A)("clean-btn",L.tocCollapsibleButton,!e&&L.tocCollapsibleButtonExpanded,n.className),children:(0,l.jsx)(C.A,{id:"theme.TOCCollapsible.toggleButtonLabel",description:"The label used by the button on the collapsible TOC component",children:"On this page"})})}const T={tocCollapsible:"tocCollapsible_ETCw",tocCollapsibleContent:"tocCollapsibleContent_vkbj",tocCollapsibleExpanded:"tocCollapsibleExpanded_sAul"};function k({toc:e,className:n,minHeadingLevel:t,maxHeadingLevel:s}){const{collapsed:a,toggleCollapsed:i}=(0,A.u)({initialState:!0});return(0,l.jsxs)("div",{className:(0,u.A)(T.tocCollapsible,!a&&T.tocCollapsibleExpanded,n),children:[(0,l.jsx)(_,{collapsed:a,onClick:i}),(0,l.jsx)(A.N,{lazy:!0,className:T.tocCollapsibleContent,collapsed:a,children:(0,l.jsx)(N.A,{toc:e,minHeadingLevel:t,maxHeadingLevel:s})})]})}const H={tocMobile:"tocMobile_ITEo"};function y(){const{toc:e,frontMatter:n}=c();return(0,l.jsx)(k,{toc:e,minHeadingLevel:n.toc_min_heading_level,maxHeadingLevel:n.toc_max_heading_level,className:(0,u.A)(g.G.docs.docTocMobile,H.tocMobile)})}var M=t(7763);function w(){const{toc:e,frontMatter:n}=c();return(0,l.jsx)(M.A,{toc:e,minHeadingLevel:n.toc_min_heading_level,maxHeadingLevel:n.toc_max_heading_level,className:g.G.docs.docTocDesktop})}var B=t(1107),I=t(540);function E({children:e}){const n=function(){const{metadata:e,frontMatter:n,contentTitle:t}=c();return n.hide_title||void 0!==t?null:e.title}();return(0,l.jsxs)("div",{className:(0,u.A)(g.G.docs.docMarkdown,"markdown"),children:[n&&(0,l.jsx)("header",{children:(0,l.jsx)(B.A,{as:"h1",children:n})}),(0,l.jsx)(I.A,{children:e})]})}var V=t(594),O=t(1689);const R={docItemContainer:"docItemContainer_Djhp",docItemCol:"docItemCol_VOVn"};function G({children:e}){const n=function(){const{frontMatter:e,toc:n}=c(),t=(0,m.l)(),s=e.hide_table_of_contents,a=!s&&n.length>0;return{hidden:s,mobile:a?(0,l.jsx)(y,{}):void 0,desktop:!a||"desktop"!==t&&"ssr"!==t?void 0:(0,l.jsx)(w,{})}}(),{metadata:t}=c();return(0,l.jsxs)("div",{className:"row",children:[(0,l.jsxs)("div",{className:(0,u.A)("col",!n.hidden&&R.docItemCol),children:[(0,l.jsx)(O.A,{metadata:t}),(0,l.jsx)(v.A,{}),(0,l.jsxs)("div",{className:R.docItemContainer,children:[(0,l.jsxs)("article",{children:[(0,l.jsx)(V.A,{}),(0,l.jsx)(x.A,{}),n.mobile,(0,l.jsx)(E,{children:e}),(0,l.jsx)(j,{})]}),(0,l.jsx)(b,{})]})]}),n.desktop&&(0,l.jsx)("div",{className:"col col--3",children:n.desktop})]})}function F(e){const n=`docs-doc-id-${e.content.metadata.id}`,t=e.content;return(0,l.jsx)(r,{content:e.content,children:(0,l.jsxs)(a.e3,{className:n,children:[(0,l.jsx)(d,{}),(0,l.jsx)(G,{children:(0,l.jsx)(t,{})})]})})}},1689:(e,n,t)=>{t.d(n,{A:()=>d});t(6540);var s=t(4164),a=t(4084),i=t(7559),l=t(7293),o=t(4848);function r({className:e}){return(0,o.jsx)(l.A,{type:"caution",title:(0,o.jsx)(a.Yh,{}),className:(0,s.A)(e,i.G.common.draftBanner),children:(0,o.jsx)(a.TT,{})})}var c=t(2234);function d({metadata:e}){const{unlisted:n,frontMatter:t}=e;return(0,o.jsxs)(o.Fragment,{children:[(n||t.unlisted)&&(0,o.jsx)(c.A,{}),t.draft&&(0,o.jsx)(r,{})]})}},1878:(e,n,t)=>{t.d(n,{A:()=>x});t(6540);var s=t(4164),a=t(4586),i=t(8774),l=t(1312),o=t(4070),r=t(7559),c=t(3886),d=t(3025),u=t(4848);const m={unreleased:function({siteTitle:e,versionMetadata:n}){return(0,u.jsx)(l.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:n.label})},children:"This is unreleased documentation for {siteTitle} {versionLabel} version."})},unmaintained:function({siteTitle:e,versionMetadata:n}){return(0,u.jsx)(l.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:e,versionLabel:(0,u.jsx)("b",{children:n.label})},children:"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained."})}};function h(e){const n=m[e.versionMetadata.banner];return(0,u.jsx)(n,{...e})}function b({versionLabel:e,to:n,onClick:t}){return(0,u.jsx)(l.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:e,latestVersionLink:(0,u.jsx)("b",{children:(0,u.jsx)(i.A,{to:n,onClick:t,children:(0,u.jsx)(l.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label",children:"latest version"})})})},children:"For up-to-date documentation, see the {latestVersionLink} ({versionLabel})."})}function v({className:e,versionMetadata:n}){const{siteConfig:{title:t}}=(0,a.A)(),{pluginId:i}=(0,o.vT)({failfast:!0}),{savePreferredVersionName:l}=(0,c.g1)(i),{latestDocSuggestion:d,latestVersionSuggestion:m}=(0,o.HW)(i),v=d??(x=m).docs.find((e=>e.id===x.mainDocId));var x;return(0,u.jsxs)("div",{className:(0,s.A)(e,r.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert",children:[(0,u.jsx)("div",{children:(0,u.jsx)(h,{siteTitle:t,versionMetadata:n})}),(0,u.jsx)("div",{className:"margin-top--md",children:(0,u.jsx)(b,{versionLabel:m.label,to:v.path,onClick:()=>l(m.name)})})]})}function x({className:e}){const n=(0,d.r)();return n.banner?(0,u.jsx)(v,{className:e,versionMetadata:n}):null}},2234:(e,n,t)=>{t.d(n,{A:()=>c});t(6540);var s=t(4164),a=t(4084),i=t(7559),l=t(7293),o=t(4848);function r({className:e}){return(0,o.jsx)(l.A,{type:"caution",title:(0,o.jsx)(a.Rc,{}),className:(0,s.A)(e,i.G.common.unlistedBanner),children:(0,o.jsx)(a.Uh,{})})}function c(e){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(a.AE,{}),(0,o.jsx)(r,{...e})]})}},4084:(e,n,t)=>{t.d(n,{AE:()=>r,Rc:()=>l,TT:()=>d,Uh:()=>o,Yh:()=>c});t(6540);var s=t(1312),a=t(5260),i=t(4848);function l(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function o(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function r(){return(0,i.jsx)(a.A,{children:(0,i.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function c(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,i.jsx)(s.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},4267:(e,n,t)=>{t.d(n,{A:()=>r});t(6540);var s=t(4164),a=t(1312),i=t(7559),l=t(3025),o=t(4848);function r({className:e}){const n=(0,l.r)();return n.badge?(0,o.jsx)("span",{className:(0,s.A)(e,i.G.docs.docVersionBadge,"badge badge--secondary"),children:(0,o.jsx)(a.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:n.label},children:"Version: {versionLabel}"})}):null}},4434:(e,n,t)=>{t.d(n,{A:()=>r});t(6540);var s=t(4164),a=t(1312),i=t(6133);const l={tags:"tags_jXut",tag:"tag_QGVx"};var o=t(4848);function r({tags:e}){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("b",{children:(0,o.jsx)(a.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,o.jsx)("ul",{className:(0,s.A)(l.tags,"padding--none","margin-left--sm"),children:e.map((e=>(0,o.jsx)("li",{className:l.tag,children:(0,o.jsx)(i.A,{...e})},e.permalink)))})]})}},5195:(e,n,t)=>{t.d(n,{A:()=>v});var s=t(6540),a=t(6342);function i(e){const n=e.map((e=>({...e,parentIndex:-1,children:[]}))),t=Array(7).fill(-1);n.forEach(((e,n)=>{const s=t.slice(2,e.level);e.parentIndex=Math.max(...s),t[e.level]=n}));const s=[];return n.forEach((e=>{const{parentIndex:t,...a}=e;t>=0?n[t].children.push(a):s.push(a)})),s}function l({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return e.flatMap((e=>{const s=l({toc:e.children,minHeadingLevel:n,maxHeadingLevel:t});return function(e){return e.level>=n&&e.level<=t}(e)?[{...e,children:s}]:s}))}function o(e){const n=e.getBoundingClientRect();return n.top===n.bottom?o(e.parentNode):n}function r(e,{anchorTopOffset:n}){const t=e.find((e=>o(e).top>=n));if(t){return function(e){return e.top>0&&e.bottom{e.current=n?0:document.querySelector(".navbar").clientHeight}),[n]),e}function d(e){const n=(0,s.useRef)(void 0),t=c();(0,s.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:s,linkActiveClassName:a,minHeadingLevel:i,maxHeadingLevel:l}=e;function o(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(s),o=function({minHeadingLevel:e,maxHeadingLevel:n}){const t=[];for(let s=e;s<=n;s+=1)t.push(`h${s}.anchor`);return Array.from(document.querySelectorAll(t.join()))}({minHeadingLevel:i,maxHeadingLevel:l}),c=r(o,{anchorTopOffset:t.current}),d=e.find((e=>c&&c.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,t){t?(n.current&&n.current!==e&&n.current.classList.remove(a),e.classList.add(a),n.current=e):e.classList.remove(a)}(e,e===d)}))}return document.addEventListener("scroll",o),document.addEventListener("resize",o),o(),()=>{document.removeEventListener("scroll",o),document.removeEventListener("resize",o)}}),[e,t])}var u=t(8774),m=t(4848);function h({toc:e,className:n,linkClassName:t,isChild:s}){return e.length?(0,m.jsx)("ul",{className:s?void 0:n,children:e.map((e=>(0,m.jsxs)("li",{children:[(0,m.jsx)(u.A,{to:`#${e.id}`,className:t??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,m.jsx)(h,{isChild:!0,toc:e.children,className:n,linkClassName:t})]},e.id)))}):null}const b=s.memo(h);function v({toc:e,className:n="table-of-contents table-of-contents__left-border",linkClassName:t="table-of-contents__link",linkActiveClassName:o,minHeadingLevel:r,maxHeadingLevel:c,...u}){const h=(0,a.p)(),v=r??h.tableOfContents.minHeadingLevel,x=c??h.tableOfContents.maxHeadingLevel,g=function({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return(0,s.useMemo)((()=>l({toc:i(e),minHeadingLevel:n,maxHeadingLevel:t})),[e,n,t])}({toc:e,minHeadingLevel:v,maxHeadingLevel:x});return d((0,s.useMemo)((()=>{if(t&&o)return{linkClassName:t,linkActiveClassName:o,minHeadingLevel:v,maxHeadingLevel:x}}),[t,o,v,x])),(0,m.jsx)(b,{toc:g,className:n,linkClassName:t,...u})}},6133:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var s=t(4164),a=t(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var l=t(4848);function o({permalink:e,label:n,count:t,description:o}){return(0,l.jsxs)(a.A,{rel:"tag",href:e,title:o,className:(0,s.A)(i.tag,t?i.tagWithCount:i.tagRegular),children:[n,t&&(0,l.jsx)("span",{children:t})]})}},7719:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var s=t(4164),a=t(1312),i=t(9022),l=t(4848);function o(e){const{className:n,previous:t,next:o}=e;return(0,l.jsxs)("nav",{className:(0,s.A)(n,"pagination-nav"),"aria-label":(0,a.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages",description:"The ARIA label for the docs pagination"}),children:[t&&(0,l.jsx)(i.A,{...t,subLabel:(0,l.jsx)(a.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc",children:"Previous"})}),o&&(0,l.jsx)(i.A,{...o,subLabel:(0,l.jsx)(a.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc",children:"Next"}),isNext:!0})]})}},7763:(e,n,t)=>{t.d(n,{A:()=>c});t(6540);var s=t(4164),a=t(5195);const i={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var l=t(4848);const o="table-of-contents__link toc-highlight",r="table-of-contents__link--active";function c({className:e,...n}){return(0,l.jsx)("div",{className:(0,s.A)(i.tableOfContents,"thin-scrollbar",e),children:(0,l.jsx)(a.A,{...n,linkClassName:o,linkActiveClassName:r})})}},9022:(e,n,t)=>{t.d(n,{A:()=>l});t(6540);var s=t(4164),a=t(8774),i=t(4848);function l(e){const{permalink:n,title:t,subLabel:l,isNext:o}=e;return(0,i.jsxs)(a.A,{className:(0,s.A)("pagination-nav__link",o?"pagination-nav__link--next":"pagination-nav__link--prev"),to:n,children:[l&&(0,i.jsx)("div",{className:"pagination-nav__sublabel",children:l}),(0,i.jsx)("div",{className:"pagination-nav__label",children:t})]})}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/1f391b9e.eb2921ce.js b/deprecated/old-docs-build/docs/assets/js/1f391b9e.eb2921ce.js
new file mode 100644
index 0000000..2d49308
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/1f391b9e.eb2921ce.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6061],{1689:(e,n,t)=>{t.d(n,{A:()=>d});t(6540);var i=t(4164),a=t(4084),s=t(7559),r=t(7293),l=t(4848);function c({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(a.Yh,{}),className:(0,i.A)(e,s.G.common.draftBanner),children:(0,l.jsx)(a.TT,{})})}var o=t(2234);function d({metadata:e}){const{unlisted:n,frontMatter:t}=e;return(0,l.jsxs)(l.Fragment,{children:[(n||t.unlisted)&&(0,l.jsx)(o.A,{}),t.draft&&(0,l.jsx)(c,{})]})}},2234:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var i=t(4164),a=t(4084),s=t(7559),r=t(7293),l=t(4848);function c({className:e}){return(0,l.jsx)(r.A,{type:"caution",title:(0,l.jsx)(a.Rc,{}),className:(0,i.A)(e,s.G.common.unlistedBanner),children:(0,l.jsx)(a.Uh,{})})}function o(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(a.AE,{}),(0,l.jsx)(c,{...e})]})}},4084:(e,n,t)=>{t.d(n,{AE:()=>c,Rc:()=>r,TT:()=>d,Uh:()=>l,Yh:()=>o});t(6540);var i=t(1312),a=t(5260),s=t(4848);function r(){return(0,s.jsx)(i.A,{id:"theme.contentVisibility.unlistedBanner.title",description:"The unlisted content banner title",children:"Unlisted page"})}function l(){return(0,s.jsx)(i.A,{id:"theme.contentVisibility.unlistedBanner.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function c(){return(0,s.jsx)(a.A,{children:(0,s.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}function o(){return(0,s.jsx)(i.A,{id:"theme.contentVisibility.draftBanner.title",description:"The draft content banner title",children:"Draft page"})}function d(){return(0,s.jsx)(i.A,{id:"theme.contentVisibility.draftBanner.message",description:"The draft content banner message",children:"This page is a draft. It will only be visible in dev and be excluded from the production build."})}},5195:(e,n,t)=>{t.d(n,{A:()=>v});var i=t(6540),a=t(6342);function s(e){const n=e.map((e=>({...e,parentIndex:-1,children:[]}))),t=Array(7).fill(-1);n.forEach(((e,n)=>{const i=t.slice(2,e.level);e.parentIndex=Math.max(...i),t[e.level]=n}));const i=[];return n.forEach((e=>{const{parentIndex:t,...a}=e;t>=0?n[t].children.push(a):i.push(a)})),i}function r({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return e.flatMap((e=>{const i=r({toc:e.children,minHeadingLevel:n,maxHeadingLevel:t});return function(e){return e.level>=n&&e.level<=t}(e)?[{...e,children:i}]:i}))}function l(e){const n=e.getBoundingClientRect();return n.top===n.bottom?l(e.parentNode):n}function c(e,{anchorTopOffset:n}){const t=e.find((e=>l(e).top>=n));if(t){return function(e){return e.top>0&&e.bottom{e.current=n?0:document.querySelector(".navbar").clientHeight}),[n]),e}function d(e){const n=(0,i.useRef)(void 0),t=o();(0,i.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:i,linkActiveClassName:a,minHeadingLevel:s,maxHeadingLevel:r}=e;function l(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(i),l=function({minHeadingLevel:e,maxHeadingLevel:n}){const t=[];for(let i=e;i<=n;i+=1)t.push(`h${i}.anchor`);return Array.from(document.querySelectorAll(t.join()))}({minHeadingLevel:s,maxHeadingLevel:r}),o=c(l,{anchorTopOffset:t.current}),d=e.find((e=>o&&o.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,t){t?(n.current&&n.current!==e&&n.current.classList.remove(a),e.classList.add(a),n.current=e):e.classList.remove(a)}(e,e===d)}))}return document.addEventListener("scroll",l),document.addEventListener("resize",l),l(),()=>{document.removeEventListener("scroll",l),document.removeEventListener("resize",l)}}),[e,t])}var m=t(8774),u=t(4848);function f({toc:e,className:n,linkClassName:t,isChild:i}){return e.length?(0,u.jsx)("ul",{className:i?void 0:n,children:e.map((e=>(0,u.jsxs)("li",{children:[(0,u.jsx)(m.A,{to:`#${e.id}`,className:t??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,u.jsx)(f,{isChild:!0,toc:e.children,className:n,linkClassName:t})]},e.id)))}):null}const h=i.memo(f);function v({toc:e,className:n="table-of-contents table-of-contents__left-border",linkClassName:t="table-of-contents__link",linkActiveClassName:l,minHeadingLevel:c,maxHeadingLevel:o,...m}){const f=(0,a.p)(),v=c??f.tableOfContents.minHeadingLevel,x=o??f.tableOfContents.maxHeadingLevel,g=function({toc:e,minHeadingLevel:n,maxHeadingLevel:t}){return(0,i.useMemo)((()=>r({toc:s(e),minHeadingLevel:n,maxHeadingLevel:t})),[e,n,t])}({toc:e,minHeadingLevel:v,maxHeadingLevel:x});return d((0,i.useMemo)((()=>{if(t&&l)return{linkClassName:t,linkActiveClassName:l,minHeadingLevel:v,maxHeadingLevel:x}}),[t,l,v,x])),(0,u.jsx)(h,{toc:g,className:n,linkClassName:t,...m})}},7763:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var i=t(4164),a=t(5195);const s={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var r=t(4848);const l="table-of-contents__link toc-highlight",c="table-of-contents__link--active";function o({className:e,...n}){return(0,r.jsx)("div",{className:(0,i.A)(s.tableOfContents,"thin-scrollbar",e),children:(0,r.jsx)(a.A,{...n,linkClassName:l,linkActiveClassName:c})})}},7973:(e,n,t)=>{t.r(n),t.d(n,{default:()=>f});t(6540);var i=t(4164),a=t(5500),s=t(7559),r=t(1656),l=t(540),c=t(7763),o=t(1689),d=t(4336);const m={mdxPageWrapper:"mdxPageWrapper_j9I6"};var u=t(4848);function f(e){const{content:n}=e,{metadata:t,assets:f}=n,{title:h,editUrl:v,description:x,frontMatter:g,lastUpdatedBy:p,lastUpdatedAt:j}=t,{keywords:A,wrapperClassName:b,hide_table_of_contents:L}=g,N=f.image??g.image,C=!!(v||j||p);return(0,u.jsx)(a.e3,{className:(0,i.A)(b??s.G.wrapper.mdxPages,s.G.page.mdxPage),children:(0,u.jsxs)(r.A,{children:[(0,u.jsx)(a.be,{title:h,description:x,keywords:A,image:N}),(0,u.jsx)("main",{className:"container container--fluid margin-vert--lg",children:(0,u.jsxs)("div",{className:(0,i.A)("row",m.mdxPageWrapper),children:[(0,u.jsxs)("div",{className:(0,i.A)("col",!L&&"col--8"),children:[(0,u.jsx)(o.A,{metadata:t}),(0,u.jsx)("article",{children:(0,u.jsx)(l.A,{children:(0,u.jsx)(n,{})})}),C&&(0,u.jsx)(d.A,{className:(0,i.A)("margin-top--sm",s.G.pages.pageFooterEditMetaRow),editUrl:v,lastUpdatedAt:j,lastUpdatedBy:p})]}),!L&&n.toc.length>0&&(0,u.jsx)("div",{className:"col col--2",children:(0,u.jsx)(c.A,{toc:n.toc,minHeadingLevel:g.toc_min_heading_level,maxHeadingLevel:g.toc_max_heading_level})})]})})]})})}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/2237.309c0319.js b/deprecated/old-docs-build/docs/assets/js/2237.309c0319.js
new file mode 100644
index 0000000..b043093
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/2237.309c0319.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[2237],{2237:(e,t,i)=>{i.r(t),i.d(t,{default:()=>h});i(6540);var n=i(1312),s=i(5500),o=i(1656),r=i(3363),a=i(4848);function h(){const e=(0,n.T)({id:"theme.NotFound.title",message:"Page Not Found"});return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(s.be,{title:e}),(0,a.jsx)(o.A,{children:(0,a.jsx)(r.A,{})})]})}},3363:(e,t,i)=>{i.d(t,{A:()=>a});i(6540);var n=i(4164),s=i(1312),o=i(1107),r=i(4848);function a({className:e}){return(0,r.jsx)("main",{className:(0,n.A)("container margin-vert--xl",e),children:(0,r.jsx)("div",{className:"row",children:(0,r.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,r.jsx)(o.A,{as:"h1",className:"hero__title",children:(0,r.jsx)(s.A,{id:"theme.NotFound.title",description:"The title of the 404 page",children:"Page Not Found"})}),(0,r.jsx)("p",{children:(0,r.jsx)(s.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page",children:"We could not find what you were looking for."})}),(0,r.jsx)("p",{children:(0,r.jsx)(s.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page",children:"Please contact the owner of the site that linked you to the original URL and let them know their link is broken."})})]})})})}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/2243d275.dba9c441.js b/deprecated/old-docs-build/docs/assets/js/2243d275.dba9c441.js
new file mode 100644
index 0000000..ebcf4c0
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/2243d275.dba9c441.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[786],{940:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/005-8261e9d5635c419f644fa5923013fd95.jpg"},2586:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>l,contentTitle:()=>d,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>c});const s=JSON.parse('{"id":"backend/node/tutorials/intro_web/Lab8IntroBackend/README","title":"Introducci\xf3n al back-end","description":"Front-end y back-end","source":"@site/docs/backend/node/tutorials/intro_web/Lab8IntroBackend/README.md","sourceDirName":"backend/node/tutorials/intro_web/Lab8IntroBackend","slug":"/backend/node/tutorials/intro_web/Lab8IntroBackend/","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab8IntroBackend/","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/backend/node/tutorials/intro_web/Lab8IntroBackend/README.md","tags":[],"version":"current","sidebarPosition":8,"frontMatter":{"sidebar_position":8},"sidebar":"tutorialSidebar","previous":{"title":"Manejo de ramas en Git","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab7Branches/"},"next":{"title":"Rutas y Formas","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab10RutasYFormas/"}}');var o=a(4848),r=a(8453);const i={sidebar_position:8},d="Introducci\xf3n al back-end",l={},c=[{value:"Front-end y back-end",id:"front-end-y-back-end",level:2},{value:"Front-end",id:"front-end",level:2},{value:"Back-end",id:"back-end",level:2},{value:"Full-stack",id:"full-stack",level:2},{value:"NodeJS",id:"nodejs",level:2},{value:"Hello World",id:"hello-world",level:2},{value:"Filesystem",id:"filesystem",level:2},{value:"Async Sort",id:"async-sort",level:2},{value:"C\xf3digo as\xedncrono",id:"c\xf3digo-as\xedncrono",level:2},{value:"Creando un servidor",id:"creando-un-servidor",level:2},{value:"Ver que hace el servidor desde el navegador",id:"ver-que-hace-el-servidor-desde-el-navegador",level:2},{value:"Devolver c\xf3digo HTML",id:"devolver-c\xf3digo-html",level:2}];function t(e){const n={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",header:"header",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.header,{children:(0,o.jsx)(n.h1,{id:"introducci\xf3n-al-back-end",children:"Introducci\xf3n al back-end"})}),"\n",(0,o.jsx)(n.h2,{id:"front-end-y-back-end",children:"Front-end y back-end"}),"\n",(0,o.jsx)(n.p,{children:"Dentro de los laboratorios anteriores hemos mostrado como el uso de HTML, CSS y Javascript, nos permite manipular el DOM dentro del navegador web y crear lo que hoy en d\xeda conocemos como p\xe1ginas web."}),"\n",(0,o.jsx)(n.p,{children:"Hasta este punto debe quedarnos claro que hemos utilizado archivos est\xe1ticos, es decir que no se modifican y que solo generan un procesamiento con Javascript a trav\xe9s de lo que conocemos como Programaci\xf3n Orientada a Eventos."}),"\n",(0,o.jsx)(n.p,{children:"Si bien lo hemos mencionado poco hemos llegado a un punto de infecci\xf3n en el curso donde si bien nos podemos volver expertos en el desarrollo web est\xe1tico, construyendo p\xe1ginas web incre\xedbles con los 3 exponentes, ha llegado el momento de empezar a pensar en el verdadero procesamiento de datos e informaci\xf3n que nos permitir\xe1 crear soluciones integrales a nuestros proyectos y clientes."}),"\n",(0,o.jsx)(n.p,{children:"Para ello necesitamos de distinguir entre el concepto de front-end y back-end. Muy escuchado por los programadores, pero \xbfqu\xe9 abarca cada uno?"}),"\n",(0,o.jsx)(n.p,{children:"Si recuerdas, hemos hablado que la arquitectura que utilizamos para la comunicaci\xf3n entre archivos la hacemos mediante un Cliente-Servidor, el tipo de arquitectura m\xe1s simple que existe. De manera simple podemos decir que el front-end es lo que est\xe1 del lado del cliente y el back-end lo que est\xe1 del lado del servidor."}),"\n",(0,o.jsx)(n.h2,{id:"front-end",children:"Front-end"}),"\n",(0,o.jsxs)(n.p,{children:["El front-end es la parte de una aplicaci\xf3n que interact\xfaa con los usuarios, es conocida como el lados del cliente. B\xe1sicamente es todo lo que vemos en pantalla cuando accedemos a un sitio web o aplicaci\xf3n: tipos de letra, colores, forma responsiva, eventos y otros elementos que permiten navegar dentro de una p\xe1gina web. Este conjunto crea lo que conocemos como la ",(0,o.jsx)(n.strong,{children:"experiencia de usuario"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"Como hemos dichos el desarrollador de front-end se encarga de la experiencia de usuario, es decir, en el momento en el que se entra a una p\xe1gina web, se debe ser capaz de navegar en ella, por lo que el usuario ver\xe1 una interface sencilla de usar, atractiva y funcional."}),"\n",(0,o.jsx)(n.p,{children:"Ahora bien, el front-end hoy en d\xeda no abarca solamente desarrollo web, pues hoy en d\xeda existen muchas formas de interfaces de usuario como aplicaciones de escritorio, aplicaciones m\xf3viles, entre otras. Cada una va a tener sus propias reglas, lenguajes y limitaciones."}),"\n",(0,o.jsx)(n.h2,{id:"back-end",children:"Back-end"}),"\n",(0,o.jsxs)(n.p,{children:["Cuando hablamos de back-end nos referimos al interior de las aplicaciones que viven en el servidor y que a menudo se les denomina coloquialmente como ",(0,o.jsx)(n.strong,{children:"el lado del servidor"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"El back end de un sitio consiste en un servidor, una aplicaci\xf3n y una base de datos. Se toman los datos, se procesa la informaci\xf3n y se env\xeda al usuario. Hoy en d\xeda, es un poco m\xe1s complejo que eso pues seg\xfan el tama\xf1o y cantidad de usuarios se requiere m\xe1s capacidad de procesamiento con lo que un solo servidor puede ser suficiente. Pero para comenzar y para un sistema b\xe1sico, un servidor podr\xe1 ser suficiente."}),"\n",(0,o.jsx)(n.p,{children:"Cuando hablamos de un solo servidor para servir a toda la aplicaci\xf3n, es decir donde metemos aplicaci\xf3n, base de datos y archivos, estamos hablando de una arquitectura de monolito, pues toda la capacidad va a recaer en el poder de computo de 1 sola computadora en este caso el servidor."}),"\n",(0,o.jsxs)(n.p,{children:["Conforme vayas avanzando en tu carrera aprender\xe1 que un monolito limita mucho las capacidades de procesamiento, crecimiento y seguridad de los sistemas, por lo que ser\xe1 tu labor al graduarte aplicar estrategias de arquitectura para aplicaciones grandes con demanda de usuarios en tiempo real o con una capacidad de usuarios o de procesamiento de datos que sobrepase los terabytes de informaci\xf3n. ",(0,o.jsx)(n.strong,{children:"Y podr\xe1s hacerlo"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"Un desarrollador back-end debe tener conocimiento amplio en lenguajes de programaci\xf3n, manejo de servidores, sistemas operativos, seguridad y bases de datos. Si bien no es necesario conocer todos los lenguajes, es importante conocer que ventajas y desventajas trae cada uno pues ning\xfan lenguaje es infalible. Adem\xe1s de que cada lenguaje cuenta con sus propios frameworks que no son m\xe1s que librer\xedas que ayudan a hacer el trabajo del desarrollador back-end m\xe1s simple incorporando buenas pr\xe1cticas, m\xe9todos de conexi\xf3n, seguridad, entre otros."}),"\n",(0,o.jsx)(n.h2,{id:"full-stack",children:"Full-stack"}),"\n",(0,o.jsx)(n.p,{children:"Algo que pudieras haber escuchado es sobre el t\xe9rmino full-stack, y este no es m\xe1s que el tipo de desarrollador que tiene conocimiento integral de front-end y back-end, as\xed como de manejo de diversos sistemas operativos y lenguajes de programaci\xf3n."}),"\n",(0,o.jsx)(n.h2,{id:"nodejs",children:"NodeJS"}),"\n",(0,o.jsx)(n.p,{children:"Para el curso vamos a estar trabajando con NodeJS el lenguaje de programaci\xf3n para servidores que no es otro m\xe1s que Javascript. NodeJS surgi\xf3 a partir de que el desarrollo de front-end es potenciado por el lenguaje Javascript, por lo que un grupo de desarrolladores decidieron facilitar la curva de desarrollo web reutilizando el lenguaje pero d\xe1ndole todas las capacidades que se necesitan para hacer el trabajo que se necesita en back-end que por ejemplo algunas funciones son: manejo de peticiones tcp/ip, capacidad de ejecuci\xf3n de c\xf3digo de javascript sin la necesidad de un navegador, ente muchas muchas otras."}),"\n",(0,o.jsxs)(n.p,{children:["Para esta pr\xe1ctica ha llegado el momento de descargar NodeJS, lo puedes hacer desde la ",(0,o.jsx)(n.a,{href:"https://nodejs.org/en",children:"p\xe1gina oficial"}),". NodeJS est\xe1 disponible para todos los sistemas operativos, te recomiendo lo instales tal cual para evitarte problemas de configuraci\xf3n, sobre todo en Windows ya que la instalaci\xf3n incluye un paquete adicional de instalaci\xf3n que incluye librer\xedas de .NET y el lenguaje Python que se necesitan para que el entorno simulado de NodeJS funcione."]}),"\n",(0,o.jsx)(n.p,{children:"Dentro de NodeJS vas a tener 2 versiones a instalar la mayor\xeda de las veces la LTS (Long Term Support) y la (Current) \xf3 actual. En general siempre intenta tener la LTS, pues es la versi\xf3n m\xe1s estable mientras que la Current puede llegar a tener problemas al ser un poco m\xe1s experimental o menos soporte a las librer\xedas."}),"\n",(0,o.jsx)(n.p,{children:"Una vez que hayamos instalado NodeJS, podemos crear una carpeta en nuestra computadora y abrir nuestra terminal para poder trabajar con NodeJS."}),"\n",(0,o.jsx)(n.h2,{id:"hello-world",children:"Hello World"}),"\n",(0,o.jsxs)(n.p,{children:["Abriendo nuestro editor de confianza vamos a tener esta carpeta de inicio y vamos a crear un archivo ",(0,o.jsx)(n.strong,{children:"app.js."})]}),"\n",(0,o.jsxs)(n.p,{children:["Anteriormente, la \xfanica forma de ver el resultado de nuestro archivo de Javascript, era mediante la liga a trav\xe9s de un archivo ",(0,o.jsx)(n.strong,{children:"HTML"})," en donde al abrir el navegador, ten\xedamos que navegar a la consola y finalmente ver nuestro resultado."]}),"\n",(0,o.jsxs)(n.p,{children:["Ahora vamos a simplificar todo el proceso ejecutando directamente ",(0,o.jsx)(n.strong,{children:"app.js"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"Dentro de app.js escribe lo siguiente:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:'console.log("Hola Mundo");\n'})}),"\n",(0,o.jsx)(n.p,{children:"Antes de continuar te voy a decir que para manejar todo lo concerniente a NodeJS, es a trav\xe9s de l\xednea de comandos y terminal. Aqu\xed no hay escape y es el motivo de huida de muchos desarrolladores pues el manejo de servidores a trav\xe9s de terminal se les hace un proceso tedioso y complicado. Si bien es un terreno desafiante te pido que no te l\xedmites todav\xeda, pues la capacidad que vas a adquirir desde este momento es mucha."}),"\n",(0,o.jsx)(n.p,{children:"Desde nuestra terminal y en la carpeta del proyecto vamos a ejecutar el siguiente comando:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"node -v\n"})}),"\n",(0,o.jsx)(n.p,{children:"Esta instrucci\xf3n nos devolver\xe1 la versi\xf3n actual de NodeJS que tenemos instalada, en mi caso es la:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"v20.11.0\n"})}),"\n",(0,o.jsx)(n.p,{children:"Si tienes una versi\xf3n diferente no te preocupes, NodeJS en ese aspecto no suele tener tantos conflictos de retrocompatibilidad como otros lenguajes, a lo mucho lo que puede suceder es que una librer\xeda no exista o se hayan realizado parches de seguridad que al menos para el aprendizaje no nos afectar\xe1n."}),"\n",(0,o.jsxs)(n.p,{children:["Ahora bien, es momento de ejecutar nuestro c\xf3digo en ",(0,o.jsx)(n.strong,{children:"app.js"}),", para ello escribe la siguiente instrucci\xf3n:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"node app.js\n"})}),"\n",(0,o.jsx)(n.p,{children:"El resultado en terminal ser\xe1 como puedes intuir:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"Hola Mundo\n"})}),"\n",(0,o.jsx)(n.p,{children:"\xc9xito, acaba de ejecutar tu primer programa en NodeJS. Si has trabajado con otros lenguajes de programaci\xf3n notar\xe1s que el proceso es similar, en el sentido que hace falta tener el entorno de ejecuci\xf3n del lenguaje o el compilador en su defecto y a partir de ello se ejecuta el programa."}),"\n",(0,o.jsx)(n.p,{children:"Lo anterior suena muy bien, pero no hemos visto nada diferente a lo que ya vimos en la introducci\xf3n a Javascript."}),"\n",(0,o.jsx)(n.h2,{id:"filesystem",children:"Filesystem"}),"\n",(0,o.jsx)(n.p,{children:"Una de las funciones importantes de un servidor y de cualquier lenguaje de programaci\xf3n es el poder hacer manejo de archivos a trav\xe9s de lo que se conoce como Filesystem."}),"\n",(0,o.jsx)(n.p,{children:"Esto nos llevar\xe1 a los siguientes 2 puntos:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Importar una librer\xeda default de NodeJS"}),"\n",(0,o.jsx)(n.li,{children:"Manejar el filesystem para leer un archivo."}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["Para importar librer\xedas en Javascript, que es algo que no hemos realizado hasta este momento vamos a hacer uso de la siguiente instrucci\xf3n en nuestro archivo ",(0,o.jsx)(n.strong,{children:"app.js"})]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"//fs es el m\xf3dulo que contiene las funciones para \n//manipular el sistema de archivos\nconst filesystem = require('fs');\n"})}),"\n",(0,o.jsx)(n.p,{children:"Esto le dir\xe1 a NodeJS que cargue la librer\xeda para el manejo del filesystem en nuestro programa mientras se est\xe9 ejecutando."}),"\n",(0,o.jsx)(n.p,{children:"Como ya tenemos acceso al sistema de archivos de la computadora, podemos crear, editar o eliminar archivos. Para nuestro caso vamos a crear un nuevo archivo con la siguiente instrucci\xf3n."}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"//Se escribe el segundo par\xe1metro en el archivo del primer par\xe1metro\nfilesystem.writeFileSync('hola.txt', 'Hola mundo desde node');\n"})}),"\n",(0,o.jsxs)(n.p,{children:["Si volvemos a ejecutar nuestro archivo con la instrucci\xf3n ",(0,o.jsx)(n.strong,{children:"node app.js"})," en la terminal veremos el mismo resultado del ",(0,o.jsx)(n.strong,{children:"Hola Mundo"}),", pero, si ves en tu carpeta del proyecto o en tu editor, deber\xeda aparecer un nuevo archivo con nombre ",(0,o.jsx)(n.strong,{children:"hola.txt"})," y si examinas su contenido ver\xe1s que dice:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"Hola mundo desde node\n"})}),"\n",(0,o.jsx)(n.p,{children:"\xbfPor qu\xe9 podemos manipular archivos? Recuerda que cuando hablamos de un servidor es la computadora que almacena nuestra aplicaci\xf3n, por tanto nosotros somos responsables de como se maneja dicha computadora. Cuando hablamos de Javascript del lado del cliente en teor\xeda no deber\xedamos modificar ning\xfan archivo dentro de la computadora del cliente, pues no es algo que nos pertenezca, vi\xe9ndolo desde el punto de ciberseguridad ser\xeda una muy mala pr\xe1ctica y nuestro sitio podr\xeda ser marcado como malicioso. Ahora nota que mi indicaci\xf3n es que no se deber\xeda hacer, pues el echo desde el punto \xe9tico y de seguridad es ese m\xe1s desde el punto de vista funcional es que puede hacerse."}),"\n",(0,o.jsx)(n.h2,{id:"async-sort",children:"Async Sort"}),"\n",(0,o.jsx)(n.p,{children:"Ya tenemos la capacidad de manipular los archivos de nuestra m\xe1quina lo que ya es un gran avance, pero que pasa con NodeJS y Javascript del lado del navegador, adem\xe1s del punto \xe9tico que te mencion\xe9 en el \xfaltimo p\xe1rrafo, \xbfexiste alguna diferencia desde el punto de vista de la sintaxis del lenguaje? La respuesta es no."}),"\n",(0,o.jsx)(n.p,{children:"NodeJS y Javascript siguen las mismas reglas de escritura de instrucciones, variables, funciones, m\xe9todos, etc. Quiz\xe1s la diferencia radica es que en Javascript tenemos acceso al objeto DOM y podemos manipularlo, pero incluso el DOM desde el punto de vista del lenguaje es solo un objeto con datos, variables y funciones."}),"\n",(0,o.jsxs)(n.p,{children:["Para el caso de NodeJS quitando el objeto ",(0,o.jsx)(n.strong,{children:"document"}),", el ",(0,o.jsx)(n.strong,{children:"windows"})," y la manipulaci\xf3n del DOM, podemos hacer el uso de la misma l\xf3gica del lenguaje."]}),"\n",(0,o.jsx)(n.p,{children:"Para comprobarlo veremos el siguiente ejemplo:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"const arreglo = [5000, 60, 90, 100, 10, 20, 10000, 0, 120, 2000, 340, 1000, 50];\n\nfor (let item of arreglo) {\n setTimeout(() => {\n console.log(item);\n }, item);\n}\n"})}),"\n",(0,o.jsx)(n.p,{children:"Trata de descubrir por t\xed mismo que hace el c\xf3digo anterior."}),"\n",(0,o.jsx)(n.p,{children:"El c\xf3digo anterior lo vamos a conocer como el async sort y nos permitir\xe1 ver uno de los usos m\xe1s frecuentes de NodeJS que es la espera as\xedncrona de peticiones."}),"\n",(0,o.jsxs)(n.p,{children:["Dentro del c\xf3digo tendremos un arreglo con n\xfameros enteros no ordenados. Lo que hace el algoritmos es recorrer dicha lista y utilizar la funci\xf3n de Javascript ",(0,o.jsx)(n.strong,{children:"setTimeout()"}),", la cual recibe 2 par\xe1metros:"]}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Funci\xf3n - Una funci\xf3n que ejecute un c\xf3digo que deseemos, en este caso imprimir el n\xfamero que estamos leyendo."}),"\n",(0,o.jsx)(n.li,{children:"Tiempo (milisegundos) - El tiempo que debe pasar en milisegundos para que se ejecute la funci\xf3n que se recibe como par\xe1metro."}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"Entonces lo que hace el c\xf3digo es imprimir en pantalla el elemento de la lista seg\xfan el tiempo en milisegundos que representa."}),"\n",(0,o.jsx)(n.p,{children:"Haciendo una corrida lo que sucede es lo siguiente:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"5000"})," (5 segs), pasar\xe1n 5 segundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"5000"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"60"})," (60 millis), pasar\xe1n 60 milisegundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"60"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"90"})," (90 millis), pasar\xe1n 90 milisegundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"90"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"100"})," (100 millis), pasar\xe1n 100 milisegundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"100"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"10"})," (10 millis), pasar\xe1n 10 milisegundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"10"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"20"})," (20 millis), pasar\xe1n 20 milisegundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"20"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"10000"})," (10 segs), pasar\xe1n 10 segundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"10000"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"0"})," (0 millis), pasar\xe1n 0 milisegundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"0"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"120"})," (120 millis), pasar\xe1n 120 milisegundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"120"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"2000"})," (2 segs), pasar\xe1n 2 segundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"2000"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"340"})," (340 millis), pasar\xe1n 340 milisegundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"340"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"1000"})," (1 segs), pasar\xe1n 1 segundo antes de imprimir ",(0,o.jsx)(n.strong,{children:"1000"}),"."]}),"\n",(0,o.jsxs)(n.li,{children:["Se ejecuta el paso por ",(0,o.jsx)(n.strong,{children:"50"})," (50 millis), pasar\xe1n 50 milisegundos antes de imprimir ",(0,o.jsx)(n.strong,{children:"50"}),"."]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"Con la corrida, resulta m\xe1s evidente que los n\xfameros no se imprimen como vienen en el arreglo, sino que se imprimen en orden pues empiezan a generarse un delay entre cada uno pues el orden es directamente proporcional al tiempo en milisegundos que esperan a imprimirse."}),"\n",(0,o.jsx)(n.p,{children:"El resultado entonces ser\xeda:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"0 \n10 \n20 \n50 \n60 \n90 \n100 \n120 \n340 \n1000 \n2000 \n5000 \n10000\n"})}),"\n",(0,o.jsx)(n.h2,{id:"c\xf3digo-as\xedncrono",children:"C\xf3digo as\xedncrono"}),"\n",(0,o.jsx)(n.p,{children:"Con lo visto anteriormente, entonces vemos que desde Javascript podemos tener c\xf3digo que va llegando en diferentes momentos aunque la ejecuci\xf3n continua sucediendo l\xednea por l\xednea en el orden esperado."}),"\n",(0,o.jsxs)(n.p,{children:["Para la funci\xf3n ",(0,o.jsx)(n.strong,{children:"setTimeOut()"})," el par\xe1metro de funci\xf3n que se recibe espera el tiempo necesario hasta poder ejecutarse, entonces aunque el c\xf3digo de manera lineal va instrucci\xf3n por instrucci\xf3n, si se cumple el tiempo de espera, se ejecuta la funci\xf3n de par\xe1metro sin importar que m\xe1s este sucediendo en ese momento. M\xe1s que decir que sea bueno o malo, depender\xe1 de lo que necesitemos hacer y para el caso de NodeJS y el manejo de nuestro servidor ser\xe1 el pan de cada d\xeda."]}),"\n",(0,o.jsx)(n.p,{children:"Con lo anterior entonces ya deber\xedas de poder resolver la siguiente inc\xf3gnita. \xbfQu\xe9 l\xednea de c\xf3digo se ejecuta primero?"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:'console.log("jojo te hacki\xe9");\nconsole.log("\xbfEn d\xf3nde se ejecuta esta l\xednea?");\n'})}),"\n",(0,o.jsx)(n.p,{children:"Del siguiente extracto de c\xf3digo:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:'const te_hackie = () => {\n console.log("jojo te hacki\xe9");\n}\n//setTimeout ejecuta la funci\xf3n recibida como primer par\xe1metro \n//cuando hayan transcurrido los milisegundos del segundo par\xe1metro\nsetTimeout(te_hackie, 7000);\n\nconsole.log("\xbfEn d\xf3nde se ejecuta esta l\xednea?");\n'})}),"\n",(0,o.jsxs)(n.p,{children:["En el c\xf3digo anterior veremos que si no hemos comentado el Async Sort, la funci\xf3n ",(0,o.jsx)(n.strong,{children:"te_hackie"})," se ejecuta despu\xe9s de la impresi\xf3n del 5000 pero antes que el 10000. Nuevamente a Javascript no lo importa el orden de las funciones, solo el tiempo en el que les toca ejecutarse, a esto es lo que conocemos como c\xf3digo as\xedncrono, y m\xe1s adelante veremos que ser\xe1 la manera en la que funciona un servidor."]}),"\n",(0,o.jsx)(n.h2,{id:"creando-un-servidor",children:"Creando un servidor"}),"\n",(0,o.jsxs)(n.p,{children:["Ha llegado el momento vamos a crear nuestro primer servidor, copia el siguiente c\xf3digo en un nuevo archivo al que le pondremos de nombre ",(0,o.jsx)(n.strong,{children:"index.js"}),"."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"const http = require('http'); \nconst server = http.createServer( (request, response) => { \n// console.log(request.url);\n// response.setHeader('Content-Type', 'text/html');\n// response.write(\"\");\n// response.end();\n});\nserver.listen(3000);\n"})}),"\n",(0,o.jsxs)(n.p,{children:["La raz\xf3n de cambiar de archivo es que en la pr\xe1ctica hay proyectos cuyo nombre principal de archivos de NodeJS lo llaman ",(0,o.jsx)(n.strong,{children:"app.js"})," \xf3 ",(0,o.jsx)(n.strong,{children:"index.js"}),", otro no tan com\xfan ser\xeda ",(0,o.jsx)(n.strong,{children:"main.js"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["En nuestro c\xf3digo haremos uso de otra librer\xeda est\xe1ndar de NodeJS llamada ",(0,o.jsx)(n.strong,{children:"http"}),", esta librer\xeda es la que nos permite definir un servidor y poder manipular las entradas y salidas del mismo."]}),"\n",(0,o.jsx)(n.p,{children:"Antes de ejecutar el archivo vamos a hacer algo, ve a tu navegador y escribe la direcci\xf3n:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"localhost:3000\n//Como alternativa puedes escribir:\n127.0.0.0:3000\n"})}),"\n",(0,o.jsx)(n.p,{children:"El resultado deber\xeda ser el siguiente:"}),"\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"lab_8",src:a(8856).A+"",width:"1918",height:"997"})}),"\n",(0,o.jsxs)(n.p,{children:["Si observas el mensaje de error dice ",(0,o.jsx)(n.strong,{children:"ERR_CONNECTION_REFUSED"})," indic\xe1ndonos que no hay nada corriendo en la direcci\xf3n local dentro del puerto 3000."]}),"\n",(0,o.jsx)(n.p,{children:"Ahora, vamos a ejecutar el archivo con:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"node index.js\n"})}),"\n",(0,o.jsx)(n.p,{children:"Dos cosas van a suceder, en la terminal no finalizar\xe1 el programa se quedar\xe1 en un modo espera. Y si nuevamente vamos al navegador y escribimos la direcci\xf3n pasar\xe1 lo siguiente:"}),"\n",(0,o.jsx)(n.p,{children:"Al inicio no pasar\xe1 nada, pero deber\xe1 aparecer un loader de que algo esta sucediendo."}),"\n",(0,o.jsxs)(n.blockquote,{children:["\n",(0,o.jsxs)(n.p,{children:["Nota: Si usaste la ventana donde nos apareci\xf3 ",(0,o.jsx)(n.strong,{children:"ERR_CONNECTION_REFUSED"})," puedes confundirte, te recomiendo hagas la prueba en una nueva pesta\xf1a."]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"Lo interesante, es que ahora no nos da la p\xe1gina de error, sino que simplemente se queda ah\xed, esto es por que el servidor recibe la petici\xf3n, pero no tenemos nada actualmente que realice."}),"\n",(0,o.jsx)(n.p,{children:"Ahora vamos a quitar el comentario del c\xf3digo que est\xe1 dentro de la declaraci\xf3n de la funci\xf3n del servidor."}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"console.log(request.url);\nresponse.setHeader('Content-Type', 'text/html');\nresponse.write(\"\");\nresponse.end();\n"})}),"\n",(0,o.jsxs)(n.p,{children:["Ahora, guarda el archivo e intenta abrirlo en el navegador con ",(0,o.jsx)(n.strong,{children:"localhost:3000"}),", \xbfqu\xe9 sucedi\xf3?"]}),"\n",(0,o.jsxs)(n.p,{children:["Nada, y esto es por que aunque guardamos nuestro archivo ",(0,o.jsx)(n.strong,{children:"index.js"})," no reiniciamos el servidor, esto es importante cuando est\xe1s desarrollando pues necesitas detener el servidor y volverlo a correr para que carguen los nuevos cambios."]}),"\n",(0,o.jsx)(n.p,{children:"Para detener el servidor, desde la terminal ejecuta la secuencia de comandos ctrl+C."}),"\n",(0,o.jsxs)(n.p,{children:["Nuevamente ejecuta ",(0,o.jsx)(n.strong,{children:"node index.js"}),", ve al navegador y recarga ahora s\xed la p\xe1gina."]}),"\n",(0,o.jsx)(n.p,{children:"El resultado ser\xe1 una p\xe1gina en blanco:"}),"\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"lab_8",src:a(5351).A+"",width:"1918",height:"998"})}),"\n",(0,o.jsx)(n.p,{children:"Vamos a revisar instrucci\xf3n por instrucci\xf3n lo que acabamos de hacer:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"console.log(request.url);\n"})}),"\n",(0,o.jsxs)(n.p,{children:["Esta l\xednea utiliza el objeto ",(0,o.jsx)(n.strong,{children:"request"})," que contendr\xe1 toda la informaci\xf3n que pasemos desde el cliente al servidor, si mandamos un formularios por ejemplo, o informaci\xf3n a trav\xe9s de una url ",(0,o.jsx)(n.strong,{children:"request"})," deber\xeda tener esa informaci\xf3n."]}),"\n",(0,o.jsxs)(n.p,{children:["En el caso de ",(0,o.jsx)(n.strong,{children:"request.url"})," lo que hace es que imprime la url que se est\xe1 llamando en ese momento."]}),"\n",(0,o.jsx)(n.p,{children:"El resultado aparecer\xe1 en la terminal y nos tiene una sorpresa."}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"/\n/favicon.ico\n"})}),"\n",(0,o.jsxs)(n.p,{children:["La primera ruta es la que hace referencia a nuestro navegador cuando escribimos ",(0,o.jsx)(n.strong,{children:"localhost:3000"}),", escribir esta url solo le da la direcci\xf3n a donde conectarse, pero cuando la encuentra adelante escribir\xe1 la url que le es solicitada, en este caso como no pasamos nada, utiliza la url default ",(0,o.jsx)(n.strong,{children:"/"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["La sorpresa vendr\xe1 con la segunda url ",(0,o.jsx)(n.strong,{children:"/favicon.ico"}),", aqu\xed lo que sucede es que al momento de llamar una cualquier url, los navegadores tienen la instrucci\xf3n est\xe1ndar de jalar esta url y colocar un icono, que seguramente has visto al lado del t\xedtulo de la pesta\xf1a del navegador."]}),"\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"lab_8",src:a(9546).A+"",width:"109",height:"27"})}),"\n",(0,o.jsx)(n.p,{children:"Como no estamos manejando esta url el navegador utiliza una imagen default."}),"\n",(0,o.jsx)(n.p,{children:"Para la instrucci\xf3n:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"response.setHeader('Content-Type', 'text/html');\n"})}),"\n",(0,o.jsxs)(n.p,{children:["Lo que estamos diciendo aqu\xed es que el contenido de la respuesta sea tratado por el navegador como c\xf3digo HTML. Aqu\xed se utiliza el objeto ",(0,o.jsx)(n.strong,{children:"response"}),", que nuevamente es esencial en el manejo del servidor, a diferencia de ",(0,o.jsx)(n.strong,{children:"request"})," como podr\xe1s imaginar, contendr\xe1 toda la informaci\xf3n que mandaremos de regreso al cliente al completar la solicitud. Lo cual nos lleva a la siguiente instrucci\xf3n:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:'response.write("");\n'})}),"\n",(0,o.jsx)(n.p,{children:"La instrucci\xf3n escribe el c\xf3digo HTML que queramos mandar de regreso, de momento lo dejamos vac\xedo y por eso es que la p\xe1gina se ve en blanco."}),"\n",(0,o.jsxs)(n.p,{children:["La funci\xf3n write solo escribe la respuesta en el ",(0,o.jsx)(n.strong,{children:"request"})," pero no lo manda, para ello es necesario hacer uso de la \xfaltima instrucci\xf3n."]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"response.end();\n"})}),"\n",(0,o.jsxs)(n.p,{children:["La funci\xf3n ",(0,o.jsx)(n.strong,{children:"end()"}),", es la encargada de mandar la respuesta al cliente y a partir de ello es que podemos ver la p\xe1gina en blanco en el navegador."]}),"\n",(0,o.jsxs)(n.p,{children:["Intenta a cambiar la url de ",(0,o.jsx)(n.strong,{children:"localhost:3000"})," con variantes de url y observa lo que pasa:"]}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"localhost:3000/unicorn\nlocalhost:3000/unicorn/error\n"})}),"\n",(0,o.jsxs)(n.p,{children:["Todas las respuestas en el navegador ser\xe1n p\xe1ginas en blanco, pues no estamos haciendo una distinci\xf3n para las url. Pero en la terminal ver\xe1s como cambia la primera url que se llama y que adem\xe1s van seguidas del ",(0,o.jsx)(n.strong,{children:"/favicon.ico"}),", tambi\xe9n nota que el servidor nunca se detiene y se combina la primera ejecuci\xf3n con las siguientes."]}),"\n",(0,o.jsx)(n.h2,{id:"ver-que-hace-el-servidor-desde-el-navegador",children:"Ver que hace el servidor desde el navegador"}),"\n",(0,o.jsxs)(n.p,{children:["Hoy en d\xeda, los servidores rastrean todas las peticiones que les hacen los clientes para mantener un mejor entendimiento de lo que se est\xe1 haciendo o solicitando. De la misma manera los navegadores han evolucionado tanto que es posible ver que acciones ejecuta el servidor, y si bien no podemos ver lo que sucede internamente podemos ver nuestros ",(0,o.jsx)(n.strong,{children:"request"})," y todas sus ",(0,o.jsx)(n.strong,{children:"responses"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["En un ciclo ideal, dentro de la arquitectura cliente-servidor siempre para todo ",(0,o.jsx)(n.strong,{children:"request"})," deber\xe1 existir un ",(0,o.jsx)(n.strong,{children:"response"}),", puede haber excepciones y para ello se manejan c\xf3digos especiales de error que veremos m\xe1s adelante."]}),"\n",(0,o.jsx)(n.p,{children:"Para ver este historial de solicitudes basta que hagamos clic derecho > seleccionemos inspeccionar elemento."}),"\n",(0,o.jsxs)(n.p,{children:["Dentro del men\xfa hasta ahora hemos explorado, la pesta\xf1a de ",(0,o.jsx)(n.strong,{children:"elements"})," para ver el contenido de la p\xe1gina y ",(0,o.jsx)(n.strong,{children:"console"})," para ver la salida del c\xf3digo de javascript. Ahora nos vamos a la pesta\xf1a ",(0,o.jsx)(n.strong,{children:"network"})," y procederemos a recargar la p\xe1gina."]}),"\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"lab_8",src:a(5289).A+"",width:"1920",height:"995"})}),"\n",(0,o.jsx)(n.p,{children:"Esta vista nos ayudar\xe1 mucho en el futuro, pues har\xe1 lo siguiente:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Nos muestra una lista de los request y sus respuestas, si seleccionamos cada uno podemos ver el contenido completo, adem\xe1s nos dice el tiempo de carga de cada cosa."}),"\n",(0,o.jsx)(n.li,{children:"Por otro lado de manera global nos indica el tiempo de carga del DOM, si estamos optimizando el tiempo de carga de la p\xe1gina es un buen sitio donde comenzar."}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"Con m\xe1s experiencia y conocimiento podr\xe1s obtener m\xe1s informaci\xf3n de lo que esta sucediendo entre el cliente y el servidor, de momento nos quedaremos as\xed."}),"\n",(0,o.jsx)(n.h2,{id:"devolver-c\xf3digo-html",children:"Devolver c\xf3digo HTML"}),"\n",(0,o.jsx)(n.p,{children:"Como ya vimos la instrucci\xf3n:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:'response.write("");\n'})}),"\n",(0,o.jsx)(n.p,{children:"Permite enviar texto de vuelta,y se permite el uso de HTML solo por que previamente usamos la instrucci\xf3n:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"response.setHeader('Content-Type', 'text/html');\n"})}),"\n",(0,o.jsx)(n.p,{children:"Vamos a enviar algo al servidor como:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:'response.write("hola mundo desde node");\n'})}),"\n",(0,o.jsx)(n.p,{children:"No olvides guardar el archivo y reiniciar el servidor."}),"\n",(0,o.jsxs)(n.p,{children:["Al volver a cargar la url, puede ser la primera, ",(0,o.jsx)(n.strong,{children:"localhost:3000"}),", veremos el siguiente resultado:"]}),"\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"lab_8",src:a(940).A+"",width:"1919",height:"994"})}),"\n",(0,o.jsxs)(n.p,{children:["Dentro de la p\xe1gina no hay problema, pero si exploramos el contenido html, veremos que se hace un c\xf3digo dif\xedcil de entender, a diferencia que hicimos lo mismo la primera vez con ",(0,o.jsx)(n.strong,{children:"index.html"}),", esto a se debe que el navegador entiende que se procesa una petici\xf3n y su respuesta no es un html estandarizado, por lo que aunque intenta hacerlo a\xf1ade m\xe1s c\xf3digo del necesario."]}),"\n",(0,o.jsx)(n.p,{children:"Vamos a modificar la respuesta con un html en regla, pero para ello usaremos el tercer tipo de comillas de Javascript para evitar conflictos con otro tipo de comillas."}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:'response.write(`\n \n \n \n \n HTML\n \n \n
hola mundo desde node
\n \n \n`);\n'})}),"\n",(0,o.jsx)(n.p,{children:"Nuevamente guardamos y reiniciamos el servidor. El resultado:"}),"\n",(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"lab_8",src:a(6107).A+"",width:"1920",height:"995"})}),"\n",(0,o.jsx)(n.p,{children:"El resultado es el h1 en negritas y que el contenido html tiene el formato esperado."}),"\n",(0,o.jsxs)(n.blockquote,{children:["\n",(0,o.jsx)(n.p,{children:"Nota: Ver\xe1s que a m\xed me aparecen 2 div extra\xf1os, estas son extensiones que tengo instaladas en chrome, y se a\xf1aden de manera autom\xe1tica a todo el c\xf3digo HTML de mi navegador. Si tu tienes extensiones instaladas el c\xf3digo puede variar."}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"\xc9xito, con esto ya podr\xedas incorporar p\xe1ginas HTML de tus laboratorios anteriores, pero si tratas de agregar archivos externos de CSS o Javascript estos no funcionar\xe1n, la \xfanica manera de que esto sirva es que agregue el c\xf3digo directamente en una etiqueta:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"\n"})}),"\n",(0,o.jsx)(n.p,{children:"\xf3"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{children:"\n //Mi c\xf3digo Javascript\n\n"})}),"\n",(0,o.jsx)(n.p,{children:"Si bien ya podr\xedamos trabajar creando p\xe1ginas web completas, esta no es la forma m\xe1s funcional de hacerlo. En pr\xf3ximos laboratorios veremos como optimizar esto separando en archivos las peticiones y para usar nuevamente nuestros archivos HTML, CSS y JS como en laboratorios anteriores por separado."}),"\n",(0,o.jsx)(n.p,{children:"Experimenta con el servidor, la \xfanica forma de que entiendas todos sus secretos es llevarlo al l\xedmite y ejecutar ver si es el resultado que realmente esperas."})]})}function u(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(t,{...e})}):t(e)}},5289:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/004-ed010d2287d7f44a21fb50fd3e8918af.jpg"},5351:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/002-de77d224db294bd3da92a04bb9e7e09b.jpg"},6107:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/006-d82f01f59619f1a762705ed84e059200.jpg"},8453:(e,n,a)=>{a.d(n,{R:()=>i,x:()=>d});var s=a(6540);const o={},r=s.createContext(o);function i(e){const n=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),s.createElement(r.Provider,{value:n},e.children)}},8856:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/images/001-864e0ff8193a5e9a52b48ffa27cb8ee8.jpg"},9546:(e,n,a)=>{a.d(n,{A:()=>s});const s="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAAbAG0DASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD87beMzTRxjq7BfzNd5DClvEsaDaijAFcRpv8AyELX/rqv8xXdV9rh1o2fNVuh6L4d+Ekd5Z6dPruuJocuqKHsLGG0e8u50OcP5SEFVODgk84NR/FT4Mav8LZIZbiWO/0ydtkV7CpX5sZ2sp+6euOSDjr1r3XxZ4Z8Orbf8LM07xXeaLe3Nsq2NwfLkt0PlbFiCGMkZCkEZ4Oa4/xt8UNT8afA/UJvEGnQab9rlt4NNZQ2+6kVw8soB6IFGMjjLEZrlhXqSkpLbZr/AC9DaVKEYtPfofPNaXiTw9qHhHxFqmhatb/ZNV0u6lsru33q/lzRuUddykqcMpGQSDjg1m19a3WpeENZ+Kni3UBdeEr7RLr4h6tP4rutUksZJZvD7TwtC9i05MjMVOoEGw/fEmPv5Fd9So4NaHLGPMfJVX9F0O+8RXklrp8H2ieO2uLtk3quIoIXmlbLED5Y43bHU4wMkgV6Pqnl/wDCktH/AOEf/wCEZ+x/ZW/4SL7V/Z/9r/b/ALfLs8nzv9M8v7P9iz9m/dY8zPPnV6t44ufBdjceH9UsL7QPt82leK7GXUdOuNNt1v7b+yNtjO1nZRxLZtNLNOFgnU3AJ8t3fagWZVWtEu/4DVO/XsfL2paXc6RcJBdxeVK8MVwq7g2Y5Y1kjPB7o6nHUZwcGqldx441iz1SVIYhZMlvoumbZocGSSdba2RwXyTlRvQoCFGzO3cCTw9bRbauzN6Ms2+m3l1Z3V3DazzWtrtNxPHGWSHccLvYDC5PAz1NFxpt5a2drdzWs8NrdbjbzyRlUm2nDbGIw2DwcdDXsH7MPi3TPAuv6/rfiHXYLbwtDp7R6n4ckQSv4hR8qtokR+X72GMh/wBWBuHNL+0/4s0zx14g0DW/Duuw3XhWbT1j0zw5Gghfw8iYVrR4h8o+bLCQZ8wHceay9o/acltO5pyrk5r6nht/areWkkTDORx7Hsa4WvQq89rHELVM1o9UWtKjMmpWwH/PRT+RzXcVzPhWNWuJnK5ZVGD6Zrpq0oK0bmdZ+9Y6fwt8SvE3gu3kt9H1aS1tpDuaBkSWPPqFcEA+4FZ3iXxZq/jDUPtus381/c42hpDwo9FUcKPYAVk0VtyRT5ktTPmla19AoooqyQooooAKKKKACiiigArz+RDHIyHqpwa9ArkPEcax6m21cblDH61y4haJm9F6tH//2Q=="}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/2359e095.885cbc66.js b/deprecated/old-docs-build/docs/assets/js/2359e095.885cbc66.js
new file mode 100644
index 0000000..4c20eca
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/2359e095.885cbc66.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[6397],{110:(n,e,i)=>{i.r(e),i.d(e,{assets:()=>d,contentTitle:()=>a,default:()=>h,frontMatter:()=>c,metadata:()=>s,toc:()=>o});const s=JSON.parse('{"id":"backend/node/tutorials/README","title":"Tutoriales","description":"Los laboratorios son extensiones de lo que vemos en clase, siempre revisa el material ya que puede haber detalles que por tiempo no alcancemos a ver y que es tu responsabilidad entender para no retrasarte en las bases de lo que tienes que aprender del curso.","source":"@site/docs/backend/node/tutorials/README.md","sourceDirName":"backend/node/tutorials","slug":"/backend/node/tutorials/","permalink":"/docs/docs/backend/node/tutorials/","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/backend/node/tutorials/README.md","tags":[],"version":"current","frontMatter":{},"sidebar":"tutorialSidebar","previous":{"title":"NodeJS","permalink":"/docs/docs/backend/node/"},"next":{"title":"Introducci\xf3n a Web","permalink":"/docs/docs/category/introducci\xf3n-a-web"}}');var r=i(4848),l=i(8453);const c={},a="Tutoriales",d={},o=[];function t(n){const e={a:"a",h1:"h1",header:"header",li:"li",p:"p",ul:"ul",...(0,l.R)(),...n.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(e.header,{children:(0,r.jsx)(e.h1,{id:"tutoriales",children:"Tutoriales"})}),"\n",(0,r.jsx)(e.p,{children:"Los laboratorios son extensiones de lo que vemos en clase, siempre revisa el material ya que puede haber detalles que por tiempo no alcancemos a ver y que es tu responsabilidad entender para no retrasarte en las bases de lo que tienes que aprender del curso."}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab1HTML/",children:"LAB1 - Introducci\xf3n a HTML"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Plantilla b\xe1sica de HTML"}),"\n",(0,r.jsx)(e.li,{children:"Introducci\xf3n a HTML"}),"\n",(0,r.jsx)(e.li,{children:"Est\xe1ndares adicionales de transmisi\xf3n de informaci\xf3n HTML, XML, JSON"}),"\n",(0,r.jsx)(e.li,{children:"Etiquetas HTML"}),"\n",(0,r.jsx)(e.li,{children:"Atributos HTML"}),"\n",(0,r.jsx)(e.li,{children:"Formularios HTML"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab2Git/",children:"LAB2 - Control de versiones"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Introducci\xf3n b\xe1sica"}),"\n",(0,r.jsx)(e.li,{children:"Requerimientos Previos"}),"\n",(0,r.jsx)(e.li,{children:"Manejo b\xe1sico de repositorios"}),"\n",(0,r.jsx)(e.li,{children:"Manejo de repositorios en la nube"}),"\n",(0,r.jsx)(e.li,{children:"Conclusi\xf3n"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab4JS/",children:"LAB4 - Introducci\xf3n a Javascript"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Inline Scripting"}),"\n",(0,r.jsx)(e.li,{children:"Estructura de c\xf3digo"}),"\n",(0,r.jsx)(e.li,{children:"UseStrict"}),"\n",(0,r.jsx)(e.li,{children:"Variables"}),"\n",(0,r.jsx)(e.li,{children:"Tipos de datos"}),"\n",(0,r.jsx)(e.li,{children:"Interacci\xf3n: Alert, Prompt, Confirm"}),"\n",(0,r.jsx)(e.li,{children:"Conversiones de Tipos"}),"\n",(0,r.jsx)(e.li,{children:"Operadores B\xe1sicos Matem\xe1ticos"}),"\n",(0,r.jsx)(e.li,{children:"Comparaciones"}),"\n",(0,r.jsx)(e.li,{children:"Condicionales"}),"\n",(0,r.jsx)(e.li,{children:"Ciclos"}),"\n",(0,r.jsx)(e.li,{children:"Funciones"}),"\n",(0,r.jsx)(e.li,{children:"Arreglos y Objetos"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab6POE/",children:"LAB6 - Programaci\xf3n Orientada a Eventos"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"El DOM"}),"\n",(0,r.jsx)(e.li,{children:"\xbfC\xf3mo seleccionar elementos en el documento?"}),"\n",(0,r.jsx)(e.li,{children:"Usar eventos con Javascript"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab7Branches/",children:"LAB7 - Manejo de Ramas"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Introducci\xf3n a las ramas"}),"\n",(0,r.jsx)(e.li,{children:"Cambio entre ramas y obtener la \xfaltima versi\xf3n"}),"\n",(0,r.jsx)(e.li,{children:"Gitflow Simplificado"}),"\n",(0,r.jsx)(e.li,{children:"Hacer merge de las ramas con Pull Request"}),"\n",(0,r.jsx)(e.li,{children:"Estrategias de Merge (merge, rebase, squash+merge)"}),"\n",(0,r.jsx)(e.li,{children:"Actualizando cambios remotos"}),"\n",(0,r.jsx)(e.li,{children:"Actualizar main o master"}),"\n",(0,r.jsx)(e.li,{children:"A\xf1adiendo etiquetas a versiones terminadas"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab8IntroBackend/",children:"LAB8 - Introducci\xf3n al Backend"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Front-end y back-end"}),"\n",(0,r.jsx)(e.li,{children:"Front-end"}),"\n",(0,r.jsx)(e.li,{children:"Back-end"}),"\n",(0,r.jsx)(e.li,{children:"Full-stack"}),"\n",(0,r.jsx)(e.li,{children:"NodeJS"}),"\n",(0,r.jsx)(e.li,{children:"Hello World"}),"\n",(0,r.jsx)(e.li,{children:"Filesystem"}),"\n",(0,r.jsx)(e.li,{children:"Async Sort"}),"\n",(0,r.jsx)(e.li,{children:"C\xf3digo As\xedncrono"}),"\n",(0,r.jsx)(e.li,{children:"Creando un servidor"}),"\n",(0,r.jsx)(e.li,{children:"Ver que hace el servidor desde el navegador"}),"\n",(0,r.jsx)(e.li,{children:"Devolver c\xf3digo HTML"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab10RutasYFormas/",children:"LAB10 - Rutas y Formas"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Rutas"}),"\n",(0,r.jsx)(e.li,{children:"Formas"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab11Express/",children:"LAB11 - Express"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"npm"}),"\n",(0,r.jsx)(e.li,{children:".gitignore"}),"\n",(0,r.jsx)(e.li,{children:"npm init"}),"\n",(0,r.jsx)(e.li,{children:"B\xe1sicos de express (Middlewares)"}),"\n",(0,r.jsx)(e.li,{children:"pm2"}),"\n",(0,r.jsx)(e.li,{children:"Rutas con express"}),"\n",(0,r.jsx)(e.li,{children:"Separando en clases"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab12EJS/",children:"LAB12 - HTML Din\xe1mico"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Carpeta p\xfablica"}),"\n",(0,r.jsx)(e.li,{children:"EJS"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/LAB13MVC/",children:"LAB13 - MVC"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"MVC (Modelo Vista Controlador)"}),"\n",(0,r.jsx)(e.li,{children:"Rutas y Controladores"}),"\n",(0,r.jsx)(e.li,{children:"Modelos"}),"\n",(0,r.jsx)(e.li,{children:"Vistas"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/LAB14Sesiones/",children:"LAB14 - Sesiones"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Cookies"}),"\n",(0,r.jsx)(e.li,{children:"Express session"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab17BD/",children:"LAB17 - Interacci\xf3n con la Base de datos"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Conexi\xf3n con MariaDB"}),"\n",(0,r.jsx)(e.li,{children:"Funciones As\xedncronas"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab18Autenticacion/",children:"LAB18 - Autenticaci\xf3n"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Registrar un usuario"}),"\n",(0,r.jsx)(e.li,{children:"Encriptaci\xf3n de la contrase\xf1a"}),"\n",(0,r.jsx)(e.li,{children:"Comparaci\xf3n de la contrase\xf1a"}),"\n",(0,r.jsx)(e.li,{children:"Middleware de autenticaci\xf3n de sesi\xf3n"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab19RBAC/",children:"LAB19 - RBAC"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Crear la base de datos"}),"\n",(0,r.jsx)(e.li,{children:"Obtener permisos de los usuarios"}),"\n",(0,r.jsx)(e.li,{children:"Interfaz gr\xe1fica din\xe1mica"}),"\n",(0,r.jsx)(e.li,{children:"Protecci\xf3n de rutas por permiso"}),"\n",(0,r.jsx)(e.li,{children:"Cerrar sesi\xf3n"}),"\n",(0,r.jsx)(e.li,{children:"Asignar privilegio a un nuevo usuario"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab22Archivos/",children:"LAB22 - Archivos"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Uso de multer"}),"\n",(0,r.jsx)(e.li,{children:"Subiendo archivos p\xfablicos"}),"\n",(0,r.jsx)(e.li,{children:"Consulta de archivos p\xfablicos"}),"\n",(0,r.jsx)(e.li,{children:"Consulta de archivos privados"}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(e.li,{children:[(0,r.jsx)(e.a,{href:"./intro_web/Lab24AJAX/",children:"LAB24 - AJAX"}),"\n",(0,r.jsxs)(e.ul,{children:["\n",(0,r.jsx)(e.li,{children:"Introducci\xf3n"}),"\n",(0,r.jsx)(e.li,{children:"Empezando con un API"}),"\n",(0,r.jsx)(e.li,{children:"Event load"}),"\n",(0,r.jsx)(e.li,{children:"Agregar producto AJAX"}),"\n",(0,r.jsx)(e.li,{children:"Cargar lista de productos AJAX"}),"\n",(0,r.jsx)(e.li,{children:"Data tables AJAX"}),"\n"]}),"\n"]}),"\n"]})]})}function h(n={}){const{wrapper:e}={...(0,l.R)(),...n.components};return e?(0,r.jsx)(e,{...n,children:(0,r.jsx)(t,{...n})}):t(n)}},8453:(n,e,i)=>{i.d(e,{R:()=>c,x:()=>a});var s=i(6540);const r={},l=s.createContext(r);function c(n){const e=s.useContext(l);return s.useMemo((function(){return"function"==typeof n?n(e):{...e,...n}}),[e,n])}function a(n){let e;return e=n.disableParentContext?"function"==typeof n.components?n.components(r):n.components||r:c(n.components),s.createElement(l.Provider,{value:e},n.children)}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/276c1c32.cc4f3577.js b/deprecated/old-docs-build/docs/assets/js/276c1c32.cc4f3577.js
new file mode 100644
index 0000000..8dcdf26
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/276c1c32.cc4f3577.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3668],{2255:s=>{s.exports=JSON.parse('{"tag":{"label":"Docusaurus","permalink":"/docs/blog/tags/docusaurus","description":"Docusaurus tag description","allTagsPath":"/docs/blog/tags","count":4,"unlisted":false},"listMetadata":{"permalink":"/docs/blog/tags/docusaurus","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/33fc5bb8.c55273be.js b/deprecated/old-docs-build/docs/assets/js/33fc5bb8.c55273be.js
new file mode 100644
index 0000000..0473195
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/33fc5bb8.c55273be.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[867],{778:(e,t,a)=>{a.r(t),a.d(t,{default:()=>b});a(6540);var s=a(4164),n=a(5500),r=a(7559),i=a(6461),o=a(8774),l=a(4096),c=a(8027),d=a(7713),g=a(1463),u=a(3892),h=a(9907),m=a(4848);function p({author:e}){const t=(0,i.wI)(e);return(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(n.be,{title:t}),(0,m.jsx)(g.A,{tag:"blog_authors_posts"})]})}function x(){const{authorsListPath:e}=(0,l.x)();return(0,m.jsx)(o.A,{href:e,children:(0,m.jsx)(i.np,{})})}function j({author:e,items:t,sidebar:a,listMetadata:s}){return(0,m.jsxs)(c.A,{sidebar:a,children:[(0,m.jsxs)("header",{className:"margin-bottom--xl",children:[(0,m.jsx)(h.A,{as:"h1",author:e}),e.description&&(0,m.jsx)("p",{children:e.description}),(0,m.jsx)(x,{})]}),0===t.length?(0,m.jsx)("p",{children:(0,m.jsx)(i.Y4,{})}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)("hr",{}),(0,m.jsx)(u.A,{items:t}),(0,m.jsx)(d.A,{metadata:s})]})]})}function b(e){return(0,m.jsxs)(n.e3,{className:(0,s.A)(r.G.wrapper.blogPages,r.G.page.blogAuthorsPostsPage),children:[(0,m.jsx)(p,{...e}),(0,m.jsx)(j,{...e})]})}},2907:(e,t,a)=>{a.d(t,{A:()=>C});a(6540);var s=a(4164),n=a(4096),r=a(4848);function i({children:e,className:t}){return(0,r.jsx)("article",{className:t,children:e})}var o=a(8774);const l={title:"title_f1Hy"};function c({className:e}){const{metadata:t,isBlogPostPage:a}=(0,n.e7)(),{permalink:i,title:c}=t,d=a?"h1":"h2";return(0,r.jsx)(d,{className:(0,s.A)(l.title,e),children:a?c:(0,r.jsx)(o.A,{to:i,children:c})})}var d=a(1312),g=a(5846),u=a(6266);const h={container:"container_mt6G"};function m({readingTime:e}){const t=function(){const{selectMessage:e}=(0,g.W)();return t=>{const a=Math.ceil(t);return e(a,(0,d.T)({id:"theme.blog.post.readingTime.plurals",description:'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One min read|{readingTime} min read"},{readingTime:a}))}}();return(0,r.jsx)(r.Fragment,{children:t(e)})}function p({date:e,formattedDate:t}){return(0,r.jsx)("time",{dateTime:e,children:t})}function x(){return(0,r.jsx)(r.Fragment,{children:" \xb7 "})}function j({className:e}){const{metadata:t}=(0,n.e7)(),{date:a,readingTime:i}=t,o=(0,u.i)({day:"numeric",month:"long",year:"numeric",timeZone:"UTC"});return(0,r.jsxs)("div",{className:(0,s.A)(h.container,"margin-vert--md",e),children:[(0,r.jsx)(p,{date:a,formattedDate:(l=a,o.format(new Date(l)))}),void 0!==i&&(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(x,{}),(0,r.jsx)(m,{readingTime:i})]})]});var l}var b=a(9907);const f={authorCol:"authorCol_Hf19",imageOnlyAuthorRow:"imageOnlyAuthorRow_pa_O",imageOnlyAuthorCol:"imageOnlyAuthorCol_G86a"};function A({className:e}){const{metadata:{authors:t},assets:a}=(0,n.e7)();if(0===t.length)return null;const i=t.every((({name:e})=>!e)),o=1===t.length;return(0,r.jsx)("div",{className:(0,s.A)("margin-top--md margin-bottom--sm",i?f.imageOnlyAuthorRow:"row",e),children:t.map(((e,t)=>(0,r.jsx)("div",{className:(0,s.A)(!i&&(o?"col col--12":"col col--6"),i?f.imageOnlyAuthorCol:f.authorCol),children:(0,r.jsx)(b.A,{author:{...e,imageURL:a.authorsImageUrls[t]??e.imageURL}})},t)))})}function v(){return(0,r.jsxs)("header",{children:[(0,r.jsx)(c,{}),(0,r.jsx)(j,{}),(0,r.jsx)(A,{})]})}var N=a(440),T=a(540);function w({children:e,className:t}){const{isBlogPostPage:a}=(0,n.e7)();return(0,r.jsx)("div",{id:a?N.LU:void 0,className:(0,s.A)("markdown",t),children:(0,r.jsx)(T.A,{children:e})})}var _=a(7559),P=a(4336),k=a(4434);function y(){return(0,r.jsx)("b",{children:(0,r.jsx)(d.A,{id:"theme.blog.post.readMore",description:"The label used in blog post item excerpts to link to full blog posts",children:"Read more"})})}function R(e){const{blogPostTitle:t,...a}=e;return(0,r.jsx)(o.A,{"aria-label":(0,d.T)({message:"Read more about {title}",id:"theme.blog.post.readMoreLabel",description:"The ARIA label for the link to full blog posts from excerpts"},{title:t}),...a,children:(0,r.jsx)(y,{})})}function U(){const{metadata:e,isBlogPostPage:t}=(0,n.e7)(),{tags:a,title:i,editUrl:o,hasTruncateMarker:l,lastUpdatedBy:c,lastUpdatedAt:d}=e,g=!t&&l,u=a.length>0;if(!(u||g||o))return null;if(t){const e=!!(o||d||c);return(0,r.jsxs)("footer",{className:"docusaurus-mt-lg",children:[u&&(0,r.jsx)("div",{className:(0,s.A)("row","margin-top--sm",_.G.blog.blogFooterEditMetaRow),children:(0,r.jsx)("div",{className:"col",children:(0,r.jsx)(k.A,{tags:a})})}),e&&(0,r.jsx)(P.A,{className:(0,s.A)("margin-top--sm",_.G.blog.blogFooterEditMetaRow),editUrl:o,lastUpdatedAt:d,lastUpdatedBy:c})]})}return(0,r.jsxs)("footer",{className:"row docusaurus-mt-lg",children:[u&&(0,r.jsx)("div",{className:(0,s.A)("col",{"col--9":g}),children:(0,r.jsx)(k.A,{tags:a})}),g&&(0,r.jsx)("div",{className:(0,s.A)("col text--right",{"col--3":u}),children:(0,r.jsx)(R,{blogPostTitle:i,to:e.permalink})})]})}function C({children:e,className:t}){const a=function(){const{isBlogPostPage:e}=(0,n.e7)();return e?void 0:"margin-bottom--xl"}();return(0,r.jsxs)(i,{className:(0,s.A)(a,t),children:[(0,r.jsx)(v,{}),(0,r.jsx)(w,{children:e}),(0,r.jsx)(U,{})]})}},3892:(e,t,a)=>{a.d(t,{A:()=>i});a(6540);var s=a(4096),n=a(2907),r=a(4848);function i({items:e,component:t=n.A}){return(0,r.jsx)(r.Fragment,{children:e.map((({content:e})=>(0,r.jsx)(s.in,{content:e,children:(0,r.jsx)(t,{children:(0,r.jsx)(e,{})})},e.metadata.permalink)))})}},4434:(e,t,a)=>{a.d(t,{A:()=>l});a(6540);var s=a(4164),n=a(1312),r=a(6133);const i={tags:"tags_jXut",tag:"tag_QGVx"};var o=a(4848);function l({tags:e}){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("b",{children:(0,o.jsx)(n.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,o.jsx)("ul",{className:(0,s.A)(i.tags,"padding--none","margin-left--sm"),children:e.map((e=>(0,o.jsx)("li",{className:i.tag,children:(0,o.jsx)(r.A,{...e})},e.permalink)))})]})}},6133:(e,t,a)=>{a.d(t,{A:()=>o});a(6540);var s=a(4164),n=a(8774);const r={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var i=a(4848);function o({permalink:e,label:t,count:a,description:o}){return(0,i.jsxs)(n.A,{rel:"tag",href:e,title:o,className:(0,s.A)(r.tag,a?r.tagWithCount:r.tagRegular),children:[t,a&&(0,i.jsx)("span",{children:a})]})}},6461:(e,t,a)=>{a.d(t,{Y4:()=>g,ZD:()=>o,np:()=>d,uz:()=>c,wI:()=>l});a(6540);var s=a(1312),n=a(5846),r=a(4848);function i(){const{selectMessage:e}=(0,n.W)();return t=>e(t,(0,s.T)({id:"theme.blog.post.plurals",description:'Pluralized label for "{count} posts". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One post|{count} posts"},{count:t}))}function o(e){const t=i();return(0,s.T)({id:"theme.blog.tagTitle",description:"The title of the page for a blog tag",message:'{nPosts} tagged with "{tagName}"'},{nPosts:t(e.count),tagName:e.label})}function l(e){const t=i();return(0,s.T)({id:"theme.blog.author.pageTitle",description:"The title of the page for a blog author",message:"{authorName} - {nPosts}"},{nPosts:t(e.count),authorName:e.name||e.key})}const c=()=>(0,s.T)({id:"theme.blog.authorsList.pageTitle",message:"Authors",description:"The title of the authors page"});function d(){return(0,r.jsx)(s.A,{id:"theme.blog.authorsList.viewAll",description:"The label of the link targeting the blog authors page",children:"View all authors"})}function g(){return(0,r.jsx)(s.A,{id:"theme.blog.author.noPosts",description:"The text for authors with 0 blog post",children:"This author has not written any posts yet."})}},7713:(e,t,a)=>{a.d(t,{A:()=>i});a(6540);var s=a(1312),n=a(9022),r=a(4848);function i(e){const{metadata:t}=e,{previousPage:a,nextPage:i}=t;return(0,r.jsxs)("nav",{className:"pagination-nav","aria-label":(0,s.T)({id:"theme.blog.paginator.navAriaLabel",message:"Blog list page navigation",description:"The ARIA label for the blog pagination"}),children:[a&&(0,r.jsx)(n.A,{permalink:a,title:(0,r.jsx)(s.A,{id:"theme.blog.paginator.newerEntries",description:"The label used to navigate to the newer blog posts page (previous page)",children:"Newer entries"})}),i&&(0,r.jsx)(n.A,{permalink:i,title:(0,r.jsx)(s.A,{id:"theme.blog.paginator.olderEntries",description:"The label used to navigate to the older blog posts page (next page)",children:"Older entries"}),isNext:!0})]})}},9022:(e,t,a)=>{a.d(t,{A:()=>i});a(6540);var s=a(4164),n=a(8774),r=a(4848);function i(e){const{permalink:t,title:a,subLabel:i,isNext:o}=e;return(0,r.jsxs)(n.A,{className:(0,s.A)("pagination-nav__link",o?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[i&&(0,r.jsx)("div",{className:"pagination-nav__sublabel",children:i}),(0,r.jsx)("div",{className:"pagination-nav__label",children:a})]})}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/36994c47.56f87c0d.js b/deprecated/old-docs-build/docs/assets/js/36994c47.56f87c0d.js
new file mode 100644
index 0000000..4fbfede
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/36994c47.56f87c0d.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9858],{5516:e=>{e.exports=JSON.parse('{"name":"docusaurus-plugin-content-blog","id":"default"}')}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/393be207.7347f3dc.js b/deprecated/old-docs-build/docs/assets/js/393be207.7347f3dc.js
new file mode 100644
index 0000000..b49db12
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/393be207.7347f3dc.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4134],{591:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>c,contentTitle:()=>p,default:()=>l,frontMatter:()=>s,metadata:()=>a,toc:()=>d});const a=JSON.parse('{"type":"mdx","permalink":"/docs/markdown-page","source":"@site/src/pages/markdown-page.md","title":"Markdown page example","description":"You don\'t need React to write simple standalone pages.","frontMatter":{"title":"Markdown page example"},"unlisted":false}');var o=n(4848),r=n(8453);const s={title:"Markdown page example"},p="Markdown page example",c={},d=[];function i(e){const t={h1:"h1",header:"header",p:"p",...(0,r.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.header,{children:(0,o.jsx)(t.h1,{id:"markdown-page-example",children:"Markdown page example"})}),"\n",(0,o.jsx)(t.p,{children:"You don't need React to write simple standalone pages."})]})}function l(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(i,{...e})}):i(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>s,x:()=>p});var a=n(6540);const o={},r=a.createContext(o);function s(e){const t=a.useContext(r);return a.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function p(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),a.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/40a1fd0e.f0f81dc5.js b/deprecated/old-docs-build/docs/assets/js/40a1fd0e.f0f81dc5.js
new file mode 100644
index 0000000..86e0f15
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/40a1fd0e.f0f81dc5.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[1150],{218:(e,a,r)=>{r.r(a),r.d(a,{assets:()=>d,contentTitle:()=>t,default:()=>u,frontMatter:()=>i,metadata:()=>n,toc:()=>l});const n=JSON.parse('{"id":"backend/node/tutorials/intro_web/Lab18Autenticacion/readme","title":"Autenticaci\xf3n","description":"Registrar un usuario","source":"@site/docs/backend/node/tutorials/intro_web/Lab18Autenticacion/readme.md","sourceDirName":"backend/node/tutorials/intro_web/Lab18Autenticacion","slug":"/backend/node/tutorials/intro_web/Lab18Autenticacion/","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab18Autenticacion/","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/backend/node/tutorials/intro_web/Lab18Autenticacion/readme.md","tags":[],"version":"current","sidebarPosition":18,"frontMatter":{"sidebar_position":18},"sidebar":"tutorialSidebar","previous":{"title":"Interacci\xf3n con la Base de Datos","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab17BD/"},"next":{"title":"RBAC (Role-based access control)","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab19RBAC/"}}');var s=r(4848),o=r(8453);const i={sidebar_position:18},t="Autenticaci\xf3n",d={},l=[{value:"Registrar un usuario",id:"registrar-un-usuario",level:2},{value:"Renderizar el EJS del Registro",id:"renderizar-el-ejs-del-registro",level:3},{value:"Registrar un usuario en la BD",id:"registrar-un-usuario-en-la-bd",level:3},{value:"Encriptaci\xf3n de la contrase\xf1a",id:"encriptaci\xf3n-de-la-contrase\xf1a",level:2},{value:"Comparaci\xf3n de la contrase\xf1a",id:"comparaci\xf3n-de-la-contrase\xf1a",level:2},{value:"Middleware de autenticaci\xf3n de sesi\xf3n",id:"middleware-de-autenticaci\xf3n-de-sesi\xf3n",level:2}];function c(e){const a={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(a.header,{children:(0,s.jsx)(a.h1,{id:"autenticaci\xf3n",children:"Autenticaci\xf3n"})}),"\n",(0,s.jsx)(a.h2,{id:"registrar-un-usuario",children:"Registrar un usuario"}),"\n",(0,s.jsx)(a.p,{children:"Con lo que hemos aprendido en pr\xe1cticas anteriores hemos conocido nuestro front-end y nuestro back-end. Hemos logrado crear una estructura que nos permite trabajar con nuestros proyectos y m\xe1s all\xe1 hemos logrado conectarnos con una fuente de datos para transmitir informaci\xf3n. Hasta el momento hemos cubierto las piezas de lo que significa el desarrollo web, pero ahora es momento de empezar a pegar todo lo que hemos aprendido y realmente empecemos a crear proyectos extraordinarios."}),"\n",(0,s.jsx)(a.p,{children:"En esta pr\xe1ctica trabajaremos en el manejo de sesi\xf3n de usuario, que como vimos en lecciones anteriores hace uso de las cookies para manejar la sesi\xf3n. Por s\xed sola la sesi\xf3n no nos sirve de nada, necesitamos haber establecido un proceso para ellos, ya que empiezas a conocer sobre UML y su notaci\xf3n, vamos a empezar a trabajar con Diagramas de Secuencia para definir nuestros casos de Uso."}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(9954).A+"",width:"1008",height:"1037"})}),"\n",(0,s.jsx)(a.p,{children:"Dentro de este diagrama podemos identificar el caso de uso de registrar usuario."}),"\n",(0,s.jsx)(a.p,{children:"Dependiendo de la forma en que trabajemos podemos separar este diagrama en partes o trabajarlo todo como uno solo tal y como viene en el ejemplo, un buen ingeniero de software no piensa en los detalles de cuantos o como mostrarlo, sino que el diagrama explique tal cual como est\xe1 construido el elemento de software."}),"\n",(0,s.jsx)(a.p,{children:"Vamos a tomar como referencia nuestro laboratorio de MVC para practicar, te dejo una copia que puedes descargar para comenzar con la plantilla que ya ten\xedamos."}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.a,{target:"_blank","data-noBrokenLinkCheck":!0,href:r(3884).A+"",children:"Descargar plantilla proyecto"})}),"\n",(0,s.jsx)(a.p,{children:"Para correr el proyecto no te olvides de ejecutar:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"npm install\npm2 start index.js --watch\npm2 logs\n"})}),"\n",(0,s.jsx)(a.p,{children:"Ya tenemos la \xfaltima versi\xf3n de nuestro servidor corriendo, por lo que podemos empezar a trabajar con nuestro caso de uso."}),"\n",(0,s.jsx)(a.h3,{id:"renderizar-el-ejs-del-registro",children:"Renderizar el EJS del Registro"}),"\n",(0,s.jsxs)(a.p,{children:["En primer lugar vamos a establecer el camino a seguir cuando el usuario sigue la ruta ",(0,s.jsx)(a.strong,{children:"/registro"})," con un ",(0,s.jsx)(a.strong,{children:"GET"}),". Como ya hemos visto el uso del GET en una url, por lo general hace que carguemos una vista o un EJS."]}),"\n",(0,s.jsxs)(a.p,{children:["Vamos a nuestra carpeta de ",(0,s.jsx)(a.strong,{children:"views"})," y dentro de ",(0,s.jsx)(a.strong,{children:"usuarios"})," ya ten\xedamos definido ",(0,s.jsx)(a.strong,{children:"registro.ejs"})," pero no agregamos ning\xfan c\xf3digo de EJS. Vamos a agregarlo:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:'\n \n <%- include(\'./../css.ejs\') %>\n \n \n
<% if (registro) { %>\xa1\xdanete al lado oscuro, tenemos galletas!<% } else { %>Bienvenido<% } %>
\n \n \n <%- include(\'./../scripts.ejs\') %>\n\n'})}),"\n",(0,s.jsx)(a.p,{children:"Usaremos este mismo ejs para cargar m\xe1s adelante el login, observa como utilizamos el EJS para diferenciar entre un usuario ya existente y uno que apenas va comenzando."}),"\n",(0,s.jsxs)(a.p,{children:["Antes de correr nuevamente la aplicaci\xf3n en el navegador, nos hace falta agregar la funci\xf3n en ",(0,s.jsx)(a.strong,{children:"usuarios.controller.js"})," y actualizar ",(0,s.jsx)(a.strong,{children:"usuarios.routes.js"}),"."]}),"\n",(0,s.jsxs)(a.p,{children:["Para hacerte entender el diagrama y la construcci\xf3n del mismo, voy a obviar algunos pasos para obligarte a que lo est\xe9s revisando y sepas donde colocar el m\xe9todo, como este es el primero te dar\xe9 una ayuda. En ",(0,s.jsx)(a.strong,{children:"usuarios.controller.js"})," agrega lo siguiente:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:'module.exports.get_registro = async(req,res) =>{\n res.render("usuarios/registro",{registro:true});\n}\n'})}),"\n",(0,s.jsx)(a.p,{children:"Ahora actualicemos la ruta:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"router.get('/registro', controller.get_registro);\n"})}),"\n",(0,s.jsx)(a.p,{children:"Guarda todos los archivos y corre en tu navegador la ruta correspondiente, deber\xedas ver lo siguiente:"}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(1281).A+"",width:"1218",height:"299"})}),"\n",(0,s.jsx)(a.h3,{id:"registrar-un-usuario-en-la-bd",children:"Registrar un usuario en la BD"}),"\n",(0,s.jsxs)(a.blockquote,{children:["\n",(0,s.jsx)(a.p,{children:"Nota: Para la siguiente secci\xf3n aseg\xfarate que tu base de datos este corriendo y este bien configurada como vimos en el laboratorio anterior."}),"\n"]}),"\n",(0,s.jsx)(a.p,{children:"Ahora bien, ya tenemos nuestra vista para que el usuario introduzca informaci\xf3n, ahora es momento de obtener dicha informaci\xf3n a trav\xe9s del form que contiene nuestro EJS."}),"\n",(0,s.jsx)(a.p,{children:"Del lado del EJS, no necesitamos agregar nada m\xe1s, pero ahora debemos recibirlo en nuestra ruta y controlador correspondiente, vamos a a\xf1adir el c\xf3digo, recuerda verificar el diagrama de secuencia para saber exactamente en donde."}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"module.exports.post_registro = async(req,res) =>{\n console.log(req.body.username);\n console.log(req.body.password);\n}\n"})}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"router.post('/registro', controller.post_registro);\n"})}),"\n",(0,s.jsx)(a.p,{children:"Vamos nuevamente a nuestro navegador y vamos a intentar cargar nuevamente la vista y agreguemos la informaci\xf3n, al enviar el formulario deber\xedamos obtener:"}),"\n",(0,s.jsx)(a.p,{children:"En el navegador veremos:"}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(3236).A+"",width:"1251",height:"295"})}),"\n",(0,s.jsx)(a.p,{children:"Y en la consola deber\xedamos ver:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"0|index | user\n0|index | demo\n"})}),"\n",(0,s.jsx)(a.p,{children:"Muy bien, logramos subir nuestros datos al servidor."}),"\n",(0,s.jsx)(a.p,{children:"Ahora vamos a ver un paso que no hemos realizado hasta ahora y es como definir nuestros modelos, dentro haciendo la conexi\xf3n a la Base de Datos."}),"\n",(0,s.jsxs)(a.p,{children:["Si tomamos como base nuestro ya definido ",(0,s.jsx)(a.strong,{children:"usuarios.model.js"}),", ten\xedamos lo anterior:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:'exports.login = function(correo,contrasena){\n return {\n nombre:"Samuel",\n id:1,\n activo:true\n };\n}\n'})}),"\n",(0,s.jsxs)(a.p,{children:["Aqu\xed definimos una funci\xf3n para manejar los datos, pero idealmente una clase de base de datos y un modelo incluyen las ",(0,s.jsx)(a.strong,{children:"Entidades"}),' y las funciones que ejecutan. Las entidades no son m\xe1s que las propiedades que contienen nuestras tablas de la base de datos, en otras palabras el resultado de los queries que vamos a obtener y por lo general van alineados a las tablas, en queries complejos puedes crear entidades complejas que incluyan m\xe1s datos que una tabla normal, pero una de las razones de "duplicar la informaci\xf3n", es que necesitamos protegernos ante cualquier ataque a la base de datos por lo que evitamos responder con los datos tal cual como vienen de la misma.']}),"\n",(0,s.jsx)(a.p,{children:"Aqu\xed es donde podemos hacer uso de las clases y objetos de javascript, creando algo como lo siguiente:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"exports.User = class {\n //Constructor de la clase. Sirve para crear un nuevo objeto, y en \xe9l se definen las propiedades del modelo\n constructor(my_username, my_name, my_password) {\n this.username = my_username;\n this.name = my_name;\n this.password = my_password;\n }\n //Este m\xe9todo servir\xe1 para guardar de manera persistente el nuevo objeto. \n async save() {\n try {\n const connection = await db();\n const result = await connection.execute(\n `INSERT INTO users (username, name, password) VALUES (?, ?, ?)`,\n [this.username, this.name, this.password]\n );\n await connection.release();\n return result;\n } catch (error) {\n throw error; // Re-throw the error for proper handling\n }\n }\n //Este m\xe9todo servir\xe1 para buscar un usuario por username\n //Es est\xe1tico ya que a diferencia de save(), el primero se guarda al crear un usuario siempre, pero en este segundo podemos buscar un usuario sin crear un nuevo objeto usuario.\n static async findUser(username) {\n try {\n const connection = await db();\n const result = await connection.execute('Select * from users WHERE username = ?', [username]);\n await connection.release();\n return result;\n } catch (error) {\n throw error; // Re-throw the error for proper handling\n }\n }\n}\n\n"})}),"\n",(0,s.jsxs)(a.p,{children:["Ahora ver\xe1s que dentro de ",(0,s.jsx)(a.strong,{children:"save()"})," y ",(0,s.jsx)(a.strong,{children:"findUser()"})," hacemos referencia a una variable ",(0,s.jsx)(a.strong,{children:"db"})," que no existe, y esto es por que la configuraci\xf3n de nuestra bd la dejamos en el archivo ",(0,s.jsx)(a.strong,{children:"index.js"}),", idealmente necesitamos separar la configuraci\xf3n en un archivo aparte, por lo que dentro de una nueva carpeta del proyecto a la que llamaremos ",(0,s.jsx)(a.strong,{children:"utils"})," crearemos el archivo database.js."]}),"\n",(0,s.jsxs)(a.p,{children:["La carpeta ",(0,s.jsx)(a.strong,{children:"utils"})," contiene por lo general archivos de configuraci\xf3n o archivos globales que se utilizan a trav\xe9s de todo el proyecto. Poco a poco ahondaremos m\xe1s en esta carpeta."]}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(8175).A+"",width:"256",height:"344"})}),"\n",(0,s.jsxs)(a.p,{children:["Dentro de ",(0,s.jsx)(a.strong,{children:"database.js"})," vamos a abstraer la conexi\xf3n que ya ten\xedamos de la base de datos en ",(0,s.jsx)(a.strong,{children:"index.js"})]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:'const mariadb = require("mariadb");\n\nconst pool = mariadb.createPool({\n host:"127.0.0.1",\n user:"root",\n password:"root",\n database: "users_test",\n connectionLimit:5\n});\n\nmodule.exports = async () => {\n try {\n const connection = await pool.getConnection();\n return connection;\n } catch (error) {\n throw error; // Re-throw the error for proper handling\n }\n};\n'})}),"\n",(0,s.jsxs)(a.p,{children:["Las primeras 2 partes son las mismas que tenemos en el ",(0,s.jsx)(a.strong,{children:"index.js"})," para la configuraci\xf3n, pero para exportar la funci\xf3n observa que definimos una funci\xf3n as\xedncrona para facilitarnos la conexi\xf3n y en caso de tener alg\xfan error siempre es importante regresar el error, esto para casos alternativos como el de nuestro diagrama de secuencia."]}),"\n",(0,s.jsxs)(a.p,{children:["Ahora ya tenemos la conexi\xf3n de nuestro modelo con el archivo de configuraci\xf3n de la base de datos. Como menci\xf3n especial, logramos ejecutar el c\xf3digo manejando errores, y tambi\xe9n hacemos uso de ",(0,s.jsx)(a.strong,{children:"await connection.release();"}),", esto para liberar la conexi\xf3n al momento de terminar, ya que si bien en los casos normales se borra recuerda que tenemos un n\xfamero limitado de conexiones, as\xed que hay que estar seguros de eliminar la conexi\xf3n una vez que dejamos de usarla."]}),"\n",(0,s.jsx)(a.p,{children:"Por \xfaltimo no olvide agregar o importar la configuraci\xf3n hasta arriba en tu modelo:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"const db = require('../utils/database.js');\n"})}),"\n",(0,s.jsx)(a.p,{children:"Ahora deberemos actualizar nuestro controlador con lo siguiente para poder registrar el usuario"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:'module.exports.post_registro = async(req,res) =>{\n try {\n const username = req.body.username;\n const name = req.body.name; // Assuming you have a \'name\' field in the request body\n const password = req.body.password;\n \n const user = new model.User(username, name, password);\n const savedUser = await user.save();\n\n res.status(201).redirect("/usuarios/login");\n \n } catch (error) {\n console.error(error);\n res.status(500).json({ message: "Error registering user!" }); // Idealmente se crea una plantilla de errores gen\xe9rica\n }\n}\n'})}),"\n",(0,s.jsx)(a.p,{children:"Antes de continuar, nos hace falta algo, ya tenemos el software, pero no hemos declarado nuestra base de datos ni la tabla dentro de la misma."}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"CREATE DATABASE IF NOT EXISTS users_test;\n\nUSE users_test;\n\nCREATE TABLE IF NOT EXISTS users (\n ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n username VARCHAR(100), \n name VARCHAR(100), \n password VARCHAR(100)\n );\n"})}),"\n",(0,s.jsx)(a.p,{children:"Ya que hemos guardado nuestra base de datos, guarda todo y vamos a probar en el navegador:"}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(5794).A+"",width:"1251",height:"295"})}),"\n",(0,s.jsxs)(a.p,{children:["Si el resultado es correcto entonces, deber\xedamos ver la url de ",(0,s.jsx)(a.strong,{children:"login"})]}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(5693).A+"",width:"559",height:"293"})}),"\n",(0,s.jsx)(a.p,{children:"Pero m\xe1s importante a\xfan, debemos revisar nuestra base de datos:"}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(4560).A+"",width:"263",height:"84"})}),"\n",(0,s.jsx)(a.p,{children:"Lo hemos conseguido, tenemos una vista con EJS, que manda datos y los guarda en la base de datos usando el modelo. Todo un uso completo de la arquitectura MVC."}),"\n",(0,s.jsx)(a.h2,{id:"encriptaci\xf3n-de-la-contrase\xf1a",children:"Encriptaci\xf3n de la contrase\xf1a"}),"\n",(0,s.jsx)(a.p,{children:"Ahora que hemos alcanzados nuevos conocimientos viene la apertura a nuevos retos, y como podr\xe1s ver tenemos uno muy importante que cubrir, la contrase\xf1a, literalmente."}),"\n",(0,s.jsx)(a.p,{children:"Como en todo buen sistema lo ideal es tener un sistema de encriptaci\xf3n adecuado, no es objetivo del curso que empieces a crear tu propio algoritmo de encriptaci\xf3n y si tu enfoque no es en ciberseguridad, te recomiendo que uses uno de los que ya existen en la industria pues adem\xe1s de ser ya probados ayudan estandarizar uno de los puntos m\xe1s importantes de nuestro sistema."}),"\n",(0,s.jsx)(a.p,{children:"Como ya lo hemos echo previamente, haremos uso de una nueva librer\xeda, est\xe1 nos ayudar\xe1 a este paso de encriptaci\xf3n, para instalarla haremos uso de la instrucci\xf3n que ya conocemos:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"npm i bcryptjs\n"})}),"\n",(0,s.jsx)(a.p,{children:"Esta librer\xeda no requiere que la configuremos desde el index.js, m\xe1s bien podemos usarla desde nuestro modelo directamente:"}),"\n",(0,s.jsx)(a.p,{children:"Primero vamos a declarar la librer\xeda en la parte superior debajo de la declaraci\xf3n del archivo de configuraci\xf3n de la base de datos:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"const db = require('../utils/database.js');\nconst bcrypt = require('bcryptjs');\n"})}),"\n",(0,s.jsx)(a.p,{children:"Ahora vamos a agregar la siguiente l\xednea:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"const hashedPass = await bcrypt.hash(this.password, 12)\n"})}),"\n",(0,s.jsx)(a.p,{children:"Aqu\xed estamos generando una contrase\xf1a a trav\xe9s de una llave hash, el 12, que estamos a\xf1adiendo es el n\xfamero de rondas de lo que se conoce como salteo, y quiere decir que 12 veces se agrega informaci\xf3n aleatoria a la contrase\xf1a para que sea m\xe1s dif\xedcil de romper. Este curso no cubre los conceptos te\xf3ricos de ciberseguridad, si tienes m\xe1s duda del algoritmo de hash y el salteo pregunta a tu profesor o investiga en internet."}),"\n",(0,s.jsxs)(a.p,{children:["As\xed entonces debemos actualizar la funci\xf3n ",(0,s.jsx)(a.strong,{children:"save()"})," a lo siguiente:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"async save() {\n try {\n const connection = await db();\n const hashedPass = await bcrypt.hash(this.password, 12)\n const result = await connection.execute(\n `INSERT INTO users (username, name, password) VALUES (?, ?, ?)`,\n [this.username, this.name, hashedPass]\n );\n await connection.release();\n return result;\n } catch (error) {\n throw error; // Re-throw the error for proper handling\n }\n }\n"})}),"\n",(0,s.jsx)(a.p,{children:"Si volvemos a guardar el formulario deber\xedamos ver algo como lo siguiente:"}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(5288).A+"",width:"552",height:"91"})}),"\n",(0,s.jsx)(a.p,{children:"He usado la misma contrase\xf1a demo, e incluso si tu haces lo mismo ver\xe1s que el resultado es diferente, a esto nos referimos con a\xf1adirle sal a la contrase\xf1a creando un resultado \xfanico aunque utilicemos la misma contrase\xf1a, veamos el ejemplo a\xf1adiendo una m\xe1s con el mismo demo."}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(9041).A+"",width:"579",height:"66"})}),"\n",(0,s.jsx)(a.p,{children:"Observa que aunque empieza similar, eventualmente cambia, esto se debe a varias razones, el tipo de contrase\xf1a, que es la misma, el algoritmo que estamos ejecutando y el n\xfamero de rondas de salteo. Aqu\xed no hay una f\xf3rmula perfecta, en ciberseguridad siempre podemos a\xf1adir m\xe1s, pero ese extra es procesamiento a\xf1adido as\xed que ten cuidado en no exceder demasiado."}),"\n",(0,s.jsx)(a.p,{children:"Hasta aqu\xed hemos cubierto todo nuestro diagrama de secuencia, agrega los casos alternos para completar el ejercicio de la pr\xe1ctica."}),"\n",(0,s.jsx)(a.h2,{id:"comparaci\xf3n-de-la-contrase\xf1a",children:"Comparaci\xf3n de la contrase\xf1a"}),"\n",(0,s.jsx)(a.p,{children:"Ya que estamos usando un algoritmo de encriptaci\xf3n ahora es el momento de guardar poder hacer un inicio de sesi\xf3n como se debe."}),"\n",(0,s.jsx)(a.p,{children:"Pero primero debemos ajustar nuestro login, ya que cuando hacemos el registro carga el EJS del laboratorio anterior."}),"\n",(0,s.jsxs)(a.p,{children:["Vamos a actualizar la funci\xf3n ",(0,s.jsx)(a.strong,{children:"render_login"})," a lo siguiente:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:'module.exports.render_login = async(req,res) =>{\n res.render("usuarios/registro",{\n registro: false\n });\n}\n'})}),"\n",(0,s.jsx)(a.p,{children:"As\xed cuando agreguemos un nuevo usuario o entremos directamente a la url veremos lo siguiente:"}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(3492).A+"",width:"808",height:"254"})}),"\n",(0,s.jsx)(a.p,{children:"Como el c\xf3digo del ejs ya estaba preparado no necesitamos realizar m\xe1s adecuaciones en el GET."}),"\n",(0,s.jsxs)(a.p,{children:["Ahora vamos al POST, la funci\xf3n ",(0,s.jsx)(a.strong,{children:"do_login"}),", aqu\xed queremos validar el usuario y la contrase\xf1a introducidas por el usuario. Por tanto vamos a agregar lo siguiente:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:'module.exports.do_login = async(req,res) =>{\n try {\n const usuarios = await model.User.findUser(req.body.username)\n\n if(usuarios.length < 1){\n res.render("usuarios/registro",{\n registro: false\n });\n return;\n }\n\n const usuario = usuarios[0];\n const doMatch = await bcrypt.compare(req.body.password, usuario.password);\n\n if(!doMatch) {\n res.render("usuarios/registro",{\n registro: false\n });\n return;\n }\n\n req.session.username = usuario.username;\n req.session.isLoggedIn = true;\n res.render(\'usuarios/logged\',{\n user:usuario\n });\n\n }catch (error){\n res.render("usuarios/registro",{\n registro: false\n });\n } \n}\n'})}),"\n",(0,s.jsx)(a.p,{children:"Como vamos a necesitar bcrypt, tambi\xe9n no debemos olvidar importarla debajo de la definici\xf3n del modelo:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"const model = require(\"../models/usuarios.model.js\");\nconst bcrypt = require('bcryptjs');\n"})}),"\n",(0,s.jsxs)(a.p,{children:["Ahora necesitamos crear una nueva vista dentro de ",(0,s.jsx)(a.strong,{children:"views->usuarios"})," a la que llamaremos ",(0,s.jsx)(a.strong,{children:"logged.ejs"}),", esta contendr\xe1 lo siguiente:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"\n \n <%- include('./../css.ejs') %>\n \n \n
Gracias por iniciar sesi\xf3n!
\n
<%= user.username %>
\n
<%= user.name %>
\n
<%= user.password %>
\n \n <%- include('./../scripts.ejs') %>\n\n"})}),"\n",(0,s.jsx)(a.p,{children:"Si guardamos todo y ejecutamos el proceso veremos nuestro login y en el caso correcto deber\xeda cargar la siguiente vista:"}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(5297).A+"",width:"813",height:"286"})}),"\n",(0,s.jsx)(a.p,{children:"Aqu\xed seguimos un paso a paso del inicio de sesi\xf3n:"}),"\n",(0,s.jsxs)(a.ol,{children:["\n",(0,s.jsx)(a.li,{children:"Buscamos al usuario en la base de datos."}),"\n",(0,s.jsx)(a.li,{children:"Si NO encontramos usuario regresamos al inicio de sesi\xf3n."}),"\n",(0,s.jsx)(a.li,{children:"Comparamos las contrase\xf1a con el m\xe9todo compare de bcrypt, que recibe una contrase\xf1a plana y el hash."}),"\n",(0,s.jsx)(a.li,{children:"Si NO hacen match las contrase\xf1as regresamos al inicio de sesi\xf3n."}),"\n",(0,s.jsx)(a.li,{children:"Si encontramos usuario manejamos la sesi\xf3n del usuario en el servidor y cargamos la vista de logged."}),"\n"]}),"\n",(0,s.jsxs)(a.p,{children:["Hemos logrado iniciar la sesi\xf3n con el usuario de nuestra base de datos, pero \xbfqu\xe9 pasa si regresamos a ",(0,s.jsx)(a.strong,{children:"/usuarios/login"}),"?. Volveremos al formulario de inicio de sesi\xf3n, esto no es un comportamiento adecuado, pues el servidor ya est\xe1 manejando nuestra sesi\xf3n, por lo que debemos actualizar ",(0,s.jsx)(a.strong,{children:"render_login"})," a lo siguiente:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:'module.exports.render_login = async(req,res) =>{\n res.render("usuarios/registro",{\n registro: false\n });\n}\n'})}),"\n",(0,s.jsxs)(a.p,{children:["Ahora bien, por facilidad, vamos a crear una nueva ruta para ",(0,s.jsx)(a.strong,{children:"/usuarios"})," que se llame ",(0,s.jsx)(a.strong,{children:"/logged"}),". Este paso lo har\xe9 directo pues ya debes estar acostumbrado a crear rutas y controladores:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"router.post('/logged', controller.get_logged);\n"})}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:'module.exports.get_logged = async(req,res) =>{\n try {\n const usuarios = await model.User.findUser(req.session.username)\n if(usuarios.length < 1){\n res.render("usuarios/registro",{\n registro: false\n });\n return;\n }\n\n const usuario = usuarios[0];\n res.render(\'usuarios/logged\',{\n user:usuario\n });\n }catch (error){\n res.render("usuarios/registro",{\n registro: false\n });\n }\n}\n'})}),"\n",(0,s.jsx)(a.p,{children:"Aqu\xed no necesitamos volver a hacer el login, pues ya tenemos iniciada la sesi\xf3n, peri s\xed necesitamos buscar al usuario."}),"\n",(0,s.jsx)(a.p,{children:"Intenta a iniciar sesi\xf3n nuevamente, y accede a nuestra nueva url:"}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.img,{alt:"lab_18",src:r(9446).A+"",width:"835",height:"293"})}),"\n",(0,s.jsx)(a.p,{children:"Esto funcionar\xe1 mientras la sesi\xf3n est\xe9 activa, pero si reinicias el servidor, e intentas entrar ser\xe1s redirigido al inicio de sesi\xf3n, pero ahora necesitamos distinguir cuando hemos iniciado sesi\xf3n y cuando no de una manera m\xe1s sencilla."}),"\n",(0,s.jsx)(a.h2,{id:"middleware-de-autenticaci\xf3n-de-sesi\xf3n",children:"Middleware de autenticaci\xf3n de sesi\xf3n"}),"\n",(0,s.jsxs)(a.p,{children:["Como ya mencionamos previamente la carpeta ",(0,s.jsx)(a.strong,{children:"utils"})," del proyecto contiene archivos y funciones que son utilizadas en todo el proyecto no solo por un solo tipo de archivo (vistas, controladores,modelos, rutas). Y como hemos platicado de pr\xe1cticas anteriores tenemos middlewares, bloques de c\xf3digo o funciones que se ejecutan antes de realizar la funci\xf3n principal."]}),"\n",(0,s.jsxs)(a.p,{children:["Este es el mejor momento de usar un middleware ya que queremos una funci\xf3n gen\xe9rica que verifique nuestra sesi\xf3n con el servidor, para ello vamos a crear dentro de la carpeta ",(0,s.jsx)(a.strong,{children:"utils"})," un archivo que llamaremos ",(0,s.jsx)(a.strong,{children:"is-auth.js"})," el cual deber\xe1 contener lo siguiente:"]}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"module.exports = (request, response, next) => {\n if (!request.session.isLoggedIn) {\n return response.redirect('/users/login');\n }\n next();\n}\n"})}),"\n",(0,s.jsx)(a.p,{children:"Is-auth est\xe1 dise\xf1ado para verificar la sesi\xf3n, si por alguna raz\xf3n la validaci\xf3n falla redirige directamente al login, pero en caso de que no, permite pasar."}),"\n",(0,s.jsx)(a.p,{children:"Para proteger nuestras rutas, lo que debemos hacer es primero declarar el nuevo archivo:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"const express = require('express');\nconst router = express.Router();\nconst controller = require(\"../controllers/usuarios.controller.js\")\nconst isAuth = require('../util/is-auth');\n"})}),"\n",(0,s.jsx)(a.p,{children:"Y ahora cada vez, que queramos usar el middleware necesitamos anteceder la llamada antes de la llamada al controlador de la siguiente manera:"}),"\n",(0,s.jsx)(a.pre,{children:(0,s.jsx)(a.code,{children:"router.get('/logged', isAuth, controller.get_logged);\n"})}),"\n",(0,s.jsx)(a.p,{children:"Nuevamente, al llamarse /logged, se ejecutar\xeda primero el middleware verificando la sesi\xf3n, y en caso de que funcione entonces pasar\xeda a get_logged, pero en caso de que no se quedar\xeda en el middleware redirigiendo al loggin."}),"\n",(0,s.jsx)(a.p,{children:"Para sistemas administrativos esto es ideal por que te permite bloquear todas las urls de acceso no autorizado e incluso personalizar los mensajes de error para que el usuario sepa en donde est\xe1."}),"\n",(0,s.jsx)(a.p,{children:"Todav\xeda existen varias optimizaciones que podemos realizar como agregar un token de seguridad para ataques CSRF, o el manejo de errores para el inicio de sesi\xf3n y que el usuario sepa que algo sali\xf3 mal, incluso mejoras sustanciales al c\xf3digo para hacerlo m\xe1s gen\xe9rico, como siempre es cuesti\xf3n de pr\xe1ctica y que te vayas adaptando a los nuevos conceptos."}),"\n",(0,s.jsx)(a.p,{children:(0,s.jsx)(a.a,{target:"_blank","data-noBrokenLinkCheck":!0,href:r(4490).A+"",children:"Ver ejemplo completo"})})]})}function u(e={}){const{wrapper:a}={...(0,o.R)(),...e.components};return a?(0,s.jsx)(a,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},1281:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/002-a777fb84a9d8038397af5173e0b73125.jpg"},3236:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/003-bf928ab6db3d372988ae7e642549c833.jpg"},3492:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/010-dff77be3a80a00a3bb11e6f049ba89dd.jpg"},3884:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/files/MVC-57bb2864c9b7650f9c85d41a1c57ca2b.zip"},4490:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/files/test-project-a2495a9885fcfe28526249bfa4de39e5.zip"},4560:(e,a,r)=>{r.d(a,{A:()=>n});const n="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCABUAQcDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9IW8VeJNQ13XLLR9C0q5ttLuktGnvtWlt3kdreGYkIttIAAJgPvdj0qX+0vHX/QueHv8Awfz/APyFR4L/AORk8e/9huL/ANN1lXkX7VP7SHi34Nar4f8AD/gzwRceI9d1mGaWC5uDbfYizMtjbR/NdwyBv7Sv9HWQldvk3EpVtyNsAPXf7S8df9C54e/8H8//AMhUf2l46/6Fzw9/4P5//kKui0m8m1LSrO7uLC40u4nhSWSxu2jaa2ZlBMbmN3QspO0lHZcg4YjBq3QByX9peOv+hc8Pf+D+f/5Cprat44jZA3h3w8C52r/xP7jk4J/58vQGuvqtdf6+z/66n/0B6AOb/tLx1/0Lnh7/AMH8/wD8hUf2l46/6Fzw9/4P5/8A5CrB+K3izxV4b8cfCrTtAu9Ht9N8R+IJdK1RNS06W5mMSWF1e7oHS4jEbFbKSPLLIMzK2P3ZSTivgv8AGLxxqvhv4Ta345m8P6lafEyyt5NPTw9pk9i+l3Ummy6h5UoluZxPEYYJ181TGyvHGPLcSs0IB6n/AGl46/6Fzw9/4P5//kKj+0vHX/QueHv/AAfz/wDyFXW0UAcl/aXjr/oXPD3/AIP5/wD5Co/tLx1/0Lnh7/wfz/8AyFVP40X17p/gG8lsZJIn3xq8kRIYIW55HTnA/GvBLrxlZvaxLpelXOm6oGQm+TUpZGJHX5Txyfyr87z/AIyoZDinhakLvlUtW1e7atG0JK+n2nFeZ9VlmQ1czo+2hKyvbZO22rvJd+iZ9D/2l46/6Fzw9/4P5/8A5CpsereOJo0kTw74eKMAwP8Ab9x0P/blXT6c8smn2rTjE7RKZB6NgZ/Wue8YeNrH4a/C/W/F2pxXE+m6Bo8+q3UVoqtM8UEDSuqBmUFiqHAJAzjJHWv0KMuaKl3PlpLlbRH/AGl46/6Fzw9/4P5//kKj+0vHX/QueHv/AAfz/wDyFXL/ABI/aO8N/DPxU3hm9stQvvEEv9lx2FjbPbQ/2jPfyXywW8MtxNFEJcabcsRK8Yb92iF5HVDz+oftjeBdF+KGm+AdWW40nxDczWNjdW13facJrC+u0iaC0e2W6NxMx+0QKZbaKaBTISZcRymOhHpH9peOv+hc8Pf+D+f/AOQqP7S8df8AQueHv/B/P/8AIVeb6B+1ha+MtK8G3vhr4b+ONdPizTLjWdNtVh0+zmazgWy82dlu7yEBfMv4415O8o7puiMcslrUv2uvh/pvirwHpLXnmWnjaysL7R9S+22UfmpeyGO0H2KS4W+PmPsG5LZkXfl2UJKYwDvv7S8df9C54e/8H8//AMhUf2l46/6Fzw9/4P5//kKvN/Bf7XOg+MLfw/dSeEvFGhWOsQ6Xdrdakliy21rqcgh0qeUQ3UjbbqffCiorvG0bNMsMZV2tfBX9rr4f/HvxVdaD4XvPOu1sn1O0b7bZXH2yzWSNGm8u3uJZbbBmg/d3aQS/vcCPMcojAO+/tLx1/wBC54e/8H8//wAhUf2l46/6Fzw9/wCD+f8A+Qq62igDkBq3jgyNGPDvh7eoDEf2/cdDnH/Ll7Gnf2l46/6Fzw9/4P5//kKukj/5CM//AFyj/m9eZ3n7Qmn6f471jQLjwv4gj0rSNasvD174pxZnTor+7itXtodguPtTb2v7SPcsBVWlyxCKzqAdR/aXjr/oXPD3/g/n/wDkKj+0vHX/AELnh7/wfz//ACFXlmoftpeC7HxJ4n8Nx6dqGp+JNFvYrGPR9L1DSry4vnfUrfTsoIr1hbYuLu2BW8Nu+JDhSY5Alu+/bC8G6Houp6pr+m6x4btrWGd4G1Q2iLeTW97Dp95bpItw0cTQX1zBavJcNFCWfzEleBWmUA9I/tLx1/0Lnh7/AMH8/wD8hUf2l46/6Fzw9/4P5/8A5CrM+Cvxu8N/Hnwrda54bl3R2V6+n3kH2q2uvs86xxy7PPtZZreTMc0T5ilcDftYq6ui+gUAcl/aXjr/AKFzw9/4P5//AJCo/tLx1/0Lnh7/AMH8/wD8hV1tFAHJf2l46/6Fzw9/4P5//kKj+0vHX/QueHv/AAfz/wDyFXW0UAcgdW8cCRYz4d8Pb2BYD+37joMZ/wCXL3FFdNJ/yEYP+uUn80ooA5vwX/yMnj3/ALDcX/pusq4Hxz+x58LPiR8WE+IXiTwvp+r61/ovnwXmn2s9vd+RBeQL5yvCzPlb1S2W5NlZ9BDg9f4fupbfxJ458ttu7W0zwD/zDrGt7+07n/nr/wCOj/CgDeorB/tO5/56/wDjo/wo/tO5/wCev/jo/wAKAN6q11/r7P8A66n/ANAesr+07n/nr/46P8Kq6lql1HamVZcPGQynaOD09PQmgCl48+GK+PPE3gjWn8R6xoz+E9TbVbe100Wphu5Wgktys/nQSPt8ma4jxGyHE7HO5UZOf+FX7Pen/C+z8OWk3ijxB4xtPDFlHYeH4PEJs9mkxrD5G6Jba3hDymH9350vmSKhkVWUTTCTX/4SjU/+fn/yGv8AhR/wlGp/8/P/AJDX/CgDvqK4H/hKNT/5+f8AyGv+FH/CUan/AM/P/kNf8KAO8dFkUqyhlIwVYZBrwn4UaDLb/GDxQbnT2SKEyGMyRHbHmQFcdhkdOOnSu6/4SjU/+fn/AMhr/hR/wlGp/wDPz/5DX/Cvn8yyenmWJwuJnKzoycrWvfTby7nqYTHywlGtRir+0VvQ76sHWvC2l+OPAt/4b1u1+26LrGmyaffW3mPH50EsRjkTchDLlWIypBGeCDXP/wDCUan/AM/P/kNf8KdJ4h1C1kaGO42xxnYq7FOAOAOlfQHlnIXP7NM+of8ACTS3/wAUfGGp3/iKytNL1C8vbLQ5fNsLf7YRaGE6b5DROb+YvujLNhRuC5Vtbwz+zrofgbUrIeF9c8QeG/DcH2JpvDGn3afY7yS0t4La3klmeNrv5YbS0RkS4WORYAJEcSTCTX/4SjU/+fn/AMhr/hR/wlGp/wDPz/5DX/CgBvgr4K6H4D/4QD7BdahN/wAIX4Zl8Kaf9pkRvNtZPsWXmwg3S/8AEvhwV2r80ny8jbxWk/sjaD4d0rw/o2keLfFGm+HtMm0S7udHjexeHVbrSls0tZ7h5LVpVYpp9mrrBJEhEOQqlnLdv/wlGp/8/P8A5DX/AAo/4SjU/wDn5/8AIa/4UAc7o/7L/hXRdF0rTIL/AFh7fTdM8L6VC0k0RZotBvXvLJmIiALPI5EpAAZcBRGea6D4Z/B63+F/kW9l4m8Qanoun2S6Zo2iahPCLPSbNdgWGJYoo2m2rFEiyXTTyqqHEgMkpkd/wlGp/wDPz/5DX/Cj/hKNT/5+f/Ia/wCFAHfUVwP/AAlGp/8APz/5DX/Cj/hKNT/5+f8AyGv+FAHax/8AIRn/AOuUf83riNS+Cuh6r/wkfm3WoL/b3ibS/FdzskQbLqw/s/yUTKcRN/ZkG4HLHfJhlyu2Q+IdQWMTC4/eOSjNsXkDBA6f7R/Om/8ACUan/wA/P/kNf8KAOItf2RtBt9Q8PPN4t8UXmkeGoba00DRJnsRbaVawahp99FBG6WqzSKH0q0j3TyyOY1bLb2L1q6h+y/4V1JbVn1DWIrizm1e7s7iOaLdbXV/rFvrBnUGIqzQXlpA0SuGTapWVZQTW3ceNLu1ltoptRihkuZDFAkgRTK4VnKqCPmO1HbA7KT0Bqf8A4SjU/wDn5/8AIa/4UCub3gnwnN4Q0qW2uvEGseKL6eY3FxqetyxtNK21UAEcMccMShEQbIY0UkF2Bkd3boK4H/hKNT/5+f8AyGv+FH/CUan/AM/P/kNf8KBnfUV51ZeNLvUrOC7tNRiurS4jWWGeEI6SIwyrKwGCCCCCOuan/wCEo1P/AJ+f/Ia/4UbivfVHfUVwP/CUan/z8/8AkNf8KP8AhKNT/wCfn/yGv+FAztZP+QjB/wBcpP5pRXzX+1D8X/F3w7+HljrHh/Vv7P1FtUitDN9mhl/dNDM7LtdCOWjQ5xnj3NFfX5ZwxjM1w6xNCcVG7Wrd9PSLPkMz4nweVYh4avCTlZPRK2vrJHrmjf8AIyeN/wDsNp/6brGtmsvw/ay3HiTxz5a7tutpnkD/AJh1jW9/Zlz/AM8//Hh/jXyB9eVaKtf2Zc/88/8Ax4f40f2Zc/8APP8A8eH+NAHnPxCvdOsfGnw4e7vo7W5bV5o4YpbryxIGsrhCfLLAMd7RKDgkGQKCN+D2Oqf8eEv4fzFav9mXP/PP/wAeH+NQXui3dxbPGsYDNjqwx1oA5Gitr/hEdQ/ux/8AfdH/AAiOof3Y/wDvugDFora/4RHUP7sf/fdH/CI6h/dj/wC+6AMWvLv2pGsE/Zz+JB1K8+w2/wDYV2Fl+1tbbpjGRDHvVlJ3ybE2Zw+7YQwYqfa/+ER1D+7H/wB90f8ACI6h/dj/AO+67cDifqeKpYm1+SUZWTts099betnYmUeaLj3PHP2adRtNU/Z5+Gs1ndQ3cK+HbCBpIJA6iSOBI5EJB+8rqysOoZSDyK9Ou/8Aj6m/32/nWr/wiOof3Y/++6fP4Vv5JpHCx4ZiRl/eljMQsViauISspybte9ru9r2V/Wy9AiuWKR4X8VviVP4E/wCE1+0a1baNt8NLc6F9sMSedfj7Z5gi3j96422uU+bG5OPn5wNc+JHiG11DxdFZazFqN7aybF/s+5tZdL0u3N7FCXuHELT21ykLSSN5oliHlyuAyo0afSH/AAiOof3Y/wDvuj/hEdQ/ux/9914s8PUk3ao1/T8/P8DzqmFqzbaqtL/h/Pz7dEfPfgPxV4r8ReJNA0+XxPY3emvHqFzJeabsvBexQPYbFFz9nhjYh55o2eKPaE3J/rVMie1Vtf8ACI6h/dj/AO+6P+ER1D+7H/33W9Gm6aalK/8Awx1YejKimpScm+/ol3Zi0Vtf8IjqH92P/vuj/hEdQ/ux/wDfdbnSYtFbX/CI6h/dj/77o/4RHUP7sf8A33QBlN/x6x/77fyWuP8Aip4gv/Dfw/1i70lJZNakjWz00QiMn7ZO6wW5/eEJgSyITu4wDwelekN4VvzCibY8hmP3/UD/AApn/CI6h/dj/wC+6icXKLina/UzqRc4SinZtb9vM+Sm1C/8CRweH9D8K32kSaDqc2qaBol6Y7omGXRdSKR5hmkZy13BdHaX3YkUA4wF39N8T69q+gvJceJtN8QWdrr2h+XcWE9vqaSGW/SOWGSZbKGEbQYpFWNfOjba5cKyCvpb/hEdQ/ux/wDfdU9S+HMusfZReQRzpazpdRxtKdnmJnYzKOG2nDAMCAyqwwyqR5ywcoJqM9NdNullt2PJjl86aahUdtdNltZbdumltLaHzE/xa8Q6jr0mn2XiKKCPVJLeRYEurW51HRg+qWNuYZYBaoLeQR3ciskpnIaPAbKMX6W88aajpviC80DWvGkugaLp93cQHxPcLZwzTSi2sJ4YJHki+zgsLu5IVY1YrbqQflkLfQn/AAiOof3Y/wDvuj/hEdQ/ux/991awtXrVf4/5miwdZXvWbenfz03699+h8ufDHxN4itvhObtdZ8jTNMg0PTIY1tY/9Ct5rLTXurp5GBz5STTSKWGxPnMnmLtVPV/hlr0+uWOr51X/AISHTbW/8jT9a/dH7dD5ELs++FVifbK80WUUAeVg5ZWJ9M/4RHUP7sf/AH3R/wAIjqH92P8A77rSjh5Ure+2l/wfP+rGuHws6Dj+8bS9fPz8/wAEYtFbX/CI6h/dj/77o/4RHUP7sf8A33XaeifNP7a3/JIdO/7Dtv8A+k9zRXpP7Q3wD8SfFrwJa6LpE2n211FqUV4z3szKmxYpkIBVWOcyL27Hmiv2XhbN8Bg8uVLEVVGV3oz8a4pyjH4zMXVw9JyjZao9Z8F/8jJ49/7DcX/pusq62uS8F/8AIyePf+w3F/6brKutr8aP2UKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAOS8F/8jJ49/7DcX/pusq62uS8F/8AIyePf+w3F/6brKutoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAOS8F/8jJ49/7DcX/pusq62iigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//2Q=="},5288:(e,a,r)=>{r.d(a,{A:()=>n});const n="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCABbAigDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9K6KKKACiisnxPc31npMk1jd6fp3l5knvtUDNDbRKpZnKBk3dAOXQAEtk7drAGtUH/CEz/wDPzH/3yaoeFdQvtU0G1utStvst2+4FfLaPeochJPLbLR71Cv5bEsm/axJU131AHIf8ITP/AM/Mf/fJo/4Qmf8A5+Y/++TXX0UAch/whM//AD8x/wDfJo/4Qmf/AJ+Y/wDvk119FAHIf8ITP/z8x/8AfJo/4Qmf/n5j/wC+TXS6pJeQ6bdvp0EF1qCwu1tBdTtBFJKFOxXkVHKKTgFgjEAk7W6Hw74G/Fvx94w+KXiXwp4gXw54k0vQdOgXUvEnhW1uLWysNb3Hz9KBnmk+1MiFHMibPLxtkRWcKvp4fL6uKoVsRBq1NJu7s7Xtp03a3au3aN3oRKSi0n1PTT4PmmwouEHlfIflPP8AF/Wk/wCEJn/5+Y/++TXVQ/6yf/f/APZVrN1rxNa6DqWgWNxHM8utXzWFu0YBVZFtp7gl8kYXZbuOMnJXjGSPLbUdWOUlFXZj/wDCEz/8/Mf/AHyaP+EJn/5+Y/8Avk1lab8bNK1zxRD4e0nTNR1LVPNuRcwxvaxtaQQ30tk1y6yzIzxGWCX/AFQkYALuVS6Bua0v9rbwJrEd1JaPd3aR/Z5IFsTBezXMMt1DbCVYLeWSWPD3MBMUyRzEOQsbMrqvM8VRW8jkeNw8bNzX/Df1+m53X/CEz/8APzH/AN8mj/hCZ/8An5j/AO+TWVq/izxXZ+JNCEVvp0VvqUsKJ4blhaTU2hIj+1XMlwkvkwLb+ady7JUcxoiy77iNV9EraM1JtLodEKim2ktv6/rr96OQ/wCEJn/5+Y/++TR/whM//PzH/wB8muvorQ1OQ/4Qmf8A5+Y/++TSjwfNDlTcIfN+QfKeP4v6V11RTf6yD/f/APZWoA5X/hCZ/wDn5j/75NH/AAhM/wDz8x/98muvrwfxR8aPEUOlfEqaAWllaQaJrd54ZuYIS1zFLpbG2vGug5aM5uXjaEKCDGG34PynCrWjRV5f1/W3qzmr4iGHV5/1/W3q0eh3vw8XUrOe0u2t7q0uI2imgmi3pIjDDKyngggkEHrmpv8AhCZ/+fmP/vk1zdp+0Z4W1Lxxe+E9PWbUNXhlurWCG3urMyXd1brI0tusJnE0bDypQJJ444iU4kw8ZeDTf2idAi/4QO01WTy7vxTptheQXPm2kGXusLEv2M3T3I3SEL8iSou7mQhHZY+s0d1L+v6Zn9cw97qXlf8Ar1Or/wCEJn/5+Y/++TR/whM//PzH/wB8mub0f9oPStS0Sw1K68P65o66tZQ3+jW94tq0mqRyywQxiLyp3WNjJd2qfvjGAZwSdqyFKHg341Xl9481rw7q2g65DqNzq8kWnac0FtI1jbQ2emPKZ5IZWjVd160wYu2QSgO8xxMfWqWmu/67ff07h9co+7rv+u339O/Q7P8A4Qmf/n5j/wC+TR/whM//AD8x/wDfJqHwb8VNP8aX1pbw6bqOnxalZNqmk3V4sXl6nZqYw08QjkdkUCeA7ZljfEo+XKuF7Wt4TjUV4s6adSNRc0HdHIf8ITP/AM/Mf/fJrL8WfDObxB4V1rSDfpb/ANpWU9j53ll/L82No923IzjdnGRnHWvQ6iuf9WP99P8A0IVtCcqclOO61HOEakXCWz0Pin/h2/P/ANFAj/8ABOf/AI/U7/8ABO2/ksorNviQWtIZHmjt20pjGjuFDsF8/ALBEBI5IRc9BX2pXi3h/wCL3iLxJ8bfF/hjTrPSTaaXpsbxaZqmqm1vVlSa8jaYxLbu2yVktjksQsMlvKATN5dfSVOMM2puKlX3enux/wDkT46pw3kdFxjKj8Tt8U/X+byPDf8Ah2/P/wBFAj/8E5/+P1Pb/wDBO2/tIbmKD4kGGK6jEM6R6UyrKgdXCuBP8w3IjYPGVU9QK+gbf4oal4W0TxTqXiWXTtc0zRZYYRrGlJFpVvNO0pimtgLy6Me6Fgm6XzgpaRogPMhdTyXib9pCTVNb+HEXg4acbXXr5kd9b1FLS3vW8q9iNrFMkU4kaO4gTe8RI3PaqhkS53DCfG2aRS56/wAuWHe38vc55ZFkFNJyo2b6c076vl6S79jyT/h2/P8A9FAj/wDBOf8A4/U9j/wTtv8ATL23vLP4kG0u7eRZobiDSmSSJ1IKsrCfIIIBBHIIr6ftvFmu3HxB8Y6AlhpzxaZpNhf6XuuZEa5kna7VhO+w+Uu+2CjarkDLZYtsXzT4kfGXxX4F+Gvji8eXTtVurGOS1sfE+nWjWNhBeCG4MkbpLJPloXhjjDAlJLi4jtyEdJCKqcaZrCLlKtpr9mHT/t0qpkGRUY+0dB2V/tT6X/veX9XR5J/w7fn/AOigR/8AgnP/AMfo/wCHb8//AEUCP/wTn/4/X0R/wvNdPa1t7/QJr64ksrO9e78O30F7p+L2eWCwjjmlaB5GneIKGEWxTIpZ1TLi/pfxs0rXvEGnaHpWmajqGrzxtNe2aPaxyaZGl09rK8wkmXzFSeKZGNv5wBjzyHiL2uNM0eixH/ksf/kRrh7IJOypa/4p/wCf9a9j5o/4dvz/APRQI/8AwTn/AOP0f8O35/8AooEf/gnP/wAfr2rUv2g5o/FHh++t/D+uP4V1LSZrixVVst2syTX2mW9nLBmfdGuLwkibyTiUblyuF6aH49aM0cv2jStWspm3R2cEyQFr64juorOa3jKSsqvHdzxW5aUpGWfcjvEDIIXG2aNtfWNv7sf/AJH+vuJjkPD8m17K1v70/v32/rqj5v8A+Hb8/wD0UCP/AME5/wDj9H/Dt+f/AKKBH/4Jz/8AH6+iNJ+Kl5N8UrrQr/TdRsp7nSdNubPw9KttJdQM91fx3NxI0UjoIlSGBmYyEDKKo82VY3oeBfiR4xuvC/gnVtbGh6rL40skk0230+3msFtLtrGS8WKZ3kn3xMkUimVVDIVX93IHJja40zZtfv8A/wAlh3t/L5oFw/kTaSoPr9qelnbX3r7tab662PDbf/gnbf2kNzFB8SDDFdRiGdI9KZVlQOrhXAn+YbkRsHjKqeoFQf8ADt+f/ooEf/gnP/x+vatb+JXjzSV+z276drFvHq6WNz4k0rwvfXlvEBBdNNELKG4eV2imgt42mWQoGuGjIDwuKv8Ah/xt438VeIJrHRtb8MajFp+k2moztNo11am8mlur6Jrfm5ZrNkFoI23pK6OXLJlfLqI8a5s5cqqu7/uw/wAvx2EshyOUlBYd3/xS/wDk/wAduzueDf8ADt+f/ooEf/gnP/x+j/h2/P8A9FAj/wDBOf8A4/XtWk/GvXdc0PxR48szpw8DaHHBetpM1hIupzWj6Xa30jC4FwYxKq3LYj8sqxjCF13eYuD4j/aH8Y6P4P8AHE0GkadLr9jfapNosjW0xsW02ymukme4Ik3GVRYyKxTCB7yxB2+acS+OM0iruu+v2YdP+3fuM5ZJw/FXdB9ftT1S/wC3ur0R5n/w7fn/AOigR/8AgnP/AMfo/wCHb8//AEUCP/wTn/4/X0vo/jzXdQ8cL4Wl0+GO9sr67m1K58mQW/8AZu3Nm8Tbs+bIZoEyw2M1pqAX/VLk+LfjDxH4HsW1jTv7O/su1j3vBcWVxcSXkoDuYmmRkjsIgsYBupt8amTLhBH+83/1yzfkc/b6L+7D/wCROl8OZGoOp7DRb+9P/wCS6f8AA3Pmj/h2/P8A9FAj/wDBOf8A4/R/w7fn/wCigR/+Cc//AB+voj4ofFiTRfBfjeLSbbUbPxNpOk6jdgyWyFbJYreV4Lt2YmNopGVfLwWLHepQGG4EVi8+LtxcXlt4et9Du9A8X39zFa29nrvkyJAksN1Kt1IbaaRXTbY3QEauHZ4lVvKVxKE+NM1UuX2//ksfu+Hcl8PZCpOHsdf8U9+3xb6bf8E+b/8Ah2/P/wBFAj/8E5/+P0f8O35/+igR/wDgnP8A8fr2rxv8XvGPgfUtO8Pix07Xtfivre4vZLCymC3umvbahcOttB5rMl3jTZ40RndGJibcPMKxWPEXxS8T31vrN/4W1Pw6NNsvEmmaNbzXFhLerdQX0emGOdXjuYx8rX0jcZDqEA28sYfG2bK/756f3Yer6dLGTyHIUn+4d105pdm39rpbU8N/4dvz/wDRQI//AATn/wCP0f8ADt+f/ooEf/gnP/x+vojxN8SNd8M+NNB0lhp13p9nHp48R3At5I5ZJNQuGs7JrNPMYKouI3aVZGJWMrtLtkV03xC+IFj8PItHvdTufsmmz3Nwt1N9mabbFFY3V05+VwVwtsTkLITjaE+bemv+uWbat17W/uw/+R76fI3/ANXMitJuja2/vS9P5u+nqmfKP/Dt+f8A6KBH/wCCc/8Ax+j/AIdvz/8ARQI//BOf/j9emJ+0/JrlvM9p4g8G+HYv+EpTSTfX12moW9pZPpT3kckzRXMaGVpo3hysnlhldFaQpvbvNB+ONr/wjF1ea1aTfbbWykvbdrKMCPXI1u5raNtPVnzI0xS3dYwzAC/tVDv5iscYccZnUbtiP/JYf/ImFPI+HqjfLS01+1Lp/wBvHzv/AMO35/8AooEf/gnP/wAfo/4dvz/9FAj/APBOf/j9fQOm/HbT5de8RXWqXn9ieG9I+326pd6NeB7iSzZxcSpd4EEmBBcEW8QkkKRGQsCHijr+JvjNqlvr3hfTrPwxrlnq7aukd/4dkNg11c2cthqDxOkn2gwBTLZsf9cr/uGBGGUPX+u2aWusRf8A7dh/8j/wS/7AyC11RvrbSU3+Uv8AgvWyPBv+Hb8//RQI/wDwTn/4/R/w7fn/AOigR/8AgnP/AMfr6I1j9oPStN0S/wBStfD+uawuk2U1/rNvZrarJpccUs8Mgl82dFkYSWl0n7kyAmAkHa0ZfJHxo1TWvEyeG9GntI9Su9Sk0+K7vtLYw2uyfWAzsi3WZ8rpBjADRYLCXJ3eSg+Ns0uksRe/92H/AMj/AF1B5BkF1FUbt7e9PX/yb5eXWx4b/wAO35/+igR/+Cc//H6P+Hb8/wD0UCP/AME5/wDj9fSHwr+JmqeNvEWu6VqEFpH/AGTbIrTWyMvnTrqOpWUsgDM21G+wI6pklPMZS74Bqv8ADf4zXnjjwvo15D4Y1HV719JtbvU7jSzbRWtveS2Md39mUT3CSFmWaLaQGQecgaQYcrUeNM1kotV99vdj0/7dKjw9kMlFqj8V7az6f9vHzv8A8O35/wDooEf/AIJz/wDH6P8Ah2/P/wBFAj/8E5/+P19EeIPjnZ2N14bn06x1G70S91f+zbu7TSbmaQt9iu5mihgRTMZYpLeJZcxEIWkQ4eOURQaF8eP7a1jXrCz8P6trN3BqUkGn6bbWX2C8a1is7GWaaZL54Au2a9VQDtZleMqrAM9L/XXNL2+sf+Sw/wDkRf2Bw/e3sutvin2v/MfP3/Dt+f8A6KBH/wCCc/8Ax+j/AIdvz/8ARQI//BOf/j9fQPwu+I3iLXv+FZ2uppaXNvr3gn+3LrUMkXEt4n2EOPLVVRExdFuM5JwAgT5/Wq1p8YZvVXNGv/5LDtf+U2o8M5JWjzRof+TT7J/zeZ8Sf8O35/8AooEf/gnP/wAfor7borX/AFszn/n/AP8AksP/AJE3/wBU8m/58f8Ak0//AJI5eiuj+yw/88Y/++RR9lh/54x/98ivkj605ysPxZ4XHiq1s4TqV5pjWt0l2klmImLOgO0MssbowDEOMrwyIwwVBrv/ALLD/wA8Y/8AvkUfZYf+eMf/AHyKAOR0qym0+wit7i/uNTlTO66uljWR8knkRoi8A44UcAZycmuzqL7LD/zxj/75FH2WH/njH/3yKAJaKi+yw/8APGP/AL5FH2WH/njH/wB8igCWiovssP8Azxj/AO+RR9lh/wCeMf8A3yKAKPifQ/8AhJvDWraP/aF9pP8AaFpNafb9Lm8m7tvMQp5sMmDskXO5WwcEA1w3wY+B8fwR0Ww0LSfF2uan4b0+0+yWei39tpscEHzBvNDW1pDI0hO7LO7bjI7NuY7h6P8AZYf+eMf/AHyKPssP/PGP/vkV208ZXp4eWFi/ck7tWT1Wiequmk3qu7J5U3zdQh/1k/8Av/8Asq1jeLvCMPi63sQb670q/wBPuftljqNj5ZmtpfLeJmVZUeNsxSyoQ6MMOSMMFYbP2WH/AJ4x/wDfIo+yw/8APGP/AL5FcEoqSswlFTXLI85tPgTp0cehwX+vatrNhpWpPrSWd9HZbZtQa6luvtTOlusiOJZjgROi7VClSrOHnsfgrp9nptppja9rlzpGny2TaZp0k8SwWEVrcw3EUKBIlMi5toU3zmSQIrBXUu5bv/ssP/PGP/vkUfZYf+eMf/fIrH6vSXQ5lhaK+z5ddu3p/wAPuce3w2mj8cX3iW08W65ZNfS273OnRx2Ulu8cKhVhDSWzTLEfnbYsgAaaVl2s5NdrUX2WH/njH/3yKPssP/PGP/vkVrGChe3U3hTjTvy9dSWiovssP/PGP/vkUfZYf+eMf/fIqzQlqKb/AFkH+/8A+ytR9lh/54x/98ij7LD/AM8Y/wDvkUAS147J+yf8Oh4X0zRrPR4dLlsrKawbV9PtbaG/uo5bGazkM0wi+dmSdnPGDIqsQQMH137LD/zxj/75FH2WH/njH/3yKxqUadW3PG9jCrQpVre0inb+v0OPtPh7J4cvr3UNH1XUZ4hLdX9p4duLtINPF5OZHkdpEhMxV5JZWKyNKimTcseUjCc14f8A2ebTQ9B0DS08TatFaWH9lT3tpaJbpb6ldWC2yxTuJIpJY8iztwUjlVcRjuWZvVfssP8Azxj/AO+RR9lh/wCeMf8A3yKl4em91/X9fhoZvC0pbr+n/VrbW02OAk+B2hSaJ4Y037XqKr4b0ldJ064WSPzECS2c0U7ZTa0qSWFu4yNhIYMjKcVY8I/CK08K+Kp/EkuuatrutT/afOudR+zr5nnpYxtlYYY1G1dOgC4A6vnJIx2/2WH/AJ4x/wDfIo+yw/8APGP/AL5FP2FO6dv6Ww/q1G6ly7W/Db7jj/Bvwr0/wXfWlxDqWo6hFptk2l6Ta3jReXplmxjLQRGONGdSIIBumaR8RD5ss5btai+yw/8APGP/AL5FH2WH/njH/wB8itIQjTVoo2p0401ywVkS1Fc/6sf76f8AoQo+yw/88Y/++RR9lh/54x/98irNCWvJb74GXGufEfxD4g1TxTq09peabHbaabeWG3uNNnxfxtJE0UCEeVDfOsTMznMshfcVjZfVfssP/PGP/vkUfZYf+eMf/fIrKpSjUtzdDCrRhWtzrbU4C1+Dpt9HsrFvF+uSNpcsUuj3C2+nQtpTJFJARBHFaJEVaGZ4ysiOACCoVgGrmvEX7Obaxb+GNKPirVr3w/a6lfXmr2d4bVTerdx3/wBobdHbBg8jXpjIRkVYixTbIFevZPssP/PGP/vkUfZYf+eMf/fIrKWFpSVmvxf9dP0MZYOjJWa/F90+/kvkrbHH6h8MV1DxJ4o1keJdctJde0lNHeG0kgiWzjQSeXLbuIfMWVGmnYMzsMynghUC814s+Cusah8J/EHhHS/HOrf6Xpr2NlFc2umw28SeRJEtuRDZLthbem7aN6iNdhXkN6r9lh/54x/98ij7LD/zxj/75FVLD05Jp9b9X13KlhaU007636vrv1OAs/gdoVmJyt3qLy3Eun3FxK8ke6ae01GbURKQEADS3FxKXCgLhtqLGAKJPgnpVzd6Z9p1PUbvS9P1ebXYtJmS1aD7c93NdicSeT5yskkxACSKCiBWDBpN/f8A2WH/AJ4x/wDfIo+yw/8APGP/AL5FH1elty/1uP6rR25f6vf8zzG3/Z90qK70hpPEGuXOn6LHDb6TpkjWogsIIru0ukiRlgEjqGsLdMyu7FA3zbjuq/efA7QrwQFrvUUlt5dQuLeVJI90M93qMOomUAoQWiuLeIoGBXC7XWQE13/2WH/njH/3yKPssP8Azxj/AO+RS+r0tfdF9Uoa+7ucR/wqK0nuJ7+/1zVtS12S2toItam+zx3Ns9vJdPFNH5UKIrgXksZBQo8fyOrK0gehoXwMtNF8MwaJJ4p8RajBY6a2maRPcS28U2kIYDB5ts0MEY84Rnassgd1G4KQJJA/o32WH/njH/3yKPssP/PGP/vkU/q9Pew/qtG6fL+f49/n1OI034W3el6Cmlw+OPESJbeSNPlhi0+3+wLGrJsjiitEidCjbdkscijarKFZVYULX4GWmm6i93p3inxFpn2q2W21OO0lt0/tH/SLm5kkeTyPMid5by4YtbvFt3jZs2rj0b7LD/zxj/75FH2WH/njH/3yKPq9PTTbzf8AX/A0D6rS00283/n/AEtNjgI/gnpUGpan5Op6jb+HNSlhku/CsKWsemSiO2htkjIWESiLy7eIGISBGClWUozIb918JNCvPB+t+HpVmaLVY9WhkvsR/aoY9Rmea5SJ9nyrvcYGCP3ce7cVzXYfZYf+eMf/AHyKPssP/PGP/vkU1Qpq+m41hqKv7u9/x1M218M2tr4s1LxCkkxvb+ytbCWNiPLEcElw6FRjO4m6kzkkYC4Awc81q3wlh1rQbPRrnxP4ik01dNTSdShkuo5f7Xt1UowuGkjYq7q0geWAxSNv5b5I9nb/AGWH/njH/wB8ij7LD/zxj/75FVKlCSs1/TLlRpyVmu/47/ecR4i+Dul+JrfXUutU1ZbjW7a6sdQuY7hd01rNG8a2+0oUVIQ4aLaoZWDsSxmuPNIfhFaN9su9R1zVtX8QTeR5PiC5+zx3ln5PmmHyRDDHENhuLjO6Nt6zSJJvjOyu3+yw/wDPGP8A75FH2WH/AJ4x/wDfIqfYU73sR9WpX5uX+v66+bOPsfhXp9vqWk6realqOr65YX325tVvWi8+5YW1xbJG4jjVFiRLqUrHGqDeS5yzyF6GhfA7QvD2iappNrd6ibK/8QW/iHy3kjxbSQS2zwW0ICAJboLSGNY8Eqg2gjAx3/2WH/njH/3yKPssP/PGP/vkUewp9v6ejD6tS/l7/irP7+p5z4q/Z18CeNpPFtxrOh2l/qXiPPm6pcWkEl3Z/wCix2y/ZpWjJj2rEHXOcOzHvgdLc+A7W81611a41DUZ5bTVzrFvDJMGihkNg1kYkUr8sWx3k2g58x2bOCRXQ/ZYf+eMf/fIo+yw/wDPGP8A75FNUaabaj5/qNYeim5KKu3f57/mzl/+FZ6X/wAJJ/bfn3f2v+2/7e2b12ef/Zv9n7cbc7PJ+bGc7+c4+Wr/AIi8FaX4o1XQtQvot9xo9ybmHCqRJleI5MqSUEghmCgjEttA/WMVs/ZYf+eMf/fIo+yw/wDPGP8A75FV7OFmrbu/zK9jTs1bRu/zOAvvgdoWrTXcOo3eo3+gTy3twmgSSRpawT3aTJdSo6Is5ZxdXXDSsq+e21V2x7LGm/CK0t/EOn6/qOuatr2u2Vyk6X999nRnRLe7hjhZIYY02KL65cEKHLOMsVUKO3+yw/8APGP/AL5FH2WH/njH/wB8io9hTTvb+v8AgdO3QzWGop3UfP8Ay+7p26HifxD+Aepajpuo6Z4R1SbSovEUd9a67ezXkQZoLm5uLgKIWtJfNWN767wqSW7EMFaUna6dMvwF0a11G61Ox1XVtP1aS5a8gv4XgZ7SVrjUJmaNXiZDkapdx4kVxsK4w6769G+yw/8APGP/AL5FH2WH/njH/wB8is1haSlzW/4Hp27+pmsFQUua3p5enbv3vqcBb/BmDSWWfQvE+ueHtQkjMd/fWYtJZNQYzzXBeVZ7eRFbzrq5f90sYzMRjaqKljR/hFaeE5Gj8Ma5q3hrTWtkgbTbP7PNCzx2qWsU264hlk3pFFAAA+wmFSytuff2/wBlh/54x/8AfIo+yw/88Y/++RWioU1ay2/rTt59+posNSVrLbbfT07X6236nH2Pwn0fS9S0m5s7nUYbfTL7+0rexe5M0X2k21xbyylpA0haVblnf5/mkXzD87ytJBqXwitLjxDqGv6drmraDrt7cvO9/Y/Z3ZEe3tIZIVSaGRNjCxtnJKlwyHDBWKnt/ssP/PGP/vkUfZYf+eMf/fIo9jTty28yvq9Ll5eXS9/mcR4f+EVp4Z1Xwdd2WuasIPC+iHQbWxk+ztDPAViDNKfJ3lyYIGyrqMxDAAZw29beE/s/9nf8TnVpfsepXOpfvLrPn+d5/wDo8vHzQx/aPkT+HyYeTs52fssP/PGP/vkUfZYf+eMf/fIqo0oR2X9af5IcaNOHwr+tP8kVdF0n+xbOS3+2Xd/vubi5829l8x182Z5fLBwMIm/Yi/woqjnGaKtfZYf+eMf/AHyKK0SsrI2SUVZEtFFFMYUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/9k="},5297:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/011-e1d8198702551e0512c13780cb7f6e19.jpg"},5693:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/006-cd27bfc06a442a783b74b7aa586fb2c4.jpg"},5794:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/005-bf928ab6db3d372988ae7e642549c833.jpg"},8175:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/004-063c82019383c5fed12564a3125b8976.jpg"},8453:(e,a,r)=>{r.d(a,{R:()=>i,x:()=>t});var n=r(6540);const s={},o=n.createContext(s);function i(e){const a=n.useContext(o);return n.useMemo((function(){return"function"==typeof e?e(a):{...a,...e}}),[a,e])}function t(e){let a;return a=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:i(e.components),n.createElement(o.Provider,{value:a},e.children)}},9041:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/009-9bac7c54dd6b559f29546b19e899d1c6.jpg"},9446:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/012-699132f0289f41241779be59f7f02da5.jpg"},9954:(e,a,r)=>{r.d(a,{A:()=>n});const n=r.p+"assets/images/001-6b13b05c99f24659c0fdd6aa9cb9a54e.png"}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/4795aa0f.d30eb304.js b/deprecated/old-docs-build/docs/assets/js/4795aa0f.d30eb304.js
new file mode 100644
index 0000000..f0286f5
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/4795aa0f.d30eb304.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3978],{3937:e=>{e.exports=JSON.parse('{"authors":[{"name":"Yangshun Tay","title":"Front End Engineer @ Facebook","url":"https://github.com/yangshun","page":{"permalink":"/docs/blog/authors/yangshun"},"socials":{"x":"https://x.com/yangshunz","github":"https://github.com/yangshun"},"imageURL":"https://github.com/yangshun.png","key":"yangshun","count":3},{"name":"S\xe9bastien Lorber","title":"Docusaurus maintainer","url":"https://sebastienlorber.com","page":{"permalink":"/docs/blog/authors/all-sebastien-lorber-articles"},"socials":{"x":"https://x.com/sebastienlorber","linkedin":"https://www.linkedin.com/in/sebastienlorber/","github":"https://github.com/slorber","newsletter":"https://thisweekinreact.com"},"imageURL":"https://github.com/slorber.png","key":"slorber","count":3}]}')}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/49d0b380.56369bee.js b/deprecated/old-docs-build/docs/assets/js/49d0b380.56369bee.js
new file mode 100644
index 0000000..a0042f1
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/49d0b380.56369bee.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[4755],{2490:(e,r,s)=>{s.d(r,{A:()=>a});const a="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACoCAMAAABt9SM9AAABd1BMVEX///8AAAB+0yFKkOL45xzn5+f45gBGRkaioqKmpqb29vYxMTGxsbGCgoLk5OTc3Nx30QDS0tKXl5eNjY08iuHCwsLZ2dlGjuI8PDyJiYmcnJzIyMgfHx/w8PD/7h25ublaWlpmZmYpKSlvb29QUFA+Pj4bGxv89r2OtuspUH1qampdXV0LCwt4eHhTU1NHR0f561Tv+eXFuBbUxRjezxn+8/Dyf1/9+tf79KnM7LD//vPb8saM1z/78pnm9tn9+MnB6Z9rsxyjmBIwLAWBeA756kFaVApMRwguTQtJexOSiBB4yB/67nb68Y3H2vVsouakxO/m7/o6crMWK0QzZJ33uqrxbkXvVR32rZrsRADuWSX72M/xd1P3tqb86eTzi24yND7n4aPS7ruf3WWs4Xz57WW65pSW2lOj3mtjpRpvaAxSiRU/Owe1qRRlXgs4NAYVIwX//B8eMwhAaxBmrBsnQgqw44MkRm83a6keOlsSIzh1qOi50fLoUKoMAAAMD0lEQVR4nO2di1/aSB7AM0AKhJCYBCIQMDytCL5rrdpVS33V+q5tt7t3vTv7sO/eXnf7WPvH30wSIEiAJDyD83UrIcb5ZL77m+GXmUkkCAwGg8FgMBgMpi2e9/sEHMTi7X6fgYN4/KTfZ+AcHj998niRltl+n4cj+PXp0yfPhWB6kmp6GKX8d91ZfPYreqEyBfSS1f0kFq5sUlM0wftERnnDJdgwcT1ZfKZt5CT0TfcTvqpEZgiSJig1vBKEKPfyDC2QIO3AmG4zi7/Bb78/hoHEEwSdThI+74xEiNkc70t6kyTh54UwkQpy/CQtirzARwmZjE0xMYpgBk/ZFLAFbbZ8JOv5P/4JRQXhOy8hpSgyR+ZYQopNEVxBguEVS9AkAZsex2UoIuGTSSZMsLBdJrpYbXsEeyCLeA4TUyFKoGYY82anZ+QUoTbDaRhuhCiVZaF3vEyiPitK8V2qcRt0XdazReU1EUCV95JcgWI4IidSYSQrG4ZtTbgqi4GlSxmmSzVug27LIv51G9kSvV4BvoQjDJ2d5gimMOOnYY+fIWhB8HNhkmAkghP98BA/fMPD3j7TrRq3QddlLd5+9vtMLu6Xo5bOixvE9KHrsqAuiiEJq0knP4g5ag9kDQ+KrGgRCEmfLyb4fMFJnwBACn6DO2aA4A8A+D4a8fkKEV8q6PNFr7ssBgASJNl02ucrSknBD+SpqI8XikDkM2QxAQ/I0PBboBCP+dLXXlZYpkCAA0DgQTjljwHaL2d4lo6zcJdAwgNSpAzCTAq+u/bNkEWRFdFk+TPQBc+CGEzuYVCFCwkwzmZi8LDxBDrgussii1BLRASA54E0A3fIk5MSz4SnMyTJgimSTE4lwpPhsA8dcN1lNUNO4U/DMjh1sACWZQEsywJYlgWwLAtgWRbgc956sjPwn/7NVSKDNz7eN0Ttn4LUxxNxAplpKpjWtqMg2NdzGXjQlI9X3UQjMthWM5CsGWVLAOPwS+jz+Qw0VVlMkgNcFi/raMYkyJanOKGsvp7K4MNUDWFZrWBBJWHAslpRI0tsdiSGEKuyJCyrBWK17WFZraiRhROH5rAVWWwc5AZvodQAIQEZfqm9VgTmp6k+n89AQ+uGrNDE2CCu/hkc5OrwnhcEsKzm0KA8uvdvmhBwM2wOubK0fvMO5D9LSysdWrA4t3q+vLyxfL66NteZAgeBlZsHszf0eGYP1lfaKnJt44VrRM/Fy+UhMLZyB4nyXAHumr1jt8jVl1CU6wpQ2N3Xzva19KpeVNXX/pL1Eldej9Sbqgi7OO98HXrE0mxDVZqvVxZ1rXxtaErzddeZulb2m6tSdR1YKXK5hSpF14UDG+O6p6UqRZfHdHCtXLRWpeja6GK1usIvplQpukz29Gsuc66grTfdrVun2TftCtr6xUyJ52ZVKU2x2/XrJFZcmYutNQuunGXLfBvUbN00KITKTVXfzFly5aSWuGTRFbSlJPSReDpd1E0D6aau71pzBW0t96Hidtiy6spzYxb9HqkM5YisCuMHoBxbry0GFrLljAzijuXAgrbW0W/Kdeu21In+FeuuXCMv+yrBLNZVQZTQyil+eJVYFIC0ei+XjcBySGit2wgsGFqwagXkKlYuh8qBgHbfmw1VDslND+zJgukDJ0Iq5XAgrrmyljZUcEL6MKt3MHZroc7LvME+z439uoLI8v2Uy0ay7rlcD++9bRpava23LfSBNVa6/25zbGzMA/8tlMYUPPfn1V21trYal/jVSNbu25Hdt++bynJAp1XTCgHUUtrevnV/u7T5Yf7+h/ntHc+H+b2Pnz7Nf35Xq6txiS8NI2vX9V/X7r3d3fcP4JdRiI2s9a7SdqmTdX9sr/TJ82m+5Nn547OnVNqZL33Ye/dx80poNS7RUNbI/x68f7j75cuXP3e/7O4+HAJZY5s7m3vvoJ5tz/b8p4WdPzZLOwuf5/c2S6WPe7WyrDZD13vgevjX2y/Q2IMHfzm1GdZ28AvzqEf3LMAv1LGPzd8aW/Dcgi+eW0YdvOE6CMMOXuni77nu/el6CDGU1dNq28N+6oAm+qtzihSDQFtDnDpYv4xWZKFL6TDQLWRW7jRXF0jYcuWIpNTGdTR09YpQXAVov4Y+nbd3udPe1GSPuGnnQnoJtru6y+hxrURbF9Jf+yrBNDaGaFBgESyMLJFTEZWHY5SHaDZsDNE4IrDsD/4RDACFSilJ/a11wzv4Z3lESx3NIpCtBo8Em7PqyhmjWQrW0gfdhEXDZxFZnLB40ZNqdggrtsxNHFqx5RxXCfRwKwstsdIGWzBnfpLVAR+ENB+cjFTuLFzaMjd9v2X+U+uNKVsjIw5YG1JJk8r3rB6YWRhiaja6zLmJ4Bp544icoZJLlnfMtVhHc+PGvtWKbTRfRzMycuGAcRlEcSaj5Eag+pE2d+Bp5AvuP7ATA8t3myxme+MQVQrqY9ZqFtjefHWjbp0k2rNvsl+vZ+2rq371H9zzYtkRDbAM7N4j46B6n442OrX0y6stj2797db+HRsLJPXMLb+80K/AvftiY7W9EntOBjXBOKiseSeB7odobTdkfWmlU///V9ZWV8/P0cpuR0WUCnoynYiiSxtYocbx3XKNkNTr3unitBZZuev6iJnWMNAVeoJv5aOwgB930QjUV03rdygPho+wTIIaxAf19pdsNXNXiFXHO6cb/c51BT02q+YGVaHiKtCvcxpU6MpETJnKurQiboW1cFCKv3YXU5ZF9ueUBhNRZDnd1EKZcmThTEuHT3XivbKb01zhx7DpCGtSrnRMqBFmKJLFj3DQQZZ7ptpHNSSKAGQb/Mq1BV79adT8OQ2UoOKE4SrTlWSq5k+VeAEYxwnDFTLVLF0fWQWcMBjAoYUJXt1qF4UpnDA0BP2pgEllyx+dJtX3OGFogARARNlAU2GscuGDR7EaQVXyBtT8UDI6gH/uzRJUU2yUp9uOlzso6EkYggE/H2hKrHUJKieHxxNHbvfoaGjU7T66PD48IXQLjNWkq9Dk9x2Bv7ksn6lCDifcoVBodBR6UoBboZB74rByAFrnACKMKDn7yWvtyzqZUKKpHrh74kQ9Jlkuz9kPIm1X1uGRsamyryNFVwrLglHVVJWq6/IEPUBZw9kpaVuyjlupUnUdE5wscSzp+NmcNmRRR6HWqhCho55Vp7vYl3XiNhFWWnC5T3pXoy5iVZZY/vA/MdMEq01xKGy1lCWhmxoqh4fLt71biKvhia2WspI1AUZFtIHOI0uuoK1h6LdaN8MAeo0KGkEAAgn0OWjNFezlj/td1fZpLav+CB/ssKy6grac3xBbyhLRS5zRSMSUcZYJi40QMTrR77q2TUtZgXQ6Ha+smw0rrigbgQVtOTwltZw6UF40fvfTlqzQodEJOImrsgKRprJU7LTCYWiHMX+C5YrIC608dYn3m5BlkDfkz9zub9+VzdN8+dsVWY7PHmJ0FMTkIMd6CSbOMZlogpUDHFnws3JaZKPGI6UGgZUH7lPw3f3t1H32Le/On55+MwitHtet4yBZGSlIU14xEAhL4agfJAoyR/Ny1C/SkrEsgy4rn8//yJ/97X50lv+ef3T2t0FkuUM9rlvHidEinYgwQiIrS0GOlqKUzApSWOb9VIGhU8ayDDqkb6c/8t+hLGjsNP/o1DCy3D2uW8fxp5PJIigmc+MgCbKRXDoCr2+ykUg8GQfppNpnRa9+5hv0WWensJ86Pf1xpuj6kT89G8I+y0TqkNItNg7zPC9Ix/Y+DR1/wdNaVgGMV9ZyKKv2itQhzrOMZRUASHsjGso+0rDTMkG/69o2LWXB7CuejmuogWVn0GEohh1aN0MvSFemRpV1omhAy86FdB9r2SFMdPAzIF45nA4Gg1MyQVjvtZzfY5m7kE4ZLNabsGgr5PgLQ6KN2R1r48rOz7EQ9qfCrNgaDlftTLJemm6JwzLL2s70vdkEYij6K0Rbax0O3SZ0hdxD8Dmo0uaSo+PRFrpCzr8irNLu+izquMFSNgT80bHjZyl0dGCZ5M8jtETSwFTocmgaoEpH1pRSPyeO1EWlGiFlSekwBZUCHQ80Ie5vXYIGdfLzeOLy6Ojo8nLi+OfJ0InCYDAYDAaDwWAwGAwGg8FgBo//A44MZgTN4kYDAAAAAElFTkSuQmCC"},3479:(e,r,s)=>{s.d(r,{A:()=>a});const a=s.p+"assets/images/004-7f3a036fcfd42d7fe1a6ef7ab7f7a4ea.jpg"},3893:(e,r,s)=>{s.d(r,{A:()=>a});const a="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAClAR4DASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD86qKK7T4Yv4Gmvr3TvHEOo29vqEa29rrlhLuGkSZz9oe225uFyFVlDqQjSbQz7CoBxdFetWf7N3iG38ZahpuvXVponhvS7aPUr/xduM+mrp8h/c3MDr/x8edjEUafM7ZXClX28V8Qr/wpqHiR38GaTqOj6HHGkKR6peLcTzMg2+e21FEbSAK7RgsFdn2tt2qoBzVbHhHwjq3jzxFaaFoVp9u1W63+Tb+Yke7ajO3zOQowqseT2rpPCPhnTfFXw18YmO3/AOKm0TyNVt2himkluLLd5VyjAN5apGXhk3bd2A/JXJX0P4f/AAt8P3Hxg+HXgbXtIjlupNNmutei8y6hlM8kM9xDDJll2mOL7NkRgfMXDFscZSqKKfkcNbFRpxn3jfp2V77q62676HgFFeweH/hfFpvw18PeKZdO0TWrzXZ7tYrfXNeSwtobeFkTIQywO8pk8zlZGRUxlcsCuz8Sfhz4JtvA2u3nheXTTqtl9g1uZItaF69rb3BeCawQxkpL5E/lN5pwSsygngFz2ivYPrlPn5V3t03vbvffyPBqK+i9Y+C/g/8A4T74d6bp9/Y/ZUkm07xWsslxFbrdaeiTag/nSYIDRuQCoVRtBBGTtxtM8J+Gte8EDWvBngyPxbr9xd3M2oaBcalPK+jWyz7YUhgheKecMs0OZcuB5Zzgk4XtUQsdTaUknr8u61u/J/00eG0Vc1m6tL7WL64sLL+zbCaeSS3svNMv2eMsSse88ttGBuPJxmqdbHoLVBRRRQMKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKvXWg6nZQtNcaddwQr96SWBlUZOBkketanw98n/hMtM8/y9m9seZjG7Y23r33Yx74r3S68n7LN9p8v7PsPmebjZtxzuzxjHrWUp8rscNfEOjJRSufNNdn4LsLa60uV5raGZhMQGkQMcbV45rjK7rwL/yCJv8Aruf/AEFa1O44WiiigDevPHniLUPBun+E7nWLubw3p9zJeWumPITDFK4wzAf99EDopeQjBkctg0UUAdJ4B8Yf8IXrVxcyxXN1YXljdadeWlrdfZ2nhnheMjftcfKWVxlWG5F44rpPCfxs1DQ/jYvxH1S0/ti/M9xPJaC4aNT5kTxhFdg5VEDgKDnCoBXm9FS4p3uYToU6nM5LdWfoehaf8UrO8+HVj4N8UaJJq+n6XdtdaZdWF1HZ3VsH3mWEyNBLujZmD4wDlRkkBQp8Nfitb/DP4ijxBY6DHPpPl+S+jzTBzKi7WjZpXRsSCWKKYuqr86fKEUhR57RS5I6ruS8PSalFrSW+/X+uh6F4Z+M2p+H/AAX4y0OZJNTuvEEizJqF3P5htJWWSO5lVXVsyTRSvGzgq2D1NU/C3jLw54XbQ9Tj8OXz+JdJkW4jvF1VVtZ50maSJ5YDAzYHyIypIm4JwVJJriaKOVD+r09dN99+1v6WxpeJteuPFXiTVdau0jjutSu5byZIQQivI5dgoJJAyxxkn61m0UVZukoqyCiiigYUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVeute1O9haG41G7nhb70cs7MpwcjIJ9ao0UCsnuFd14F/5BE3/Xc/+grXC13XgX/kETf9dz/6CtAzhaKKKACiivaf+Eb0P4+aD53g/RbTw38QtKtsT+FtO8z7PrVrEvE1kJGZ/tUaL+8hLM0oUyKS+9SAeLUV7D4s0vw18FfDeoeE7zTtO8W/EPUI/J1e5lkaS18PAHItrdo3HmXauFMkuSibTCA4MufHqACivbPiB8OfD2h/tWWvg6y0/wAnw4+raZbNZefI2Y5VgMi7yxfku3O7IzxjitOH4c+FlvPH9touhx+J/F2m+IbywsPC91qLxImnxlnE8aK6TXMiiGRNiSFvmDFTxnL2isn3OD67T5Yys9Un02ffU8Aor2Dxd4V0KT4J3fieDwv/AMI1rsfixNGltftFw/kKlgrSx7ZWJGZld8Nll3bdxAqn4g8D6Ta/DL4RapZ6Rc3eq6/PqEd/HZzP5975d0kcccYIdVfaSo2oeSCQ1PnRccVCVtHq7dN0m+/keV0V7zqXw80nUPgn4y1u+8Kab4Y8R6DPZPF/ZOsPcMVmlETwXNtJPM8DqdxIfY24AYGxw2B8ZNB8NeC9D8F2mleH401DWvC+n6pd6hNdzuUlfcWaJN4VS21g27euCuxYyCWSqJuyJhi4VJcsU73t07J9+zPPfF3hHVvAfiK70LXbT7Dqtrs8638xJNu5FdfmQlTlWU8HvWPX0X4++Dfhbw14++Kt+LKSLwv4PtLB7bS4Lh2M13cpF5SSFjuMBcyeZtkVwCNh9Oa8J+F/B/xG8Eaxr1/pkfg2Pw3qWnDUJ9IluJkubK6nET/JM8rJJHtZwykg5KlCcGkqiauRTxsZU1Npva+nWSVtLt9V333PGaK9g+LXhvTfCNrfx6f4Etv+EcuNtvovi621Oa7+0Mku0yvKkrW7PIsM+YQiMm/oNnPj9aRlzK6OujVVaPOl+X6NhRRRVGwUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFXrrQdTsoWmuNOu4IV+9JLAyqMnAySPWtT4e+T/wmWmef5eze2PMxjdsbb177sY98V7pdeT9lm+0+X9n2HzPNxs2453Z4xj1rKU+V2OGviHRkopXPmmu68C/8gib/ruf/QVrha7rwL/yCJv+u5/9BWtTuOFooooAKn0/ULrSb62vrG5ms722lWaC5t5DHJFIpBV1YcqwIBBHIIqCigAooooA9ak+N2map400XxtrXhaTUvGFjJbS3N1Hqfk2l7JCwCytAIiySeWqD5JAm5AxQjcjYOreNPCnifWNb1TWvC+pPf6jq11qIl0/WUg2xzMGWFg9vIG2HeQ4Ck7zkcDHB0VHJFbHLHDUo6xTXTd7dt9j1rXv2gbjx8viC18ZaLHqOmatdwaktvpNwbJ7S7ihSASxu6y5DRJtZHDDnK7SObml/tFJ4dvPhzJo/hiO1t/Bsl8I4Zr1pTdxXJw+5to2ybS2WGVLtkIqgR14zRS9nHaxH1Ohbl5dO13ba23oekQ/FDRNH+Hni3wnonhm5tYfEH2RpL6+1QXE8bQTeZjCwxoUI4ACqwLMSzgqq4/xH+IX/CwP+EX/ANA+wf2JoVrov+u8zzvJ3fvfujbu3fd5xjqa4+imopO5rHD04y50td932S/JI9a8R/tA3HiDx94w1t9FjGjeKrSGy1DRp7gygIiRqJY32hROvllo5GRthc/Ke+DB8Q9J0nTzoml+H7mLw5eX9re6tbXmqvJc6gsBfFuZY0jRYiHJwIywfDbjhQvB0UckVoiY4WlFKMVpp1fTb7u53knxG0/SPBviDw14a0a5sbDxB9n/ALQk1W/W8l/cSeZF5JjihCclt24PkEY2454OiiqSS2NoU4078vUKKKKZoFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABV6617U72FobjUbueFvvRyzsynByMgn1qjRQKye4V3XgX/AJBE3/Xc/wDoK1wtd14F/wCQRN/13P8A6CtAzhaKKKACiiigDU8P+F9V8VTXsek2Mt61jZzahcmMfLDbxIXkkcngAAd+pKqMlgCP4X1WPwvF4jNjKdDlvH09b5RmMXCoshjOPunY4IzjcA2M7Wx9A6P/AGl/wx9J/wAK5+y/avtE/wDwnv2Pd/aXkb3+z5zz9m8rO/Zx97t9oql+zD/wkn/CM+K/7T/sv/hTv7v/AIST/hI/M+yffj3fZfL+f7Zsxt2d/Kz83lV87PM5xp1ayStCfLZv3nrb75bwXVW197TbkV0u5870UV6F8MvBOmat4Z8aeLNbSS40zw1aQOlnA+03F3NKEgSToTBlW8za6OARtOa+hb5VdnLUqKnHml5fi7I89rY8XeEdW8B+IrvQtdtPsOq2uzzrfzEk27kV1+ZCVOVZTwe9d5D4f0Lx58JfFviSz0O28Mar4XntGcafNcSwX0NzJ5QRlnkdkdGUsHVsEMVK9GHp/wAZn8Na3+1Vd+GNU8Lx3y6td2Njcamb6eO6hea3hjSSDawiUJuRtskcm4q+WwwCZe01tbv+n+ZwyxlqnKovRSvtfTlfe20v61Pmj+xtQ/sf+1vsNz/ZXn/Zft3kt5Hnbd3l78bd+3nbnOOap19I+Mo7Lwj+zjrmhf2Lpt3/AGZ46n0b7Rm6TzJY7Nk+2bfPOJTtztyYhn7lQ3Hwd0PwP43tfCmu6Z4futP8u3i1XWrzxZDbXttLLAhllgiMse2ONn3KksDs4U5JDqFSqoUcdFpuS7221St5+Z86UV7ZceDfCXg/4Q6xr8mm23i7UbHxnNodrqDXU0drc262+5XZIpBuQ4LjY6nJU72UFW39Z+B3hzxD4+02TRrKTR9AuvBK+LrnTG1RUMRKMBDHczIyqDJ5W55OADIcgAKK9oi/r1Nbp2119Pnc+dKK9g8S+DfD9x8NdY1W4g8N+F/Eelzwta2mh+II9QXVIZGCOhiNzM6PEfn3hsFWIK8bhD+0loPhrwL8SNY8K+HPD8em29nJbym7ku55pTut0Yxrufasfzg4YM+4E7wpCK1UTdkaQxUak1CKd9e3S3n5ra55LRXpH7OfhHSfHnxk8PaFrtp9u0q6+0edb+Y8e7bbyuvzIQwwyqeD2o+CvhHSfF3/AAnn9rWn2v8Aszwpf6nafvHTy7iPy9j/ACkZxuPByDnkU3NK5dTERpuSa+FJ/e2v0PN6K9y/4RPw1pXgvw9rfh7wZH8Q9PS0hu/El3LqU/2ixlZW8238m3dGt418iVhNKjqd45IA3cr8avCOk+Ef+ED/ALJtPsn9p+FLDU7v947+ZcSeZvf5icZ2jgYAxwKSqJuxEMVGpNQSfXt0+d18zzeivbPid8PdN8L/AB88T6D4d8Jf2vpunwQSxafNdTC1tlaGBpJriXeHESmRyWMsaqWUs21SrbPiX4W+Gf7V+Cl3FottpieKL/7Fq2nadqxvrN/LuoomaKZZHI3iRsgSHbwM7lYle0WnmZ/XadoOz95XW3a/fsvQ8S8I+EdW8eeIrTQtCtPt2q3W/wAm38xI921GdvmchRhVY8ntWPXv/hPwV4P8TftQReBv+EZjt/D1pqWp2sifbbh5rkRpMU3vvGArRjaECnbgO0hyx45fC+i+B/hT4b8Wanpkev6t4ju7tLSzupZVs4rSAqju/lPFIJ/N6Ydk2E5AbGD2mtv66jWKTmo2eqVl115vO20fl37eY0V9C6T8N/h9qFr4Q8WarH/Y1hr2k39xB4e/tL7PazahZSonk/a5txiinG8/OwKE4EhyorzD4nabNoN5b6TqPgWPwVrNtJM0whluWS6iJVUKiaSTIVo5cSIxV93H3clxqKTsi6eKjVnyJO/y6XT662atpdHE1saZ4R1bWPDuta7Z2nnaVo3kfb7jzEXyfOcpH8pIZssCPlBx3xXrUPwvs/Bek+D7m90Lw/4kk1nTYNWvV1nxRHp5SKSRykduhmgdD5QXdI4lXecKMIdxrHwr8I6P4Z+M02mX0fiWPw9JpJ0fWIrjcFS4l/eDMbeXIQDsJI6oSAp4E+0XT+tTJ4yD0j3Xn9pR76b9fWztY8Nor3L4ieE/DXhXTJbzwt4Mj8V+CTaNDH4ubUp5rhbmSMMrzCF1jtzG88KiKWEFthHVsjw2rjLmV0dVGsq0eaK0+X+enowoooqzcKs6lpl5o95JZ39pPY3ceN9vcxtHIuQCMqQCMgg/jVavSP2jP+SyeIf+3f8A9J4qm+tjJzaqKHdN/db/ADOA1LTLzR7ySzv7Sexu48b7e5jaORcgEZUgEZBB/Gux8C/8gib/AK7n/wBBWr37Rn/JZPEP/bv/AOk8VUfAv/IIm/67n/0FaIvmSYUZupTjN9UmcLRRRVGoUUUUAanh/wAUar4VmvZNJvpbJr6zm0+5EZ+Wa3lQpJG4PBBB79CFYYKggfxRqsnheLw4b6UaHFePqC2KnEZuGRYzIcfeOxABnO0FsY3NnLorN04OXM4q47sK6rwL8QLjwXFrVk9pHqeja3afYtQsZ2IDJuDCWPqqzpgmORlfYWJ2muVoq2k9GZzhGouWS0Ow1Lx1aW/he/8ADnhvTLnSNK1GeC5vpLy/Nzc3DRB9kZZEij8oF923yy28AljhQveav+0HomrfFSX4hN4Hx4hjw1pFJqYlsxIsAjjlmjaHc7owDAxvEPkTgMC7eJ0VDhFmEsLSluu/V9bX6+SO8t/ij9s+HGreE9dsrnVPtWrNrkOox3vlzreNC0TNMXR/NQ5ViBsbIb5ueNLxh8W9H+It5Za14n8LyXvieC0S3uLuzv0tbW/eMtsknhWDeSVKq2yVMhQFMfGPMaKfJG9x/VqV+ZLX1fU7D/hYX/Fof+EG+wf8x3+2vt/nf9O/k+V5e3/gW7d7Y712Fl+0Zd6b4o8L6pBodtLZ6Z4bg8L6hpt44mi1K1QnzAcp8m/IOMNgqM7gSp8foocIvdBLDUp35o73/Hc3te1Hw5PZpDoeiX1hIZA8lxqWpLdvgAgIgSGJVBzltwYnamCo3Buk8feIrr48fE/WdftLWx0aS8jikNtf6rBCiCOKOLAmmMasTtB2jnk8EAmvPaKfL1L9kk1KO6utbve1+vkjvND/AOEj+C3iLSvFlld6I95Zz4iW21azv925GDK8cMrMEZCyluMbuGBINbGh/Frwz4P/AOEs/wCEf8GXNp/wkOk3elN9s1k3H2NZsY8rEK5Rcch97NtTDrht/ldFJxUtzOWHjU1qavyuv1PSPB/xQ0T4e61pfiPw94ZubfxNYQLGsl5qgnsXkMPlTSGAQrJ84LttE2FZhjKjbUN/8TtM8TaHoMHibw9Jq+raFaDT7O9t9Q+zRS2qcww3EQjJcISw3RvEzK2C24B689oo5Fe4/q9Pm5ra97u/XrfzPZvEX7Q1v4s1z4gS6l4bkGjeMI7AXFpa6iI7i3e02eWUmaFlIO1sgx/xDBGOc28+NloU+GkFh4b+w2fgm/lu4ojfGRrtWuI5sMxT5X/d/MwG0sxKogwg8ropeziun9bERwdCKSUdvN9uXv20PSPCvxi/4Rn45T/EX+yPtPmX97e/2b9p2Y+0LKNvmbD93zeu3nb0GazdL+IlvL4LtfCfiPS5NX0ayu5L2yltboW95bO6gNEkrxyqsDYLmNUGXw27jB4minyRNPq9O97du/S9vzZ3l18Q9J1lLLTdX8P3M3hzS7F7LS7Cy1V4ZbZmuPOa4aR0kR5XBZXIjVSCMKm1QLmofFXSdU8O2Ph268K/atC0exvrfRVn1F/tVrcXLpIbiWVFVZtjq21PLQYbByRk+b0UciF9Xp6abeb/AMz0jUvippfjDwv4c0vxdoFzqN5oMDWVpqWlX0VlLJa4Xy4pt1vLv8vawUgrwxyCSWPSaNDL4U+AfxHn1nT/APhHk8Wz6aNDs5FdftSxTGeQwq5ZzEkciYkYlTuUbixrxOilyLZESw0bKMdFdP7nfTWy1PSLX4oaJ4V/4SKTwj4ZudJm12xudLuI9S1QXsENrN95YlEMbhwQm1nd+AchicjzeiiqUUtjeFONO/L1+f5hRRRVGoV6bY/H7WoY9Oe/0bQNc1GwRUh1TVLIy3YCsWTMgcHKk8Hrnk5JJPmVFS4qW5lUpQq/Grl3W9bvvEmrXOp6ncyXl9cvvlmk6sen0AAwABwAABgCut8C/wDIIm/67n/0Fa4Wu68C/wDIIm/67n/0Fao0SSVkcLRRRQMK73VPgn4n0Hxh4P8ADGrQRaXrPieO2ltre6Zla3E87QxicBSUOU3FQCVBAIDZUT/s9+LtB8B/Gbwvr3iaLzdFs7hmmbyBN5TGN1jl2d/LkZHyMsNmVBYAVqfGr4deL0+NC6bqWrReMdY8TSQXOl6xBPH5eqRTtst3X5tsYONgXIVQuFJQKx8itipxxaw91GPK5XfV7WXT3d5a31XS7NFFctzzvxR4X1XwV4gvtD1yxl03VbGTyp7aYfMpxkEEcEEEEMCQwIIJBBrLr3P9qDUpIZvDfhjWPFkXjjxhoEcsGqavDaIiwgpCEsjOPnuDE6TsXcZzMQcNvA8MrpwOIlisNCtNWb7Xt6q6Ts91foyZLldgor2b4ySWfwh8YXvgfR9F0ie1stNitL66vrCO4lvLqW1Be6SSQNLbkeYpWNJNqtHnHzMDs6PJZar4b8Mj4aaL4W1TULK0jm1vQdYsI7nVL67VGM7Ri4DeZAVt8hLVw480/KCRt6faaJ20Z531r3I1FHSWz6W8+x4BRXv+jyWWq+G/DI+Gmi+FtU1CytI5tb0HWLCO51S+u1RjO0YuA3mQFbfIS1cOPNPygkbeI+BPgfTfGviLXX1Zrb7BouhXurPFeCbyJWjQKgkMLCQIrSK52ZYhCAOaPaaNvoUsUuWU5K3L06/d59Dzetjxd4b/AOER8RXek/2pputfZ9n+naRcefaybkVvkfAzjdg8cEEdq9I8S33hLVPhrrEeqa34bv8AxZazwzaNL4Z0eawaVWYLcRXH+iwxsgUB0JG4MG+bB2n0PxzY+BvDXxw+I2jtp/h/RdZePT4/Dk2rWh/sizkeCNZzJEitGCRLvVpI2QMm5iO69prt/Wn+Zi8W1Kzi+uluzjr3t73bv2Plyiuw+J1vrtrrGnw+INH03SbxbGN45NJtbeGC9hdnkScG3/cyZD7d6cYjAPKmuPrVO6uehCXPFS/4IUUUUywooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACtLxH4c1HwlrNxpOq2/2W/t9vmw71fbuUMOVJB4YHg96za9R/aI0y8m+NWqxR2k7y3n2b7MixsTPmGNBsGPm+ZSvHcEdRU395IwlUcasYdGm/ut/mcB4j8Oaj4S1m40nVbf7Lf2+3zYd6vt3KGHKkg8MDwe9dR4F/5BE3/Xc/8AoK1e/aM/5LJ4h/7d/wD0niqj4F/5BE3/AF3P/oK0RfNFMdGbqUoze7SZwtFFFUbBV2HXNSt7jTp4tQuop9Nx9hkSZg1riRpB5RB+TEjM/wAuPmYnqapUUnFS3QBRRRTA9C1v41aj4qs7X/hIdF0jxBq1rpr6VDrWorcPdLERIFYgTCJpE81isjRlshWJLDNQ6N8XbnwzdWOpaF4e0TRfENlBHBDrdpHOZxtiETOYnlaAu6bgxMXJdm4Y5rg6Kjkjsc/1elbltp26elu3lsd5o3xdufDN1Y6loXh7RNF8Q2UEcEOt2kc5nG2IRM5ieVoC7puDExcl2bhjmjQfjNrfhPxRoOvaDaabod5pVjFp7x2FsY4tQjQncbpd37x343NwcqpG0qCODoo5Y9geHpO943vp8u3p5HbWez4iXkGjaZpPhbwhGZFmnvJr1oEwDsDNLdzuwC72JSLlupVyi7eq+L3xd0LxP8VPF+rWPh7TfEekalPam3m1eO4ilXyIPJ3RmGWN1RzklWPICEqCuB4/RRyq9yfq8XPmb2VuvVp779Eb3jDxtqfji8sptRaMR2Fomn2VvCm1La1QsY4VPLMF3EBnLMe7E81g0UVSVtEdEYqC5YqyCiiimUFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV3eifHLxx4d0m202w16SOztk8uGOS3hlKr2Xc6E4HQDPAAA4ArhKKTipboznThUVpxT9SS6upr25luLiWSe4mcySSysWd2JyWJPJJPOTXbeBf8AkETf9dz/AOgrXC13XgX/AJBE3/Xc/wDoK0zQ4WiiigArvdU+CfifQfGHg/wxq0EWl6z4njtpba3umZWtxPO0MYnAUlDlNxUAlQQCA2VE/wCz34u0HwH8ZvC+veJovN0WzuGaZvIE3lMY3WOXZ38uRkfIyw2ZUFgBWp8avh14vT40LpupatF4x1jxNJBc6XrEE8fl6pFO2y3dfm2xg42BchVC4UlArHyK2KnHFrD3UY8rld9XtZdPd3lrfVdLs0UVy3PO/FHhfVfBXiC+0PXLGXTdVsZPKntph8ynGQQRwQQQQwJDAggkEGsuvc/2oNSkhm8N+GNY8WReOPGGgRywapq8NoiLCCkISyM4+e4MTpOxdxnMxBw28DwyunA4iWKw0K01Zvte3qrpOz3V+jJkuV2Civf/ABnJZappl/f/AAy0Xwtf+C7fTZRcaW9hHLrNirRr5txOZgbglJLg7ZYZHjQRLk4U5xvjJJZ/CHxhe+B9H0XSJ7Wy02K0vrq+sI7iW8upbUF7pJJA0tuR5ilY0k2q0ecfMwPSql9LHnU8V7RqKjq7u3VWtv2eq0/E8ZrY8SeG/wDhHP7L/wCJppuqfb7GK+/4ltx532ffn9zLwNkq4+ZecZHNeneL5LPw3+z98Mrmx0XSF1PWY9XhvNSlsI5bh40uQoXcwIBwww+N6bQEZQWDeh+GfBvh+4+OvwTsJdD02Sw1DwZb3V5avaRmK5mNrdMZJFxh3JVTuOTlR6UnUsr+v4Gc8Zyxc7aLm+fLf/I+Vq2PEnhHVvCP9l/2tafZP7TsYtTtP3iP5lvJnY/yk4ztPBwRjkV6p8L762+Imn+NbPUND0S2TQ9Cn1/S5bLS4EltprUqUiZypNxEwcq63HmlgASd2WPY/EjxP4fm8ZfB6Dx1ZW1x4Tn8KafdX62djHDKrPHOikPCqyLErMreUjBQFOEJwCOo1K1gni5xq8nLte/fa6t/X+Z8xUV6p8VI9dbQfts2m+Errw5fX6SWmteFdPt4ooZPKLm13oiSp8swzHcDeTECPusTsaT4R03wn8JfCHiMat4b03V/EM9+zXPiHTJr3EEMiRCGOMQzxfeVnMjIr/OqqcBs1z6Jm31lKEZW1bsra9L9vJ9Dyvw34b/4SP8AtT/iaabpf2Cxlvv+JlceT9o2Y/cxcHfK2flXjODzWPX0jpOk+A/EXijx5eaDb6bqUJ+H15qNzFDZyLZ2WqAqJDaJOgeNAcMndd5AIHyjN0eSy1Xw34ZHw00XwtqmoWVpHNreg6xYR3OqX12qMZ2jFwG8yArb5CWrhx5p+UEjbHtPIxWMd3eLW2+ltOp4BRRRW56YUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFaXiPw5qPhLWbjSdVt/st/b7fNh3q+3coYcqSDwwPB71m16j+0Rpl5N8atVijtJ3lvPs32ZFjYmfMMaDYMfN8yleO4I6ipv7yRhKo41Yw6NN/db/M4DxH4c1HwlrNxpOq2/2W/t9vmw71fbuUMOVJB4YHg966jwL/yCJv8Aruf/AEFavftGf8lk8Q/9u/8A6TxVR8C/8gib/ruf/QVoi+aKY6M3UpRm92kzhaKKKo2CrsOualb3GnTxahdRT6bj7DIkzBrXEjSDyiD8mJGZ/lx8zE9TVKik4qW6AKKKKYHeXHxduYP7YfQ/D2ieFrnWIJrS+m0mOf8Ae28v+shEc0skcaE7T+7VSu0BSBkGbW/jVqPiqztf+Eh0XSPEGrWumvpUOtaitw90sREgViBMImkTzWKyNGWyFYksM157RUckTn+r0t7a/j9+50mu+PdQ8QeDfDHhq4htksPD32r7LJErCV/PkEj+YSxBwRxgDjrmvVfgj8Vv7c+Ovw7v/Elxpuj2GgaS2jx3Tv5EQhjtZ1jaRnYjeS+M8AkjArwaiiUFJNf1qTUw1OpTlTta9/8Aybf8ztrv4rXsnhHV/D9jo+kaNb61JBJqk9hFIr3hhZnQFWkaOIb2LbYUjHAAAX5amufi7c6pB4fXWPD2ia5Notj/AGXDcX0c+6W0CSokTqkqp8glyrqqyBkQ7sg54Oinyor2FPt/Vrfkehaf8aL3SVsbO08O+H18PWt22oHw9cW0l1ZTXRheHzpPOkeQkI4wocKCinbnJNPRfixqOn+Cx4S1LT7HxJ4ejuxfW1jqjXAFrLtcExtDLGwDeYxKklckkAEkniaKXLEPq9Lt/Xf18z2b4d+ILPwzofjzxTqs2kWEGueHrjQdN0fTJ42uDLLtRP3AYukaLCS0kxDNlWzIz5PK6N8XbnwzdWOpaF4e0TRfENlBHBDrdpHOZxtiETOYnlaAu6bgxMXJdm4Y5rg6KXItbkLDQvJy1v8Altbz877hRRRWh1hRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV3eifHLxx4d0m202w16SOztk8uGOS3hlKr2Xc6E4HQDPAAA4ArhKKTipboznThUVpxT9SS6upr25luLiWSe4mcySSysWd2JyWJPJJPOTXbeBf+QRN/13P/oK1wtd14F/5BE3/Xc/+grTNDhaKKKAOp8ceD4fCf2DybiSfz0YPvAHzLjJGOx3dO2OprlqKK0qJKTSMqUnKCbCiiiszUKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAruvAv8AyCJv+u5/9BWiigD/2Q=="},4922:(e,r,s)=>{s.d(r,{A:()=>a});const a=s.p+"assets/files/test-project-3aaf4dcbda2c9e242c15a57f21d4cefe.zip"},6024:(e,r,s)=>{s.d(r,{A:()=>a});const a=s.p+"assets/images/007-5307721d86e5069ac4c0d62fc18f86d8.jpg"},7484:(e,r,s)=>{s.d(r,{A:()=>a});const a=s.p+"assets/images/003-690513153b9ef6265dfa7fc9df316889.jpg"},7642:(e,r,s)=>{s.d(r,{A:()=>a});const a=s.p+"assets/images/005-624205b9efb27d1e1315022fdbcd4dfd.jpg"},8453:(e,r,s)=>{s.d(r,{R:()=>t,x:()=>i});var a=s(6540);const o={},n=a.createContext(o);function t(e){const r=a.useContext(n);return a.useMemo((function(){return"function"==typeof e?e(r):{...r,...e}}),[r,e])}function i(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:t(e.components),a.createElement(n.Provider,{value:r},e.children)}},8537:(e,r,s)=>{s.d(r,{A:()=>a});const a=s.p+"assets/images/002-8e9638747692a350bf89d807c88aeb73.jpg"},9785:(e,r,s)=>{s.r(r),s.d(r,{assets:()=>l,contentTitle:()=>i,default:()=>c,frontMatter:()=>t,metadata:()=>a,toc:()=>d});const a=JSON.parse('{"id":"backend/node/tutorials/intro_web/LAB13MVC/readme","title":"MVC","description":"MVC (Modelo Vista Controlador)","source":"@site/docs/backend/node/tutorials/intro_web/LAB13MVC/readme.md","sourceDirName":"backend/node/tutorials/intro_web/LAB13MVC","slug":"/backend/node/tutorials/intro_web/LAB13MVC/","permalink":"/docs/docs/backend/node/tutorials/intro_web/LAB13MVC/","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/backend/node/tutorials/intro_web/LAB13MVC/readme.md","tags":[],"version":"current","sidebarPosition":13,"frontMatter":{"sidebar_position":13},"sidebar":"tutorialSidebar","previous":{"title":"HTML Din\xe1mico (EJS)","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab12EJS/"},"next":{"title":"Sesiones","permalink":"/docs/docs/backend/node/tutorials/intro_web/LAB14Sesiones/"}}');var o=s(4848),n=s(8453);const t={sidebar_position:13},i="MVC",l={},d=[{value:"MVC (Modelo Vista Controlador)",id:"mvc-modelo-vista-controlador",level:2},{value:"Rutas y Controladores",id:"rutas-y-controladores",level:2},{value:"Modelos",id:"modelos",level:2},{value:"Vistas",id:"vistas",level:2}];function u(e){const r={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,n.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(r.header,{children:(0,o.jsx)(r.h1,{id:"mvc",children:"MVC"})}),"\n",(0,o.jsx)(r.h2,{id:"mvc-modelo-vista-controlador",children:"MVC (Modelo Vista Controlador)"}),"\n",(0,o.jsx)(r.p,{children:"Con lo que hemos trabajado en nuestro servidor hasta el momento, hemos cargado rutas, hemos trabajado con HTML din\xe1mico y ya podemos crear sitios web, pero nos faltan 2 puntos elementales, conectar datos e informaci\xf3n y procesarlas para cargarlo en nuestro HTML."}),"\n",(0,o.jsx)(r.p,{children:"Con lo que hemos visto hasta ahora en el HTML din\xe1mico, podemos definir lo que llamamos como UI en vistas que es toda la interfaz de HTML, CSS y JS para poder verlo en el navegador."}),"\n",(0,o.jsx)(r.p,{children:"Tambi\xe9n hemos experimentado un poco en las rutas cuando no regresamos directamente un HTML y regresamos por ejemplo un JSON de informaci\xf3n."}),"\n",(0,o.jsx)(r.p,{children:"Igualmente ya vimos que dentro de EJS, aunque cargamos el EJS como HTML, podemos pasar un JSON para agregar informaci\xf3n desde nuestra ruta."}),"\n",(0,o.jsx)(r.p,{children:"El punto que empezaremos a ver ahora es uno de los m\xe1s importantes desde el punto de ingenier\xeda de software, la arquitectura del proyecto."}),"\n",(0,o.jsx)(r.p,{children:"Cuando hablamos de la arquitectura nos estamos refiriendo a la forma en la que las carpetas del proyecto est\xe1n estructuradas."}),"\n",(0,o.jsx)(r.p,{children:"Como ya habr\xe1s notado un proyecto grande de desarrollo, incluye archivos HTML, CSS, JS para el cliente, archivos js de back-end, de front-end y sobre ellos viene la l\xf3gica de crear m\xf3dulos en nuestra aplicaci\xf3n seg\xfan las funciones que tiene."}),"\n",(0,o.jsx)(r.p,{children:"Cuando ya tenemos una gran cantidad de archivos, esto se vuelve complicado si no planificamos como estructurar nuestros archivos, y es por ello que viene la primer aproximaci\xf3n en una de las arquitecturas m\xe1s comunes utilizadas en desarrollo web."}),"\n",(0,o.jsx)(r.p,{children:"Hasta ahora cuando habl\xe1bamos de arquitectura nos refer\xedamos a cliente-servidor. Pero esta arquitecturas nos habla de como se establece el protocolo de comunicaci\xf3n del sistema."}),"\n",(0,o.jsx)(r.p,{children:"Ahora la arquitectura de software como ya mencion\xe9 habla de la estructura de carpetas del proyecto, si bien existen una gran cantidad de arquitecturas, todas con un prop\xf3sitos espec\xedfico, en desarrollo de back-end la m\xe1s com\xfan y simple es MVC o mejor conocida como Modelo-Vista-Controlador."}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.img,{alt:"lab_13",src:s(2490).A+"",width:"300",height:"168"})}),"\n",(0,o.jsx)(r.p,{children:"Un diagrama que explica de manera sencilla como funciona la arquitectura es el siguiente:"}),"\n",(0,o.jsx)(r.p,{children:"Tendremos una petici\xf3n HTTP, hasta ahora la hemos resuelto directamente en una funci\xf3n de Javascript, pero dentro del servidor lo ideal es separar seg\xfan la fuente, esto hace m\xe1s legible el c\xf3digo, estructura mejor la carpeta de archivos y permite probar lo que estamos haciendo."}),"\n",(0,o.jsxs)(r.p,{children:["Al llegar el request HTTP, llegamos a la ruta o lo que ahora conoceremos como el ",(0,o.jsx)(r.strong,{children:"controlador"}),", dentro de este lo m\xe1s com\xfan ser\xe1 tener que llamar a alg\xfan elemento de informaci\xf3n, normalmente una base de datos, lo que sucede en la arquitectura es pasar esta responsabilidad a otro archivo el cual llamaremos el ",(0,o.jsx)(r.strong,{children:"modelo"})," que su \xfanica funci\xf3n es obtener y manejar la informaci\xf3n, por \xfaltimo se regresar\xe1 al ",(0,o.jsx)(r.strong,{children:"controlador"})," y este har\xe1 el pre renderizado del HTML con EJS como en nuestro caso y devolver\xe1 la petici\xf3n al cliente habiendo cargado en el c\xf3digo HTML la informaci\xf3n que se ocupa de la base de datos."]}),"\n",(0,o.jsx)(r.p,{children:"Todo este camino se realizar\xe1 para cada ruta de cada funci\xf3n de cada m\xf3dulo del sistema, hacerlo de manera efectiva nos ayudar\xe1 a que existan partes del c\xf3digo replicadas y simplificar\xe1 la llamada de funciones, ya que no necesitamos crear un query a la base de datos para obtener lo usuarios en cada ruta, sino que esto lo delegaremos a un modelo que har\xe1 esta llamada y se insertar\xe1 cada vez que lo utilicemos en un controlador."}),"\n",(0,o.jsx)(r.p,{children:"Algo adicional que podemos o no a\xf1adir en NodeJS y Express, es un archivo de rutas, esto hace m\xe1s f\xe1cil ver que rutas hay disponibles en nuestro proyecto."}),"\n",(0,o.jsx)(r.p,{children:"Veamos un ejemplo sencillo abarcando toda la arquitectura."}),"\n",(0,o.jsx)(r.h2,{id:"rutas-y-controladores",children:"Rutas y Controladores"}),"\n",(0,o.jsxs)(r.p,{children:["Vamos a crear un nuevo proyecto base con ",(0,o.jsx)(r.strong,{children:"npm init"}),", aqu\xed vamos a agregar express, body-parser y ejs. Tambi\xe9n vamos a configurar la carpeta p\xfablica como ya hicimos en el laboratorio anterior. Por facilidad en la carpeta p\xfablica a\xf1adiremos un archivo script.js con un alert como en el laboratorio anterior."]}),"\n",(0,o.jsxs)(r.p,{children:["El contenido de ",(0,o.jsx)(r.strong,{children:"index.js"})," quedar\xeda como el siguiente:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"const http = require('http');\nconst express = require('express');\nconst path = require('path');\nconst fs = require('fs');\nconst app = express();\n\napp.set('view engine', 'ejs');\napp.set('views', 'views');\n\nconst bodyParser = require('body-parser');\napp.use(bodyParser.urlencoded({extended: false}));\napp.use(express.static(path.join(__dirname, 'public')));\n\napp.get('/', (request, response, next) => {\n response.setHeader('Content-Type', 'text/plain');\n response.send(\"Hola Mundo\");\n response.end(); \n});\n\nconst server = http.createServer( (request, response) => { \n console.log(request.url);\n});\napp.listen(3000);\n"})}),"\n",(0,o.jsx)(r.p,{children:"Al correr el proyecto con pm2 no olvides que el comando es:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"pm2 start index.js --watch\n"})}),"\n",(0,o.jsx)(r.p,{children:"Ahora vamos a configurar un m\xf3dulo con una ruta como hicimos en laboratorios anteriores."}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.img,{alt:"lab_13",src:s(8537).A+"",width:"236",height:"232"})}),"\n",(0,o.jsxs)(r.p,{children:["Crearemos la carpeta ",(0,o.jsx)(r.strong,{children:"routes"})," y a\xf1adiremos un archivo ",(0,o.jsx)(r.strong,{children:"usuarios.routes.js"}),"."]}),"\n",(0,o.jsxs)(r.p,{children:["Ahora dentro de ",(0,o.jsx)(r.strong,{children:"index.js"})," a\xf1adiremos la ruta creada como si fuera un m\xf3dulo de usuarios."]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"const rutasUsuarios = require('./routes/usuarios.routes');\napp.use('/usuarios', rutasUsuarios);\n"})}),"\n",(0,o.jsxs)(r.p,{children:["El c\xf3digo de ",(0,o.jsx)(r.strong,{children:"index.js"})," quedar\xeda de la siguiente forma:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"const http = require('http');\nconst express = require('express');\nconst path = require('path');\nconst fs = require('fs');\nconst app = express();\n\napp.set('view engine', 'ejs');\napp.set('views', 'views');\n\nconst bodyParser = require('body-parser');\napp.use(bodyParser.urlencoded({extended: false}));\napp.use(express.static(path.join(__dirname, 'public')));\n\napp.get('/', (request, response, next) => {\n response.setHeader('Content-Type', 'text/plain');\n response.send(\"Hola Mundo\");\n response.end(); \n});\n\nconst rutasUsuarios = require('./routes/usuarios.routes');\napp.use('/usuarios', rutasUsuarios);\n\nconst server = http.createServer( (request, response) => { \n console.log(request.url);\n});\napp.listen(3000);\n"})}),"\n",(0,o.jsxs)(r.p,{children:["Si guardas el archivo ",(0,o.jsx)(r.strong,{children:"index.js"})," ver\xe1s muchos errores y es por que aunque ya existe ",(0,o.jsx)(r.strong,{children:"usuarios.routes.js"})," a\xfan no estamos soportando express para poderlo llamar. Para ello agrega lo siguiente en ",(0,o.jsx)(r.strong,{children:"usuarios.routes.js"}),":"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"const express = require('express');\nconst path = require('path');\nconst fs = require('fs');\nconst router = express.Router();\n\nrouter.get('/obtener_usuarios', ()=>{});\n\nmodule.exports = router;\n"})}),"\n",(0,o.jsxs)(r.p,{children:["Aqu\xed estaremos sirviendo una ruta ",(0,o.jsx)(r.strong,{children:"/obtener_usuarios"})," que de momento solo incluye una funci\xf3n flecha vac\xeda."]}),"\n",(0,o.jsxs)(r.p,{children:["En laboratorios anteriores servimos la ruta desde aqu\xed, pero ahora vamos a incorporar el controlador, para ello vamos a crear una carpeta llamada ",(0,o.jsx)(r.strong,{children:"controllers"}),", y a esta vamos a a\xf1adirle un archivo ",(0,o.jsx)(r.strong,{children:"usuarios.controller.js"}),"."]}),"\n",(0,o.jsxs)(r.p,{children:["Dentro de ",(0,o.jsx)(r.strong,{children:"usuarios.routes.js"})," vamos a a\xf1adir el controlador debajo de la definici\xf3n de route:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:'const controller = require("../controllers/usuarios.controller.js")\n'})}),"\n",(0,o.jsxs)(r.p,{children:["Ahora vamos a sustituir la funci\xf3n flecha vac\xeda que ten\xedamos en la ruta ",(0,o.jsx)(r.strong,{children:"/obtener_usuarios"})," y vamos a llamar a una funci\xf3n dentro de nuestro controller."]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"router.get('/obtener_usuarios', controller.index);\n"})}),"\n",(0,o.jsxs)(r.p,{children:["Por \xfaltimo en ",(0,o.jsx)(r.strong,{children:"usuarios.controller.js"})," vamos colocar lo siguiente:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:'module.exports.index = async(req,res) =>{\n res.status(200).send({status:"success",message:"Get all users"})\n}\n'})}),"\n",(0,o.jsxs)(r.p,{children:["Aqu\xed definimos la funci\xf3n ",(0,o.jsx)(r.strong,{children:"index"})," y de momento solo regresamos un json de respuesta."]}),"\n",(0,o.jsx)(r.p,{children:"Es importante que entiendas todo el camino hasta el momento pues ello es la base para lo que viene."}),"\n",(0,o.jsx)(r.p,{children:"Si entras a la url en el navegador"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"http://localhost:3000/usuarios/obtener_usuarios\n"})}),"\n",(0,o.jsx)(r.p,{children:"El resultado ser\xe1 el siguiente:"}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.img,{alt:"lab_13",src:s(7484).A+"",width:"1920",height:"518"})}),"\n",(0,o.jsx)(r.p,{children:"Pero internamente lo que sucede es lo siguiente:"}),"\n",(0,o.jsxs)(r.ol,{children:["\n",(0,o.jsx)(r.li,{children:"Llega un request a index.js a la url /usuarios /obtener_usuarios"}),"\n",(0,o.jsxs)(r.li,{children:["Index.js encuentra /usuarios definido por lo que lo pasa al archivo ",(0,o.jsx)(r.strong,{children:"usuarios.routes.js"}),"."]}),"\n",(0,o.jsxs)(r.li,{children:["Usuarios detecta la url /obtener_usuarios y detecta que se llama de una funci\xf3n index en ",(0,o.jsx)(r.strong,{children:"usuarios.controller.js"}),"."]}),"\n",(0,o.jsx)(r.li,{children:"Desde usuarios.controller.js se llama y ejecuta la respuesta del request."}),"\n"]}),"\n",(0,o.jsx)(r.p,{children:"Con lo anterior a nivel arquitectura hemos segmentado en carpetas nuestras rutas y controladores, por lo que ahora en rutas vamos a tener una lista de urls con funciones que se llaman del controlador."}),"\n",(0,o.jsxs)(r.p,{children:["El controlador es el encargado de hacer el renderizado y servir de ",(0,o.jsx)(r.strong,{children:"cerebro"})," para la funci\xf3n solicitada. Aqu\xed lo que nos falta es conectar con un modelo y cargar el EJS de la vista para completar la arquitectura."]}),"\n",(0,o.jsx)(r.h2,{id:"modelos",children:"Modelos"}),"\n",(0,o.jsx)(r.p,{children:"Como ya mencionamos anteriormente, los modelos son la parte de conexi\xf3n con los datos de informaci\xf3n. No confundir con que es un medio exclusivo para conectar con la base de datos, ya que dentro de un sistema podemos tener diferentes fuentes de informaci\xf3n como: archivos, fuentes de datos, conexiones con otros sistemas y s\xed las bases de datos."}),"\n",(0,o.jsxs)(r.p,{children:["Los modelos son parte de nuestra arquitectura por lo que es necesario definirlos en una carpeta ",(0,o.jsx)(r.strong,{children:"models"})," de nuestra aplicaci\xf3n. Para ello vamos a crear la carpeta y dentro un archivo que llamaremos usuarios.model.js."]}),"\n",(0,o.jsxs)(r.p,{children:["Al igual que la ruta al controlador, es necesario conectar el controlador al modelo. Por lo que en el controlador ",(0,o.jsx)(r.strong,{children:"usuarios.controller.js"})," debemos a\xf1adir al inicio del archivo lo siguiente:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:'const model = require("../models/usuarios.model.js")\n'})}),"\n",(0,o.jsxs)(r.p,{children:["Ahora para el contenido de ",(0,o.jsx)(r.strong,{children:"usuarios.model.js"})," tendremos lo siguiente:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:'exports.ObtenerUsuarios = function(correo,contrasena){\n console.log("Obtener Usuarios");\n}\n'})}),"\n",(0,o.jsx)(r.p,{children:"Aqu\xed estamos creando una funci\xf3n ObtenerUsuarios, que conectar\xeda con nuestra fuente de informaci\xf3n y regresar\xeda los datos."}),"\n",(0,o.jsxs)(r.p,{children:["Por \xfaltimo dentro de ",(0,o.jsx)(r.strong,{children:"usuarios.controller.js"})," en la funci\xf3n index vamos a llamar al modelo de la siguiente manera:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:'const model = require("../models/usuarios.model.js")\n\nmodule.exports.index = async(req,res) =>{\n model.ObtenerUsuarios()\n res.status(200).send({status:"success",message:"Get all users"})\n}\n'})}),"\n",(0,o.jsx)(r.p,{children:"Si entramos a la url"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"http://localhost:3000/usuarios/obtener_usuarios\n"})}),"\n",(0,o.jsx)(r.p,{children:"Vamos a nuestra terminal y observamos que el log se realiza correctamente:"}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.img,{alt:"lab_13",src:s(3479).A+"",width:"1046",height:"557"})}),"\n",(0,o.jsx)(r.p,{children:"Ahora, depender\xe1 de nuestro motor de base de datos o fuente de informaci\xf3n, pero las buenas pr\xe1cticas nos piden crear objetos para almacenar la informaci\xf3n de nuestros modelos. Es decir crear objetos aunque sea en formato JSON que guarden la estructura de nuestros datos para poderlo manipular."}),"\n",(0,o.jsx)(r.p,{children:"Idealmente estos se separan en archivos adicionales, pero por facilidad vamos a declararlos en el mismo archivo de modelo simulando una llamada a una fuente de datos."}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:'exports.ObtenerUsuarios = function(correo,contrasena){\n let usuarios = [];\n\n usuarios.push({\n nombre:"Samuel",\n id:1,\n activo:true\n });\n usuarios.push({\n nombre:"Lisa",\n id:1,\n activo:true\n });\n usuarios.push({\n nombre:"Bob",\n id:1,\n activo:true\n });\n usuarios.push({\n nombre:"Alicia",\n id:1,\n activo:true\n });\n\n return usuarios;\n}\n'})}),"\n",(0,o.jsx)(r.p,{children:"Ahora si regresamos al controlador e imprimimos el valor de length de los usuarios regresados:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:'const model = require("../models/usuarios.model.js")\n\nmodule.exports.index = async(req,res) =>{\n const usuarios = model.ObtenerUsuarios()\n console.log(usuarios.length)\n res.status(200).send({status:"success",message:"Get all users"})\n}\n'})}),"\n",(0,o.jsx)(r.p,{children:"Nuestro resultado ser\xe1:"}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.img,{alt:"lab_13",src:s(7642).A+"",width:"1369",height:"656"})}),"\n",(0,o.jsx)(r.p,{children:"Listo, hemos conectado un modelo dentro de nuestra arquitectura, ahora solo falta la vista."}),"\n",(0,o.jsx)(r.h2,{id:"vistas",children:"Vistas"}),"\n",(0,o.jsx)(r.p,{children:"Como vimos en el laboratorio anterior, debemos hacer uso de un HTML din\xe1mico para definir nuestra vista y cargar los datos de nuestro modelo y controlador."}),"\n",(0,o.jsxs)(r.p,{children:["Dentro de nuestro archivo ",(0,o.jsx)(r.strong,{children:"index.js"})," hemos definido el uso de ejs, pero en la estructura del proyecto no hemos agregado la carpeta. Por lo que vamos a crear ",(0,o.jsx)(r.strong,{children:"views"})," y dentro de la misma una carpeta que se llame ",(0,o.jsx)(r.strong,{children:"usuarios"})," para que desde adentro tengamos un archivo ",(0,o.jsx)(r.strong,{children:"obtener_usuarios.ejs"}),".,"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"\n\n\n <%- include('./../css.ejs') %>\n\n\n
Obtener Usuarios
\n\n<%- include('./../scripts.ejs') %>\n\n"})}),"\n",(0,o.jsx)(r.p,{children:"Tambi\xe9n vamos a crear los archivos css y scripts, pero estos los colocaremos fuera de la carpeta de usuarios. De momento no a\xf1adiremos nada a estos archivos pero esta es una estructura que te recomiendo para que todo tu m\xf3dulo de usuarios contenga al menos los mismos css y scripts del proyecto."}),"\n",(0,o.jsx)(r.p,{children:"El resultado de la estructura se ver\xeda de la siguiente manera:"}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.img,{alt:"lab_13",src:s(3893).A+"",width:"286",height:"165"})}),"\n",(0,o.jsxs)(r.p,{children:["Si vamos a nuestro archivo ",(0,o.jsx)(r.strong,{children:"usuarios.controller.js"})," vamos a renderizar nuestra vista sustituyendo el send que ten\xedamos por lo siguiente:"]}),"\n",(0,o.jsx)(r.p,{children:'const model = require("../models/usuarios.model.js")'}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:'module.exports.index = async(req,res) =>{\n const usuarios = model.ObtenerUsuarios()\n console.log(usuarios.length)\n //res.status(200).send({status:"success",message:"Get all users"})\n res.render("./usuarios/obtener_usuarios")\n}\n'})}),"\n",(0,o.jsx)(r.p,{children:"Observa como a diferencia del laboratorio anterior, usamos la estructura de archivos para cargar nuestra carpeta de usuarios y luego nuestro ejs para modularizar nuestra funcionalidad."}),"\n",(0,o.jsxs)(r.p,{children:["Por \xfaltimo vamos a cargar nuestros usuarios en una etiqueta ",(0,o.jsx)(r.strong,{children:"ul"}),", primero vamos a pasarlos desde el resultado de nuestro modelo a la vista usando el par\xe1metro de json que recibe el m\xe9todo render."]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:'const model = require("../models/usuarios.model.js")\n\nmodule.exports.index = async(req,res) =>{\n const usuarios = model.ObtenerUsuarios()\n console.log(usuarios.length)\n //res.status(200).send({status:"success",message:"Get all users"})\n res.render("./usuarios/obtener_usuarios",{\n usuarios: usuarios\n })\n}\n'})}),"\n",(0,o.jsx)(r.p,{children:"Y dentro de nuestro ejs vamos a cargar el c\xf3digo de la siguiente manera:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{children:"\n\n\n <%- include('./../css.ejs') %>\n\n\n
Obtener Usuarios
\n
\n <% for(var i = 0; i < usuarios.length; i++){ %>\n
\n\n<%- include('./../scripts.ejs') %>\n\n"})}),"\n",(0,o.jsx)(r.p,{children:"Aqu\xed haremos uso de la estructura que definimos en nuestro modelo. Y la cargaremos dentro de nuestro c\xf3digo."}),"\n",(0,o.jsx)(r.p,{children:"El resultado final en el navegador lo veremos de la siguiente manera:"}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.img,{alt:"lab_13",src:s(6024).A+"",width:"1919",height:"688"})}),"\n",(0,o.jsx)(r.p,{children:"Y con esto hemos conectado la arquitectura completa de nuestro proyecto, tenemos el modelo para cargar informaci\xf3n, el controlador para manipular la transacci\xf3n y la vista para visualizarlo, adem\xe1s de la ruta para ver el tipo de url que estamos llamando."}),"\n",(0,o.jsx)(r.p,{children:"Entiendo que de inicio parecen muchos archivos, pero esta buena pr\xe1ctica har\xe1 que tu c\xf3digo sea m\xe1s simple en proyectos grandes pues la misma forma de repetir las cosas una y otra vez hace que el c\xf3digo sea lo m\xe1s igual posible."}),"\n",(0,o.jsx)(r.p,{children:(0,o.jsx)(r.a,{target:"_blank","data-noBrokenLinkCheck":!0,href:s(4922).A+"",children:"Ver ejemplo completo"})})]})}function c(e={}){const{wrapper:r}={...(0,n.R)(),...e.components};return r?(0,o.jsx)(r,{...e,children:(0,o.jsx)(u,{...e})}):u(e)}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/5159.a7517e8d.js b/deprecated/old-docs-build/docs/assets/js/5159.a7517e8d.js
new file mode 100644
index 0000000..25fdc4d
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/5159.a7517e8d.js
@@ -0,0 +1 @@
+(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[5159],{540:(e,t,n)=>{"use strict";n.d(t,{A:()=>ze});var s=n(6540),a=n(8453),r=n(5260),c=n(2303),o=n(4164),i=n(5293),l=n(6342);function d(){const{prism:e}=(0,l.p)(),{colorMode:t}=(0,i.G)(),n=e.theme,s=e.darkTheme||n;return"dark"===t?s:n}var u=n(7559),m=n(8426),h=n.n(m),f=n(9532),p=n(4848);const x=/title=(?["'])(?.*?)\1/,g=/\{(?[\d,-]+)\}/,j={js:{start:"\\/\\/",end:""},jsBlock:{start:"\\/\\*",end:"\\*\\/"},jsx:{start:"\\{\\s*\\/\\*",end:"\\*\\/\\s*\\}"},bash:{start:"#",end:""},html:{start:"\x3c!--",end:"--\x3e"}},b={...j,lua:{start:"--",end:""},wasm:{start:"\\;\\;",end:""},tex:{start:"%",end:""},vb:{start:"['\u2018\u2019]",end:""},vbnet:{start:"(?:_\\s*)?['\u2018\u2019]",end:""},rem:{start:"[Rr][Ee][Mm]\\b",end:""},f90:{start:"!",end:""},ml:{start:"\\(\\*",end:"\\*\\)"},cobol:{start:"\\*>",end:""}},v=Object.keys(j);function N(e,t){const n=e.map((e=>{const{start:n,end:s}=b[e];return`(?:${n}\\s*(${t.flatMap((e=>[e.line,e.block?.start,e.block?.end].filter(Boolean))).join("|")})\\s*${s})`})).join("|");return new RegExp(`^\\s*(?:${n})\\s*$`)}function y({showLineNumbers:e,metastring:t}){return"boolean"==typeof e?e?1:void 0:"number"==typeof e?e:function(e){const t=e?.split(" ").find((e=>e.startsWith("showLineNumbers")));if(t){if(t.startsWith("showLineNumbers=")){const e=t.replace("showLineNumbers=","");return parseInt(e,10)}return 1}}(t)}function A(e,t){const{language:n,magicComments:s}=t;if(void 0===n)return{lineClassNames:{},code:e};const a=function(e,t){switch(e){case"js":case"javascript":case"ts":case"typescript":return N(["js","jsBlock"],t);case"jsx":case"tsx":return N(["js","jsBlock","jsx"],t);case"html":return N(["js","jsBlock","html"],t);case"python":case"py":case"bash":return N(["bash"],t);case"markdown":case"md":return N(["html","jsx","bash"],t);case"tex":case"latex":case"matlab":return N(["tex"],t);case"lua":case"haskell":return N(["lua"],t);case"sql":return N(["lua","jsBlock"],t);case"wasm":return N(["wasm"],t);case"vb":case"vba":case"visual-basic":return N(["vb","rem"],t);case"vbnet":return N(["vbnet","rem"],t);case"batch":return N(["rem"],t);case"basic":return N(["rem","f90"],t);case"fsharp":return N(["js","ml"],t);case"ocaml":case"sml":return N(["ml"],t);case"fortran":return N(["f90"],t);case"cobol":return N(["cobol"],t);default:return N(v,t)}}(n,s),r=e.split(/\r?\n/),c=Object.fromEntries(s.map((e=>[e.className,{start:0,range:""}]))),o=Object.fromEntries(s.filter((e=>e.line)).map((({className:e,line:t})=>[t,e]))),i=Object.fromEntries(s.filter((e=>e.block)).map((({className:e,block:t})=>[t.start,e]))),l=Object.fromEntries(s.filter((e=>e.block)).map((({className:e,block:t})=>[t.end,e])));for(let u=0;uvoid 0!==e));o[t]?c[o[t]].range+=`${u},`:i[t]?c[i[t]].start=u:l[t]&&(c[l[t]].range+=`${c[l[t]].start}-${u-1},`),r.splice(u,1)}const d={};return Object.entries(c).forEach((([e,{range:t}])=>{h()(t).forEach((t=>{d[t]??=[],d[t].push(e)}))})),{code:r.join("\n"),lineClassNames:d}}function w(e,t){const n=e.replace(/\r?\n$/,"");return function(e,{metastring:t,magicComments:n}){if(t&&g.test(t)){const s=t.match(g).groups.range;if(0===n.length)throw new Error(`A highlight range has been given in code block's metastring (\`\`\` ${t}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges.`);const a=n[0].className,r=h()(s).filter((e=>e>0)).map((e=>[e-1,[a]]));return{lineClassNames:Object.fromEntries(r),code:e}}return null}(n,{...t})??A(n,{...t})}function C(e){const t=function(e){return t=e.language??function(e){if(!e)return;const t=e.split(" ").find((e=>e.startsWith("language-")));return t?.replace(/language-/,"")}(e.className)??e.defaultLanguage,t?.toLowerCase()??"text";var t}({language:e.language,defaultLanguage:e.defaultLanguage,className:e.className}),{lineClassNames:n,code:s}=w(e.code,{metastring:e.metastring,magicComments:e.magicComments,language:t}),a=function({className:e,language:t}){return(0,o.A)(e,t&&!e?.includes(`language-${t}`)&&`language-${t}`)}({className:e.className,language:t}),r=(c=e.metastring,(c?.match(x)?.groups.title??"")||e.title);var c;const i=y({showLineNumbers:e.showLineNumbers,metastring:e.metastring});return{codeInput:e.code,code:s,className:a,language:t,title:r,lineNumbersStart:i,lineClassNames:n}}const k=(0,s.createContext)(null);function B({metadata:e,wordWrap:t,children:n}){const a=(0,s.useMemo)((()=>({metadata:e,wordWrap:t})),[e,t]);return(0,p.jsx)(k.Provider,{value:a,children:n})}function L(){const e=(0,s.useContext)(k);if(null===e)throw new f.dV("CodeBlockContextProvider");return e}const T="codeBlockContainer_Ckt0";function E({as:e,...t}){const n=function(e){const t={color:"--prism-color",backgroundColor:"--prism-background-color"},n={};return Object.entries(e.plain).forEach((([e,s])=>{const a=t[e];a&&"string"==typeof s&&(n[a]=s)})),n}(d());return(0,p.jsx)(e,{...t,style:n,className:(0,o.A)(t.className,T,u.G.common.codeBlock)})}const _="codeBlock_bY9V",M="codeBlockStandalone_MEMb",U="codeBlockLines_e6Vv",S="codeBlockLinesWithNumbering_o6Pm";function z({children:e,className:t}){return(0,p.jsx)(E,{as:"pre",tabIndex:0,className:(0,o.A)(M,"thin-scrollbar",t),children:(0,p.jsx)("code",{className:U,children:e})})}const I={attributes:!0,characterData:!0,childList:!0,subtree:!0};function H(e,t){const[n,a]=(0,s.useState)(),r=(0,s.useCallback)((()=>{a(e.current?.closest("[role=tabpanel][hidden]"))}),[e,a]);(0,s.useEffect)((()=>{r()}),[r]),function(e,t,n=I){const a=(0,f._q)(t),r=(0,f.Be)(n);(0,s.useEffect)((()=>{const t=new MutationObserver(a);return e&&t.observe(e,r),()=>t.disconnect()}),[e,a,r])}(n,(e=>{e.forEach((e=>{"attributes"===e.type&&"hidden"===e.attributeName&&(t(),r())}))}),{attributes:!0,characterData:!1,childList:!1,subtree:!1})}function P({children:e}){return e}var V=n(1765);function R({line:e,token:t,...n}){return(0,p.jsx)("span",{...n})}const W="codeLine_lJS_",$="codeLineNumber_Tfdd",D="codeLineContent_feaV";function q({line:e,classNames:t,showLineNumbers:n,getLineProps:s,getTokenProps:a}){const r=function(e){const t=1===e.length&&"\n"===e[0].content?e[0]:void 0;return t?[{...t,content:""}]:e}(e),c=s({line:r,className:(0,o.A)(t,n&&W)}),i=r.map(((e,t)=>{const n=a({token:e});return(0,p.jsx)(R,{...n,line:r,token:e,children:n.children},t)}));return(0,p.jsxs)("span",{...c,children:[n?(0,p.jsxs)(p.Fragment,{children:[(0,p.jsx)("span",{className:$}),(0,p.jsx)("span",{className:D,children:i})]}):i,(0,p.jsx)("br",{})]})}const O=s.forwardRef(((e,t)=>(0,p.jsx)("pre",{ref:t,tabIndex:0,...e,className:(0,o.A)(e.className,_,"thin-scrollbar")})));function F(e){const{metadata:t}=L();return(0,p.jsx)("code",{...e,className:(0,o.A)(e.className,U,void 0!==t.lineNumbersStart&&S),style:{...e.style,counterReset:void 0===t.lineNumbersStart?void 0:"line-count "+(t.lineNumbersStart-1)}})}function G({className:e}){const{metadata:t,wordWrap:n}=L(),s=d(),{code:a,language:r,lineNumbersStart:c,lineClassNames:i}=t;return(0,p.jsx)(V.f4,{theme:s,code:a,language:r,children:({className:t,style:s,tokens:a,getLineProps:r,getTokenProps:l})=>(0,p.jsx)(O,{ref:n.codeBlockRef,className:(0,o.A)(e,t),style:s,children:(0,p.jsx)(F,{children:a.map(((e,t)=>(0,p.jsx)(q,{line:e,getLineProps:r,getTokenProps:l,classNames:i[t],showLineNumbers:void 0!==c},t)))})})})}function J({children:e,fallback:t}){return(0,c.A)()?(0,p.jsx)(p.Fragment,{children:e?.()}):t??null}var Z=n(1312);function X({className:e,...t}){return(0,p.jsx)("button",{type:"button",...t,className:(0,o.A)("clean-btn",e)})}function Y(e){return(0,p.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,p.jsx)("path",{fill:"currentColor",d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})})}function Q(e){return(0,p.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,p.jsx)("path",{fill:"currentColor",d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"})})}const K={copyButtonCopied:"copyButtonCopied_Vdqa",copyButtonIcons:"copyButtonIcons_IEyt",copyButtonIcon:"copyButtonIcon_TrPX",copyButtonSuccessIcon:"copyButtonSuccessIcon_cVMy"};function ee(e){return e?(0,Z.T)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,Z.T)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"})}function te({className:e}){const{copyCode:t,isCopied:n}=function(){const{metadata:{code:e}}=L(),[t,n]=(0,s.useState)(!1),a=(0,s.useRef)(void 0),r=(0,s.useCallback)((()=>{navigator.clipboard.writeText(e).then((()=>{n(!0),a.current=window.setTimeout((()=>{n(!1)}),1e3)}))}),[e]);return(0,s.useEffect)((()=>()=>window.clearTimeout(a.current)),[]),{copyCode:r,isCopied:t}}();return(0,p.jsx)(X,{"aria-label":ee(n),title:(0,Z.T)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,o.A)(e,K.copyButton,n&&K.copyButtonCopied),onClick:t,children:(0,p.jsxs)("span",{className:K.copyButtonIcons,"aria-hidden":"true",children:[(0,p.jsx)(Y,{className:K.copyButtonIcon}),(0,p.jsx)(Q,{className:K.copyButtonSuccessIcon})]})})}function ne(e){return(0,p.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,p.jsx)("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})})}const se="wordWrapButtonIcon_b1P5",ae="wordWrapButtonEnabled_uzNF";function re({className:e}){const{wordWrap:t}=L();if(!(t.isEnabled||t.isCodeScrollable))return!1;const n=(0,Z.T)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return(0,p.jsx)(X,{onClick:()=>t.toggle(),className:(0,o.A)(e,t.isEnabled&&ae),"aria-label":n,title:n,children:(0,p.jsx)(ne,{className:se,"aria-hidden":"true"})})}const ce="buttonGroup_M5ko";function oe({className:e}){return(0,p.jsx)(J,{children:()=>(0,p.jsxs)("div",{className:(0,o.A)(e,ce),children:[(0,p.jsx)(re,{}),(0,p.jsx)(te,{})]})})}const ie="codeBlockContent_QJqH",le="codeBlockTitle_OeMC";function de({className:e}){const{metadata:t}=L();return(0,p.jsxs)(E,{as:"div",className:(0,o.A)(e,t.className),children:[t.title&&(0,p.jsx)("div",{className:le,children:(0,p.jsx)(P,{children:t.title})}),(0,p.jsxs)("div",{className:ie,children:[(0,p.jsx)(G,{}),(0,p.jsx)(oe,{})]})]})}function ue(e){const t=function(e){const{prism:t}=(0,l.p)();return C({code:e.children,className:e.className,metastring:e.metastring,magicComments:t.magicComments,defaultLanguage:t.defaultLanguage,language:e.language,title:e.title,showLineNumbers:e.showLineNumbers})}(e),n=function(){const[e,t]=(0,s.useState)(!1),[n,a]=(0,s.useState)(!1),r=(0,s.useRef)(null),c=(0,s.useCallback)((()=>{const n=r.current.querySelector("code");e?n.removeAttribute("style"):(n.style.whiteSpace="pre-wrap",n.style.overflowWrap="anywhere"),t((e=>!e))}),[r,e]),o=(0,s.useCallback)((()=>{const{scrollWidth:e,clientWidth:t}=r.current,n=e>t||r.current.querySelector("code").hasAttribute("style");a(n)}),[r]);return H(r,o),(0,s.useEffect)((()=>{o()}),[e,o]),(0,s.useEffect)((()=>(window.addEventListener("resize",o,{passive:!0}),()=>{window.removeEventListener("resize",o)})),[o]),{codeBlockRef:r,isEnabled:e,isCodeScrollable:n,toggle:c}}();return(0,p.jsx)(B,{metadata:t,wordWrap:n,children:(0,p.jsx)(de,{})})}function me({children:e,...t}){const n=(0,c.A)(),a=function(e){return s.Children.toArray(e).some((e=>(0,s.isValidElement)(e)))?e:Array.isArray(e)?e.join(""):e}(e),r="string"==typeof a?ue:z;return(0,p.jsx)(r,{...t,children:a},String(n))}function he(e){return(0,p.jsx)("code",{...e})}var fe=n(8774),pe=n(3535);var xe=n(3427),ge=n(1422);const je="details_lb9f",be="isBrowser_bmU9",ve="collapsibleContent_i85q";function Ne(e){return!!e&&("SUMMARY"===e.tagName||Ne(e.parentElement))}function ye(e,t){return!!e&&(e===t||ye(e.parentElement,t))}function Ae({summary:e,children:t,...n}){(0,xe.A)().collectAnchor(n.id);const a=(0,c.A)(),r=(0,s.useRef)(null),{collapsed:i,setCollapsed:l}=(0,ge.u)({initialState:!n.open}),[d,u]=(0,s.useState)(n.open),m=s.isValidElement(e)?e:(0,p.jsx)("summary",{children:e??"Details"});return(0,p.jsxs)("details",{...n,ref:r,open:d,"data-collapsed":i,className:(0,o.A)(je,a&&be,n.className),onMouseDown:e=>{Ne(e.target)&&e.detail>1&&e.preventDefault()},onClick:e=>{e.stopPropagation();const t=e.target;Ne(t)&&ye(t,r.current)&&(e.preventDefault(),i?(l(!1),u(!0)):l(!0))},children:[m,(0,p.jsx)(ge.N,{lazy:!1,collapsed:i,onCollapseTransitionEnd:e=>{l(e),u(!e)},children:(0,p.jsx)("div",{className:ve,children:t})})]})}const we="details_b_Ee";function Ce({...e}){return(0,p.jsx)(Ae,{...e,className:(0,o.A)("alert alert--info",we,e.className)})}function ke(e){const t=s.Children.toArray(e.children),n=t.find((e=>s.isValidElement(e)&&"summary"===e.type)),a=(0,p.jsx)(p.Fragment,{children:t.filter((e=>e!==n))});return(0,p.jsx)(Ce,{...e,summary:n,children:a})}var Be=n(1107);function Le(e){return(0,p.jsx)(Be.A,{...e})}const Te="containsTaskList_mC6p";function Ee(e){if(void 0!==e)return(0,o.A)(e,e?.includes("contains-task-list")&&Te)}const _e="img_ev3q";var Me=n(7293),Ue=n(418);const Se={Head:r.A,details:ke,Details:ke,code:function(e){return function(e){return void 0!==e.children&&s.Children.toArray(e.children).every((e=>"string"==typeof e&&!e.includes("\n")))}(e)?(0,p.jsx)(he,{...e}):(0,p.jsx)(me,{...e})},a:function(e){const t=(0,pe.v)(e.id);return(0,p.jsx)(fe.A,{...e,className:(0,o.A)(t,e.className)})},pre:function(e){return(0,p.jsx)(p.Fragment,{children:e.children})},ul:function(e){return(0,p.jsx)("ul",{...e,className:Ee(e.className)})},li:function(e){(0,xe.A)().collectAnchor(e.id);const t=(0,pe.v)(e.id);return(0,p.jsx)("li",{className:(0,o.A)(t,e.className),...e})},img:function(e){return(0,p.jsx)("img",{decoding:"async",loading:"lazy",...e,className:(t=e.className,(0,o.A)(t,_e))});var t},h1:e=>(0,p.jsx)(Le,{as:"h1",...e}),h2:e=>(0,p.jsx)(Le,{as:"h2",...e}),h3:e=>(0,p.jsx)(Le,{as:"h3",...e}),h4:e=>(0,p.jsx)(Le,{as:"h4",...e}),h5:e=>(0,p.jsx)(Le,{as:"h5",...e}),h6:e=>(0,p.jsx)(Le,{as:"h6",...e}),admonition:Me.A,mermaid:Ue.A};function ze({children:e}){return(0,p.jsx)(a.x,{components:Se,children:e})}},4336:(e,t,n)=>{"use strict";n.d(t,{A:()=>x});n(6540);var s=n(4164),a=n(1312),r=n(7559),c=n(8774);const o={iconEdit:"iconEdit_Z9Sw"};var i=n(4848);function l({className:e,...t}){return(0,i.jsx)("svg",{fill:"currentColor",height:"20",width:"20",viewBox:"0 0 40 40",className:(0,s.A)(o.iconEdit,e),"aria-hidden":"true",...t,children:(0,i.jsx)("g",{children:(0,i.jsx)("path",{d:"m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"})})})}function d({editUrl:e}){return(0,i.jsxs)(c.A,{to:e,className:r.G.common.editThisPage,children:[(0,i.jsx)(l,{}),(0,i.jsx)(a.A,{id:"theme.common.editThisPage",description:"The link label to edit the current page",children:"Edit this page"})]})}var u=n(6266);function m({lastUpdatedAt:e}){const t=new Date(e),n=(0,u.i)({day:"numeric",month:"short",year:"numeric",timeZone:"UTC"}).format(t);return(0,i.jsx)(a.A,{id:"theme.lastUpdated.atDate",description:"The words used to describe on which date a page has been last updated",values:{date:(0,i.jsx)("b",{children:(0,i.jsx)("time",{dateTime:t.toISOString(),itemProp:"dateModified",children:n})})},children:" on {date}"})}function h({lastUpdatedBy:e}){return(0,i.jsx)(a.A,{id:"theme.lastUpdated.byUser",description:"The words used to describe by who the page has been last updated",values:{user:(0,i.jsx)("b",{children:e})},children:" by {user}"})}function f({lastUpdatedAt:e,lastUpdatedBy:t}){return(0,i.jsxs)("span",{className:r.G.common.lastUpdated,children:[(0,i.jsx)(a.A,{id:"theme.lastUpdated.lastUpdatedAtBy",description:"The sentence used to display when a page has been last updated, and by who",values:{atDate:e?(0,i.jsx)(m,{lastUpdatedAt:e}):"",byUser:t?(0,i.jsx)(h,{lastUpdatedBy:t}):""},children:"Last updated{atDate}{byUser}"}),!1]})}const p={lastUpdated:"lastUpdated_JAkA",noPrint:"noPrint_WFHX"};function x({className:e,editUrl:t,lastUpdatedAt:n,lastUpdatedBy:a}){return(0,i.jsxs)("div",{className:(0,s.A)("row",e),children:[(0,i.jsx)("div",{className:(0,s.A)("col",p.noPrint),children:t&&(0,i.jsx)(d,{editUrl:t})}),(0,i.jsx)("div",{className:(0,s.A)("col",p.lastUpdated),children:(n||a)&&(0,i.jsx)(f,{lastUpdatedAt:n,lastUpdatedBy:a})})]})}},6266:(e,t,n)=>{"use strict";n.d(t,{i:()=>a});var s=n(4586);function a(e={}){const{i18n:{currentLocale:t}}=(0,s.A)(),n=function(){const{i18n:{currentLocale:e,localeConfigs:t}}=(0,s.A)();return t[e].calendar}();return new Intl.DateTimeFormat(t,{calendar:n,...e})}},7293:(e,t,n)=>{"use strict";n.d(t,{A:()=>M});var s=n(6540),a=n(4848);function r(e){const{mdxAdmonitionTitle:t,rest:n}=function(e){const t=s.Children.toArray(e),n=t.find((e=>s.isValidElement(e)&&"mdxAdmonitionTitle"===e.type)),r=t.filter((e=>e!==n)),c=n?.props.children;return{mdxAdmonitionTitle:c,rest:r.length>0?(0,a.jsx)(a.Fragment,{children:r}):null}}(e.children),r=e.title??t;return{...e,...r&&{title:r},children:n}}var c=n(4164),o=n(1312),i=n(7559);const l="admonition_xJq3",d="admonitionHeading_Gvgb",u="admonitionIcon_Rf37",m="admonitionContent_BuS1";function h({type:e,className:t,children:n}){return(0,a.jsx)("div",{className:(0,c.A)(i.G.common.admonition,i.G.common.admonitionType(e),l,t),children:n})}function f({icon:e,title:t}){return(0,a.jsxs)("div",{className:d,children:[(0,a.jsx)("span",{className:u,children:e}),t]})}function p({children:e}){return e?(0,a.jsx)("div",{className:m,children:e}):null}function x(e){const{type:t,icon:n,title:s,children:r,className:c}=e;return(0,a.jsxs)(h,{type:t,className:c,children:[s||n?(0,a.jsx)(f,{title:s,icon:n}):null,(0,a.jsx)(p,{children:r})]})}function g(e){return(0,a.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"})})}const j={icon:(0,a.jsx)(g,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.note",description:"The default label used for the Note admonition (:::note)",children:"note"})};function b(e){return(0,a.jsx)(x,{...j,...e,className:(0,c.A)("alert alert--secondary",e.className),children:e.children})}function v(e){return(0,a.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"})})}const N={icon:(0,a.jsx)(v,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.tip",description:"The default label used for the Tip admonition (:::tip)",children:"tip"})};function y(e){return(0,a.jsx)(x,{...N,...e,className:(0,c.A)("alert alert--success",e.className),children:e.children})}function A(e){return(0,a.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"})})}const w={icon:(0,a.jsx)(A,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.info",description:"The default label used for the Info admonition (:::info)",children:"info"})};function C(e){return(0,a.jsx)(x,{...w,...e,className:(0,c.A)("alert alert--info",e.className),children:e.children})}function k(e){return(0,a.jsx)("svg",{viewBox:"0 0 16 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"})})}const B={icon:(0,a.jsx)(k,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.warning",description:"The default label used for the Warning admonition (:::warning)",children:"warning"})};function L(e){return(0,a.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,a.jsx)("path",{fillRule:"evenodd",d:"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"})})}const T={icon:(0,a.jsx)(L,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.danger",description:"The default label used for the Danger admonition (:::danger)",children:"danger"})};const E={icon:(0,a.jsx)(k,{}),title:(0,a.jsx)(o.A,{id:"theme.admonition.caution",description:"The default label used for the Caution admonition (:::caution)",children:"caution"})};const _={...{note:b,tip:y,info:C,warning:function(e){return(0,a.jsx)(x,{...B,...e,className:(0,c.A)("alert alert--warning",e.className),children:e.children})},danger:function(e){return(0,a.jsx)(x,{...T,...e,className:(0,c.A)("alert alert--danger",e.className),children:e.children})}},...{secondary:e=>(0,a.jsx)(b,{title:"secondary",...e}),important:e=>(0,a.jsx)(C,{title:"important",...e}),success:e=>(0,a.jsx)(y,{title:"success",...e}),caution:function(e){return(0,a.jsx)(x,{...E,...e,className:(0,c.A)("alert alert--warning",e.className),children:e.children})}}};function M(e){const t=r(e),n=(s=t.type,_[s]||(console.warn(`No admonition component found for admonition type "${s}". Using Info as fallback.`),_.info));var s;return(0,a.jsx)(n,{...t})}},8426:(e,t)=>{function n(e){let t,n=[];for(let s of e.split(",").map((e=>e.trim())))if(/^-?\d+$/.test(s))n.push(parseInt(s,10));else if(t=s.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)){let[e,s,a,r]=t;if(s&&r){s=parseInt(s),r=parseInt(r);const e=s{"use strict";n.d(t,{R:()=>c,x:()=>o});var s=n(6540);const a={},r=s.createContext(a);function c(e){const t=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:c(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/59362658.6a39b9be.js b/deprecated/old-docs-build/docs/assets/js/59362658.6a39b9be.js
new file mode 100644
index 0000000..418f871
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/59362658.6a39b9be.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9325],{1180:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>c,contentTitle:()=>l,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>i});var s=o(1632),n=o(4848),r=o(8453);const a={slug:"mdx-blog-post",title:"MDX Blog Post",authors:["slorber"],tags:["docusaurus"]},l=void 0,c={authorsImageUrls:[void 0]},i=[];function u(e){const t={a:"a",admonition:"admonition",code:"code",p:"p",pre:"pre",...(0,r.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)(t.p,{children:["Blog posts support ",(0,n.jsx)(t.a,{href:"https://docusaurus.io/docs/markdown-features",children:"Docusaurus Markdown features"}),", such as ",(0,n.jsx)(t.a,{href:"https://mdxjs.com/",children:"MDX"}),"."]}),"\n",(0,n.jsx)(t.admonition,{type:"tip",children:(0,n.jsx)(t.p,{children:"Use the power of React to create interactive blog posts."})}),"\n","\n",(0,n.jsx)(t.p,{children:"For example, use JSX to create an interactive button:"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-js",children:"\n"})}),"\n",(0,n.jsx)("button",{onClick:()=>alert("button clicked!"),children:"Click me!"})]})}function p(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(u,{...e})}):u(e)}},1632:e=>{e.exports=JSON.parse('{"permalink":"/docs/blog/mdx-blog-post","source":"@site/blog/2021-08-01-mdx-blog-post.mdx","title":"MDX Blog Post","description":"Blog posts support Docusaurus Markdown features, such as MDX.","date":"2021-08-01T00:00:00.000Z","tags":[{"inline":false,"label":"Docusaurus","permalink":"/docs/blog/tags/docusaurus","description":"Docusaurus tag description"}],"readingTime":0.27,"hasTruncateMarker":true,"authors":[{"name":"S\xe9bastien Lorber","title":"Docusaurus maintainer","url":"https://sebastienlorber.com","page":{"permalink":"/docs/blog/authors/all-sebastien-lorber-articles"},"socials":{"x":"https://x.com/sebastienlorber","linkedin":"https://www.linkedin.com/in/sebastienlorber/","github":"https://github.com/slorber","newsletter":"https://thisweekinreact.com"},"imageURL":"https://github.com/slorber.png","key":"slorber"}],"frontMatter":{"slug":"mdx-blog-post","title":"MDX Blog Post","authors":["slorber"],"tags":["docusaurus"]},"unlisted":false,"prevItem":{"title":"Welcome","permalink":"/docs/blog/welcome"},"nextItem":{"title":"Long Blog Post","permalink":"/docs/blog/long-blog-post"}}')},8453:(e,t,o)=>{o.d(t,{R:()=>a,x:()=>l});var s=o(6540);const n={},r=s.createContext(n);function a(e){const t=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function l(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(n):e.components||n:a(e.components),s.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/5be1457f.a7f61114.js b/deprecated/old-docs-build/docs/assets/js/5be1457f.a7f61114.js
new file mode 100644
index 0000000..1e62f2c
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/5be1457f.a7f61114.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9258],{2253:e=>{e.exports=JSON.parse('{"archive":{"blogPosts":[{"id":"welcome","metadata":{"permalink":"/docs/blog/welcome","source":"@site/blog/2021-08-26-welcome/index.md","title":"Welcome","description":"Docusaurus blogging features are powered by the blog plugin.","date":"2021-08-26T00:00:00.000Z","tags":[{"inline":false,"label":"Facebook","permalink":"/docs/blog/tags/facebook","description":"Facebook tag description"},{"inline":false,"label":"Hello","permalink":"/docs/blog/tags/hello","description":"Hello tag description"},{"inline":false,"label":"Docusaurus","permalink":"/docs/blog/tags/docusaurus","description":"Docusaurus tag description"}],"readingTime":0.56,"hasTruncateMarker":true,"authors":[{"name":"S\xe9bastien Lorber","title":"Docusaurus maintainer","url":"https://sebastienlorber.com","page":{"permalink":"/docs/blog/authors/all-sebastien-lorber-articles"},"socials":{"x":"https://x.com/sebastienlorber","linkedin":"https://www.linkedin.com/in/sebastienlorber/","github":"https://github.com/slorber","newsletter":"https://thisweekinreact.com"},"imageURL":"https://github.com/slorber.png","key":"slorber"},{"name":"Yangshun Tay","title":"Front End Engineer @ Facebook","url":"https://github.com/yangshun","page":{"permalink":"/docs/blog/authors/yangshun"},"socials":{"x":"https://x.com/yangshunz","github":"https://github.com/yangshun"},"imageURL":"https://github.com/yangshun.png","key":"yangshun"}],"frontMatter":{"slug":"welcome","title":"Welcome","authors":["slorber","yangshun"],"tags":["facebook","hello","docusaurus"]},"unlisted":false,"nextItem":{"title":"MDX Blog Post","permalink":"/docs/blog/mdx-blog-post"}},"content":"[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).\\n\\nHere are a few tips you might find useful.\\n\\n\x3c!-- truncate --\x3e\\n\\nSimply add Markdown files (or folders) to the `blog` directory.\\n\\nRegular blog authors can be added to `authors.yml`.\\n\\nThe blog post date can be extracted from filenames, such as:\\n\\n- `2019-05-30-welcome.md`\\n- `2019-05-30-welcome/index.md`\\n\\nA blog post folder can be convenient to co-locate blog post images:\\n\\n\\n\\nThe blog supports tags as well!\\n\\n**And if you don\'t want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config."},{"id":"mdx-blog-post","metadata":{"permalink":"/docs/blog/mdx-blog-post","source":"@site/blog/2021-08-01-mdx-blog-post.mdx","title":"MDX Blog Post","description":"Blog posts support Docusaurus Markdown features, such as MDX.","date":"2021-08-01T00:00:00.000Z","tags":[{"inline":false,"label":"Docusaurus","permalink":"/docs/blog/tags/docusaurus","description":"Docusaurus tag description"}],"readingTime":0.27,"hasTruncateMarker":true,"authors":[{"name":"S\xe9bastien Lorber","title":"Docusaurus maintainer","url":"https://sebastienlorber.com","page":{"permalink":"/docs/blog/authors/all-sebastien-lorber-articles"},"socials":{"x":"https://x.com/sebastienlorber","linkedin":"https://www.linkedin.com/in/sebastienlorber/","github":"https://github.com/slorber","newsletter":"https://thisweekinreact.com"},"imageURL":"https://github.com/slorber.png","key":"slorber"}],"frontMatter":{"slug":"mdx-blog-post","title":"MDX Blog Post","authors":["slorber"],"tags":["docusaurus"]},"unlisted":false,"prevItem":{"title":"Welcome","permalink":"/docs/blog/welcome"},"nextItem":{"title":"Long Blog Post","permalink":"/docs/blog/long-blog-post"}},"content":"Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).\\n\\n:::tip\\n\\nUse the power of React to create interactive blog posts.\\n\\n:::\\n\\n{/* truncate */}\\n\\nFor example, use JSX to create an interactive button:\\n\\n```js\\n\\n```\\n\\n"},{"id":"long-blog-post","metadata":{"permalink":"/docs/blog/long-blog-post","source":"@site/blog/2019-05-29-long-blog-post.md","title":"Long Blog Post","description":"This is the summary of a very long blog post,","date":"2019-05-29T00:00:00.000Z","tags":[{"inline":false,"label":"Hello","permalink":"/docs/blog/tags/hello","description":"Hello tag description"},{"inline":false,"label":"Docusaurus","permalink":"/docs/blog/tags/docusaurus","description":"Docusaurus tag description"}],"readingTime":2.04,"hasTruncateMarker":true,"authors":[{"name":"Yangshun Tay","title":"Front End Engineer @ Facebook","url":"https://github.com/yangshun","page":{"permalink":"/docs/blog/authors/yangshun"},"socials":{"x":"https://x.com/yangshunz","github":"https://github.com/yangshun"},"imageURL":"https://github.com/yangshun.png","key":"yangshun"}],"frontMatter":{"slug":"long-blog-post","title":"Long Blog Post","authors":"yangshun","tags":["hello","docusaurus"]},"unlisted":false,"prevItem":{"title":"MDX Blog Post","permalink":"/docs/blog/mdx-blog-post"},"nextItem":{"title":"First Blog Post","permalink":"/docs/blog/first-blog-post"}},"content":"This is the summary of a very long blog post,\\n\\nUse a `\x3c!--` `truncate` `--\x3e` comment to limit blog post size in the list view.\\n\\n\x3c!-- truncate --\x3e\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet\\n\\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet"},{"id":"first-blog-post","metadata":{"permalink":"/docs/blog/first-blog-post","source":"@site/blog/2019-05-28-first-blog-post.md","title":"First Blog Post","description":"Lorem ipsum dolor sit amet...","date":"2019-05-28T00:00:00.000Z","tags":[{"inline":false,"label":"Hola","permalink":"/docs/blog/tags/hola","description":"Hola tag description"},{"inline":false,"label":"Docusaurus","permalink":"/docs/blog/tags/docusaurus","description":"Docusaurus tag description"}],"readingTime":0.13,"hasTruncateMarker":true,"authors":[{"name":"S\xe9bastien Lorber","title":"Docusaurus maintainer","url":"https://sebastienlorber.com","page":{"permalink":"/docs/blog/authors/all-sebastien-lorber-articles"},"socials":{"x":"https://x.com/sebastienlorber","linkedin":"https://www.linkedin.com/in/sebastienlorber/","github":"https://github.com/slorber","newsletter":"https://thisweekinreact.com"},"imageURL":"https://github.com/slorber.png","key":"slorber"},{"name":"Yangshun Tay","title":"Front End Engineer @ Facebook","url":"https://github.com/yangshun","page":{"permalink":"/docs/blog/authors/yangshun"},"socials":{"x":"https://x.com/yangshunz","github":"https://github.com/yangshun"},"imageURL":"https://github.com/yangshun.png","key":"yangshun"}],"frontMatter":{"slug":"first-blog-post","title":"First Blog Post","authors":["slorber","yangshun"],"tags":["hola","docusaurus"]},"unlisted":false,"prevItem":{"title":"Long Blog Post","permalink":"/docs/blog/long-blog-post"}},"content":"Lorem ipsum dolor sit amet...\\n\\n\x3c!-- truncate --\x3e\\n\\n...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet"}]}}')}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/5e95c892.1bccf5cd.js b/deprecated/old-docs-build/docs/assets/js/5e95c892.1bccf5cd.js
new file mode 100644
index 0000000..5839269
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/5e95c892.1bccf5cd.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[9647],{7121:(e,s,r)=>{r.r(s),r.d(s,{default:()=>l});r(6540);var t=r(4164),u=r(5500),a=r(7559),c=r(2831),n=r(1656),i=r(4848);function l(e){return(0,i.jsx)(u.e3,{className:(0,t.A)(a.G.wrapper.docsPages),children:(0,i.jsx)(n.A,{children:(0,c.v)(e.route.routes)})})}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/606c302d.f44aad66.js b/deprecated/old-docs-build/docs/assets/js/606c302d.f44aad66.js
new file mode 100644
index 0000000..5890133
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/606c302d.f44aad66.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[3011],{9374:e=>{e.exports=JSON.parse('{"tag":{"label":"Hello","permalink":"/docs/blog/tags/hello","description":"Hello tag description","allTagsPath":"/docs/blog/tags","count":2,"unlisted":false},"listMetadata":{"permalink":"/docs/blog/tags/hello","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}}')}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/6080e7e2.8a2561ec.js b/deprecated/old-docs-build/docs/assets/js/6080e7e2.8a2561ec.js
new file mode 100644
index 0000000..e2988a2
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/6080e7e2.8a2561ec.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[900],{70:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>a,contentTitle:()=>d,default:()=>l,frontMatter:()=>c,metadata:()=>o,toc:()=>i});const o=JSON.parse('{"id":"backend/node/README","title":"NodeJS","description":"","source":"@site/docs/backend/node/README.md","sourceDirName":"backend/node","slug":"/backend/node/","permalink":"/docs/docs/backend/node/","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/backend/node/README.md","tags":[],"version":"current","sidebarPosition":0,"frontMatter":{"sidebar_position":0},"sidebar":"tutorialSidebar","previous":{"title":"Desarrollo Web","permalink":"/docs/docs/backend/"},"next":{"title":"Tutoriales","permalink":"/docs/docs/backend/node/tutorials/"}}');var s=n(4848),r=n(8453);const c={sidebar_position:0},d="NodeJS",a={},i=[];function u(e){const t={h1:"h1",header:"header",...(0,r.R)(),...e.components};return(0,s.jsx)(t.header,{children:(0,s.jsx)(t.h1,{id:"nodejs",children:"NodeJS"})})}function l(e={}){const{wrapper:t}={...(0,r.R)(),...e.components};return t?(0,s.jsx)(t,{...e,children:(0,s.jsx)(u,{...e})}):u(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>c,x:()=>d});var o=n(6540);const s={},r=o.createContext(s);function c(e){const t=o.useContext(r);return o.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function d(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:c(e.components),o.createElement(r.Provider,{value:t},e.children)}}}]);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/assets/js/6101f326.53d97e24.js b/deprecated/old-docs-build/docs/assets/js/6101f326.53d97e24.js
new file mode 100644
index 0000000..bff72da
--- /dev/null
+++ b/deprecated/old-docs-build/docs/assets/js/6101f326.53d97e24.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkmy_website=self.webpackChunkmy_website||[]).push([[7820],{308:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/files/7TypeConversions-d80142a78a3fe3d16434d6ea86d44b76.zip"},353:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/files/5DataTypes-ca67cdf95a284dcd80dfa572a2167cdc.zip"},397:(e,n,a)=>{a.d(n,{A:()=>s});const s=a.p+"assets/files/10Conditionals-3e24c68dfe8243c740742b827d334632.zip"},907:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>u,frontMatter:()=>l,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"backend/node/tutorials/intro_web/Lab4JS/README","title":"Intro a Javascript","description":"Para este laboratorio tendr\xe1s aparte los archivos HTML y JS correspondientes para poder verlos desde tu navegador y podr\xe1s ver cada concepto dentro de este documento.","source":"@site/docs/backend/node/tutorials/intro_web/Lab4JS/README.md","sourceDirName":"backend/node/tutorials/intro_web/Lab4JS","slug":"/backend/node/tutorials/intro_web/Lab4JS/","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab4JS/","draft":false,"unlisted":false,"editUrl":"https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/docs/backend/node/tutorials/intro_web/Lab4JS/README.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"sidebar_position":4},"sidebar":"tutorialSidebar","previous":{"title":"Intro a CSS","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab3CSS/"},"next":{"title":"Frameworks de estilo","permalink":"/docs/docs/backend/node/tutorials/intro_web/Lab5StyleFramework/"}}');var r=a(4848),o=a(8453);const l={sidebar_position:4},i="Intro a Javascript",c={},d=[{value:"Inline Scripting",id:"inline-scripting",level:2},{value:"Estructura de C\xf3digo",id:"estructura-de-c\xf3digo",level:2},{value:"Declaraciones",id:"declaraciones",level:3},{value:"Punto y coma",id:"punto-y-coma",level:3},{value:"Comentarios",id:"comentarios",level:3},{value:"UseStrict",id:"usestrict",level:2},{value:"Variables",id:"variables",level:2},{value:"Uso de let",id:"uso-de-let",level:3},{value:"Var en lugar de let",id:"var-en-lugar-de-let",level:3},{value:"Declaraciones dobles",id:"declaraciones-dobles",level:3},{value:"Nombrado de variables",id:"nombrado-de-variables",level:3},{value:"Constantes",id:"constantes",level:3},{value:"Constantes en may\xfasculas",id:"constantes-en-may\xfasculas",level:4},{value:"\xbfReusar o crear variables?",id:"reusar-o-crear-variables",level:3},{value:"Tipos de datos",id:"tipos-de-datos",level:2},{value:"Number",id:"number",level:3},{value:"BigInt",id:"bigint",level:3},{value:"String",id:"string",level:3},{value:"Boolean",id:"boolean",level:3},{value:"Null",id:"null",level:3},{value:"Undefined",id:"undefined",level:3},{value:"Objetos y s\xedmbolos",id:"objetos-y-s\xedmbolos",level:3},{value:"El operador typeof",id:"el-operador-typeof",level:3},{value:"Interacci\xf3n: Alert, prompt, confirm",id:"interacci\xf3n-alert-prompt-confirm",level:2},{value:"Alert",id:"alert",level:3},{value:"Prompt",id:"prompt",level:3},{value:"Confirm",id:"confirm",level:3},{value:"Conversiones de Tipos",id:"conversiones-de-tipos",level:2},{value:"Conversi\xf3n de Strings",id:"conversi\xf3n-de-strings",level:3},{value:"Conversi\xf3n Num\xe9rica",id:"conversi\xf3n-num\xe9rica",level:3},{value:"Conversi\xf3n Booleana",id:"conversi\xf3n-booleana",level:3},{value:"Operadores B\xe1sicos Matem\xe1ticos",id:"operadores-b\xe1sicos-matem\xe1ticos",level:2},{value:"Operaciones Matem\xe1ticas",id:"operaciones-matem\xe1ticas",level:3},{value:"Resto %",id:"resto-",level:3},{value:"Exponenciaci\xf3n **",id:"exponenciaci\xf3n-",level:3},{value:"Concatenaci\xf3n de string binaria con +",id:"concatenaci\xf3n-de-string-binaria-con-",level:3},{value:"Conversi\xf3n num\xe9rica, unaria +",id:"conversi\xf3n-num\xe9rica-unaria-",level:3},{value:"Encadenar asignaciones",id:"encadenar-asignaciones",level:3},{value:"Modificar al momento",id:"modificar-al-momento",level:3},{value:"Incrementar",id:"incrementar",level:3},{value:"Decrementar",id:"decrementar",level:3},{value:"Operadores Bitwise",id:"operadores-bitwise",level:3},{value:"Comparaciones",id:"comparaciones",level:2},{value:"Igualdad estricta",id:"igualdad-estricta",level:3},{value:"Comparaci\xf3n entre null y undefined",id:"comparaci\xf3n-entre-null-y-undefined",level:3},{value:"Null vs 0",id:"null-vs-0",level:3},{value:"No comparar undefined",id:"no-comparar-undefined",level:3},{value:"Condicionales",id:"condicionales",level:2},{value:"Declaraciones IF",id:"declaraciones-if",level:3},{value:"Conversi\xf3n booleana",id:"conversi\xf3n-booleana-1",level:3},{value:"El ELSE",id:"el-else",level:3},{value:"Operador condicional ?",id:"operador-condicional-",level:3},{value:"M\xfaltiple ?",id:"m\xfaltiple-",level:4},{value:"La declaraci\xf3n switch",id:"la-declaraci\xf3n-switch",level:3},{value:"Agrupando case",id:"agrupando-case",level:4},{value:"Ciclos",id:"ciclos",level:2},{value:"Ciclo while",id:"ciclo-while",level:3},{value:"Ciclo Do..while",id:"ciclo-dowhile",level:3},{value:"Ciclo for",id:"ciclo-for",level:3},{value:"Break o rompiendo el ciclo",id:"break-o-rompiendo-el-ciclo",level:3},{value:"Continuar la siguiente iteraci\xf3n",id:"continuar-la-siguiente-iteraci\xf3n",level:3},{value:"Funciones",id:"funciones",level:2},{value:"Declaraci\xf3n de funciones",id:"declaraci\xf3n-de-funciones",level:3},{value:"Variables locales",id:"variables-locales",level:3},{value:"Variables externas",id:"variables-externas",level:3},{value:"Par\xe1metros",id:"par\xe1metros",level:3},{value:"Valores default",id:"valores-default",level:3},{value:"Regresando valores",id:"regresando-valores",level:3},{value:"Nombrado de funciones",id:"nombrado-de-funciones",level:3},{value:"Expresiones con funciones",id:"expresiones-con-funciones",level:3},{value:"Las funciones son un valor",id:"las-funciones-son-un-valor",level:3},{value:"Funciones Callback",id:"funciones-callback",level:3},{value:"Funciones flecha",id:"funciones-flecha",level:3},{value:"Debugging en el navegador",id:"debugging-en-el-navegador",level:2},{value:"Arreglos y Objetos",id:"arreglos-y-objetos",level:2},{value:"Arreglos",id:"arreglos",level:3},{value:"Iteraciones sobre arreglos",id:"iteraciones-sobre-arreglos",level:3},{value:"Objetos",id:"objetos",level:3}];function t(e){const n={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"intro-a-javascript",children:"Intro a Javascript"})}),"\n",(0,r.jsx)(n.p,{children:"Para este laboratorio tendr\xe1s aparte los archivos HTML y JS correspondientes para poder verlos desde tu navegador y podr\xe1s ver cada concepto dentro de este documento."}),"\n",(0,r.jsx)(n.p,{children:"Javascript es el lenguaje de programaci\xf3n por excelencia para usarse dentro del navegador, cuando hablamos de desarrollo web, tenemos que el c\xf3digo HTML, le da estructura a nuestra p\xe1gina, el CSS le da el estilo y JS nos permite dotar de funcionalidad a nuestro sitio."}),"\n",(0,r.jsx)(n.p,{children:"Visto de otra forma, JS nos va a ayudar a que en la arquitectura cliente-servidor, sea el cliente el que tenga mayor capacidad de solicitar al servidor de informaci\xf3n ya sea a trav\xe9s de la petici\xf3n de la misma o el guardado."}),"\n",(0,r.jsx)(n.p,{children:"Comenzaremos desde la base de como declarar el javascript para poder trabajar con \xe9l, hasta los conceptos generales que tiene el lenguaje de programaci\xf3n."}),"\n",(0,r.jsxs)(n.blockquote,{children:["\n",(0,r.jsx)(n.p,{children:"Nota: este curso no est\xe1 orientado a ser una clase de programaci\xf3n de javascript, solamente se toman las nociones b\xe1sicas para poder empezar a construir sitios web, se te pide que poco a poco entiendas el lenguaje con pr\xe1ctica e investigando por tu cuenta las peculiaridades y especializaciones del lenguaje."}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"inline-scripting",children:"Inline Scripting"}),"\n",(0,r.jsx)(n.p,{children:"Cuando hablamos de empezar a trabajar con Javascript vamos a tener varias formas de hacerlo."}),"\n",(0,r.jsx)(n.p,{children:"La primera de ellas es a trav\xe9s del inline scripting o dentro del mismo archivo HTML donde estemos trabajando. Esta forma nos permite visualizar el c\xf3digo en cualquier parte del archivo."}),"\n",(0,r.jsx)(n.p,{children:"Por lo general aunque se puede realizar la buena pr\xe1ctica nos dice que debemos separar en archivos diferentes la funcionalidad, pero eso lo veremos poco a poco."}),"\n",(0,r.jsx)(n.p,{children:"Los programas de Javascript pueden ser insertados en cualquier parte del HTML usando la etiqueta"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/blog/atom.xml b/deprecated/old-docs-build/docs/blog/atom.xml
new file mode 100644
index 0000000..55eae70
--- /dev/null
+++ b/deprecated/old-docs-build/docs/blog/atom.xml
@@ -0,0 +1,109 @@
+
+
+ https://tc2005b.github.io/docs/blog
+ TC2005B Blog
+ 2021-08-26T00:00:00.000Z
+ https://github.com/jpmonette/feed
+
+ TC2005B Blog
+ https://tc2005b.github.io/docs/img/favicon.ico
+
+
+ https://tc2005b.github.io/docs/blog/welcome
+
+ 2021-08-26T00:00:00.000Z
+
+ Docusaurus blogging features are powered by the blog plugin.
+
Here are a few tips you might find useful.
+
Simply add Markdown files (or folders) to the blog directory.
+
Regular blog authors can be added to authors.yml.
+
The blog post date can be extracted from filenames, such as:
+
+
2019-05-30-welcome.md
+
2019-05-30-welcome/index.md
+
+
A blog post folder can be convenient to co-locate blog post images:
+
+
The blog supports tags as well!
+
And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.
]]>
+
+ Sébastien Lorber
+ https://sebastienlorber.com
+
+
+ Yangshun Tay
+ https://github.com/yangshun
+
+
+
+
+
+
+
+ https://tc2005b.github.io/docs/blog/mdx-blog-post
+
+ 2021-08-01T00:00:00.000Z
+
+ Blog posts support Docusaurus Markdown features, such as MDX.
+
tip
Use the power of React to create interactive blog posts.
+
+
For example, use JSX to create an interactive button:
+]]>
+
+ Sébastien Lorber
+ https://sebastienlorber.com
+
+
+
+
+
+ https://tc2005b.github.io/docs/blog/long-blog-post
+
+ 2019-05-29T00:00:00.000Z
+
+ This is the summary of a very long blog post,
+
Use a <!--truncate--> comment to limit blog post size in the list view.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/blog/index.html b/deprecated/old-docs-build/docs/blog/index.html
new file mode 100644
index 0000000..9bbc2ee
--- /dev/null
+++ b/deprecated/old-docs-build/docs/blog/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+Blog | TC2005B
+
+
+
+
+
+
+
Use a <!--truncate--> comment to limit blog post size in the list view.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/blog/mdx-blog-post/index.html b/deprecated/old-docs-build/docs/blog/mdx-blog-post/index.html
new file mode 100644
index 0000000..1d7ef30
--- /dev/null
+++ b/deprecated/old-docs-build/docs/blog/mdx-blog-post/index.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+MDX Blog Post | TC2005B
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/blog/rss.xml b/deprecated/old-docs-build/docs/blog/rss.xml
new file mode 100644
index 0000000..53c156a
--- /dev/null
+++ b/deprecated/old-docs-build/docs/blog/rss.xml
@@ -0,0 +1,87 @@
+
+
+
+ TC2005B Blog
+ https://tc2005b.github.io/docs/blog
+ TC2005B Blog
+ Thu, 26 Aug 2021 00:00:00 GMT
+ https://validator.w3.org/feed/docs/rss2.html
+ https://github.com/jpmonette/feed
+ es
+
+
+ https://tc2005b.github.io/docs/blog/welcome
+ https://tc2005b.github.io/docs/blog/welcome
+ Thu, 26 Aug 2021 00:00:00 GMT
+
+ Docusaurus blogging features are powered by the blog plugin.
+
Here are a few tips you might find useful.
+
Simply add Markdown files (or folders) to the blog directory.
+
Regular blog authors can be added to authors.yml.
+
The blog post date can be extracted from filenames, such as:
+
+
2019-05-30-welcome.md
+
2019-05-30-welcome/index.md
+
+
A blog post folder can be convenient to co-locate blog post images:
+
+
The blog supports tags as well!
+
And if you don't want a blog: just delete this directory, and use blog: false in your Docusaurus config.
]]>
+ Facebook
+ Hello
+ Docusaurus
+
+
+
+ https://tc2005b.github.io/docs/blog/mdx-blog-post
+ https://tc2005b.github.io/docs/blog/mdx-blog-post
+ Sun, 01 Aug 2021 00:00:00 GMT
+
+ Blog posts support Docusaurus Markdown features, such as MDX.
+
tip
Use the power of React to create interactive blog posts.
+
+
For example, use JSX to create an interactive button:
+]]>
+ Docusaurus
+
+
+
+ https://tc2005b.github.io/docs/blog/long-blog-post
+ https://tc2005b.github.io/docs/blog/long-blog-post
+ Wed, 29 May 2019 00:00:00 GMT
+
+ This is the summary of a very long blog post,
+
Use a <!--truncate--> comment to limit blog post size in the list view.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
]]>
+ Hello
+ Docusaurus
+
+
+
+ https://tc2005b.github.io/docs/blog/first-blog-post
+ https://tc2005b.github.io/docs/blog/first-blog-post
+ Tue, 28 May 2019 00:00:00 GMT
+
+ Lorem ipsum dolor sit amet...
+
...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
]]>
+ Hola
+ Docusaurus
+
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/blog/tags/docusaurus/index.html b/deprecated/old-docs-build/docs/blog/tags/docusaurus/index.html
new file mode 100644
index 0000000..ca097ba
--- /dev/null
+++ b/deprecated/old-docs-build/docs/blog/tags/docusaurus/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+4 publicaciones etiquetados con "Docusaurus" | TC2005B
+
+
+
+
+
+
+
Los laboratorios son extensiones de lo que vemos en clase, siempre revisa el material ya que puede haber detalles que por tiempo no alcancemos a ver y que es tu responsabilidad entender para no retrasarte en las bases de lo que tienes que aprender del curso.
Con lo que hemos trabajado en nuestro servidor hasta el momento, hemos cargado rutas, hemos trabajado con HTML dinámico y ya podemos crear sitios web, pero nos faltan 2 puntos elementales, conectar datos e información y procesarlas para cargarlo en nuestro HTML.
+
Con lo que hemos visto hasta ahora en el HTML dinámico, podemos definir lo que llamamos como UI en vistas que es toda la interfaz de HTML, CSS y JS para poder verlo en el navegador.
+
También hemos experimentado un poco en las rutas cuando no regresamos directamente un HTML y regresamos por ejemplo un JSON de información.
+
Igualmente ya vimos que dentro de EJS, aunque cargamos el EJS como HTML, podemos pasar un JSON para agregar información desde nuestra ruta.
+
El punto que empezaremos a ver ahora es uno de los más importantes desde el punto de ingeniería de software, la arquitectura del proyecto.
+
Cuando hablamos de la arquitectura nos estamos refiriendo a la forma en la que las carpetas del proyecto están estructuradas.
+
Como ya habrás notado un proyecto grande de desarrollo, incluye archivos HTML, CSS, JS para el cliente, archivos js de back-end, de front-end y sobre ellos viene la lógica de crear módulos en nuestra aplicación según las funciones que tiene.
+
Cuando ya tenemos una gran cantidad de archivos, esto se vuelve complicado si no planificamos como estructurar nuestros archivos, y es por ello que viene la primer aproximación en una de las arquitecturas más comunes utilizadas en desarrollo web.
+
Hasta ahora cuando hablábamos de arquitectura nos referíamos a cliente-servidor. Pero esta arquitecturas nos habla de como se establece el protocolo de comunicación del sistema.
+
Ahora la arquitectura de software como ya mencioné habla de la estructura de carpetas del proyecto, si bien existen una gran cantidad de arquitecturas, todas con un propósitos específico, en desarrollo de back-end la más común y simple es MVC o mejor conocida como Modelo-Vista-Controlador.
+
+
Un diagrama que explica de manera sencilla como funciona la arquitectura es el siguiente:
+
Tendremos una petición HTTP, hasta ahora la hemos resuelto directamente en una función de Javascript, pero dentro del servidor lo ideal es separar según la fuente, esto hace más legible el código, estructura mejor la carpeta de archivos y permite probar lo que estamos haciendo.
+
Al llegar el request HTTP, llegamos a la ruta o lo que ahora conoceremos como el controlador, dentro de este lo más común será tener que llamar a algún elemento de información, normalmente una base de datos, lo que sucede en la arquitectura es pasar esta responsabilidad a otro archivo el cual llamaremos el modelo que su única función es obtener y manejar la información, por último se regresará al controlador y este hará el pre renderizado del HTML con EJS como en nuestro caso y devolverá la petición al cliente habiendo cargado en el código HTML la información que se ocupa de la base de datos.
+
Todo este camino se realizará para cada ruta de cada función de cada módulo del sistema, hacerlo de manera efectiva nos ayudará a que existan partes del código replicadas y simplificará la llamada de funciones, ya que no necesitamos crear un query a la base de datos para obtener lo usuarios en cada ruta, sino que esto lo delegaremos a un modelo que hará esta llamada y se insertará cada vez que lo utilicemos en un controlador.
+
Algo adicional que podemos o no añadir en NodeJS y Express, es un archivo de rutas, esto hace más fácil ver que rutas hay disponibles en nuestro proyecto.
+
Veamos un ejemplo sencillo abarcando toda la arquitectura.
Vamos a crear un nuevo proyecto base con npm init, aquí vamos a agregar express, body-parser y ejs. También vamos a configurar la carpeta pública como ya hicimos en el laboratorio anterior. Por facilidad en la carpeta pública añadiremos un archivo script.js con un alert como en el laboratorio anterior.
+
El contenido de index.js quedaría como el siguiente:
Si guardas el archivo index.js verás muchos errores y es por que aunque ya existe usuarios.routes.js aún no estamos soportando express para poderlo llamar. Para ello agrega lo siguiente en usuarios.routes.js:
Aquí estaremos sirviendo una ruta /obtener_usuarios que de momento solo incluye una función flecha vacía.
+
En laboratorios anteriores servimos la ruta desde aquí, pero ahora vamos a incorporar el controlador, para ello vamos a crear una carpeta llamada controllers, y a esta vamos a añadirle un archivo usuarios.controller.js.
+
Dentro de usuarios.routes.js vamos a añadir el controlador debajo de la definición de route:
Por último en usuarios.controller.js vamos colocar lo siguiente:
+
module.exports.index = async(req,res) =>{ res.status(200).send({status:"success",message:"Get all users"}) }
+
Aquí definimos la función index y de momento solo regresamos un json de respuesta.
+
Es importante que entiendas todo el camino hasta el momento pues ello es la base para lo que viene.
+
Si entras a la url en el navegador
+
http://localhost:3000/usuarios/obtener_usuarios
+
El resultado será el siguiente:
+
+
Pero internamente lo que sucede es lo siguiente:
+
+
Llega un request a index.js a la url /usuarios /obtener_usuarios
+
Index.js encuentra /usuarios definido por lo que lo pasa al archivo usuarios.routes.js.
+
Usuarios detecta la url /obtener_usuarios y detecta que se llama de una función index en usuarios.controller.js.
+
Desde usuarios.controller.js se llama y ejecuta la respuesta del request.
+
+
Con lo anterior a nivel arquitectura hemos segmentado en carpetas nuestras rutas y controladores, por lo que ahora en rutas vamos a tener una lista de urls con funciones que se llaman del controlador.
+
El controlador es el encargado de hacer el renderizado y servir de cerebro para la función solicitada. Aquí lo que nos falta es conectar con un modelo y cargar el EJS de la vista para completar la arquitectura.
Como ya mencionamos anteriormente, los modelos son la parte de conexión con los datos de información. No confundir con que es un medio exclusivo para conectar con la base de datos, ya que dentro de un sistema podemos tener diferentes fuentes de información como: archivos, fuentes de datos, conexiones con otros sistemas y sí las bases de datos.
+
Los modelos son parte de nuestra arquitectura por lo que es necesario definirlos en una carpeta models de nuestra aplicación. Para ello vamos a crear la carpeta y dentro un archivo que llamaremos usuarios.model.js.
+
Al igual que la ruta al controlador, es necesario conectar el controlador al modelo. Por lo que en el controlador usuarios.controller.js debemos añadir al inicio del archivo lo siguiente:
+
const model = require("../models/usuarios.model.js")
+
Ahora para el contenido de usuarios.model.js tendremos lo siguiente:
Aquí estamos creando una función ObtenerUsuarios, que conectaría con nuestra fuente de información y regresaría los datos.
+
Por último dentro de usuarios.controller.js en la función index vamos a llamar al modelo de la siguiente manera:
+
const model = require("../models/usuarios.model.js") module.exports.index = async(req,res) =>{ model.ObtenerUsuarios() res.status(200).send({status:"success",message:"Get all users"}) }
+
Si entramos a la url
+
http://localhost:3000/usuarios/obtener_usuarios
+
Vamos a nuestra terminal y observamos que el log se realiza correctamente:
+
+
Ahora, dependerá de nuestro motor de base de datos o fuente de información, pero las buenas prácticas nos piden crear objetos para almacenar la información de nuestros modelos. Es decir crear objetos aunque sea en formato JSON que guarden la estructura de nuestros datos para poderlo manipular.
+
Idealmente estos se separan en archivos adicionales, pero por facilidad vamos a declararlos en el mismo archivo de modelo simulando una llamada a una fuente de datos.
Como vimos en el laboratorio anterior, debemos hacer uso de un HTML dinámico para definir nuestra vista y cargar los datos de nuestro modelo y controlador.
+
Dentro de nuestro archivo index.js hemos definido el uso de ejs, pero en la estructura del proyecto no hemos agregado la carpeta. Por lo que vamos a crear views y dentro de la misma una carpeta que se llame usuarios para que desde adentro tengamos un archivo obtener_usuarios.ejs.,
También vamos a crear los archivos css y scripts, pero estos los colocaremos fuera de la carpeta de usuarios. De momento no añadiremos nada a estos archivos pero esta es una estructura que te recomiendo para que todo tu módulo de usuarios contenga al menos los mismos css y scripts del proyecto.
+
El resultado de la estructura se vería de la siguiente manera:
+
+
Si vamos a nuestro archivo usuarios.controller.js vamos a renderizar nuestra vista sustituyendo el send que teníamos por lo siguiente:
+
const model = require("../models/usuarios.model.js")
Observa como a diferencia del laboratorio anterior, usamos la estructura de archivos para cargar nuestra carpeta de usuarios y luego nuestro ejs para modularizar nuestra funcionalidad.
+
Por último vamos a cargar nuestros usuarios en una etiqueta ul, primero vamos a pasarlos desde el resultado de nuestro modelo a la vista usando el parámetro de json que recibe el método render.
Aquí haremos uso de la estructura que definimos en nuestro modelo. Y la cargaremos dentro de nuestro código.
+
El resultado final en el navegador lo veremos de la siguiente manera:
+
+
Y con esto hemos conectado la arquitectura completa de nuestro proyecto, tenemos el modelo para cargar información, el controlador para manipular la transacción y la vista para visualizarlo, además de la ruta para ver el tipo de url que estamos llamando.
+
Entiendo que de inicio parecen muchos archivos, pero esta buena práctica hará que tu código sea más simple en proyectos grandes pues la misma forma de repetir las cosas una y otra vez hace que el código sea lo más igual posible.
Dentro del mundo del desarrollo web, seguramente has de haber escuchado el concepto de cookie. Hoy en día, las cookies son muy importantes ya que dentro de la mayoría de los sitios web tenemos modales que nos preguntan si las aceptamos o no.
+
Las cookies tienen muchas funciones, pero entre las más importantes es mantener nuestras sesiones en el navegador. Sin ellas, es lo mismo que cuando apagamos el javascript dentro del navegador. Esto aunque protegería nuestra privacidad en internet, nos limitaría en la cantidad de cosas que podemos definir para un proyecto.
+
De manera simple, las cookies son archivos de texto con pequeños datos, que se utilizan para identificar un ordenador cuando estás en internet.
+
Los datos generados dependen del sitio web, pero por lo general van acompañados con un ID exclusivo y la información específica que representan.
+
Debido a las leyes internacionales, como el Reglamento General de Protección de Datos (RGPD) de la UE, y a ciertas leyes estatales, como la Ley de Privacidad del Consumidor de California (CCPA), muchos sitios web ahora deben solicitar permiso para usar ciertas cookies con tu navegador y proporcionar información acerca de cómo se utilizarán las cookies si aceptas.
En general, todas las cookies funcionan de la misma manera, pero se han aplicado a diferentes casos de uso:
+
Cookies mágicas es una vieja expresión informática que se refiere a paquetes de información que se envían y reciben sin cambios en los datos. Estas se utilizarían normalmente para iniciar sesión en sistemas informáticos de bases de datos, como la red interna de una empresa. Este concepto es anterior al de "cookie" que usamos hoy.
+
Las cookies HTTP son una versión reutilizada de la "cookie mágica" creada para la navegación por Internet actual. En 1994, Lou Montulli, programador de navegadores web, se inspiró en la "cookie mágica" para crear la cookie HTTP, mientras ayudaba a una tienda de compras en línea a arreglar sus servidores sobrecargados. La cookie HTTP es lo que actualmente denominamos cookie de forma más general. También es lo que algunos ciber delincuentes pueden utilizar para espiar tu actividad en línea y piratear información personal.
+
Dentro de Node y express podemos hace uso de las cookies para mantener y revisar una sesión de usuario.
+
Para comenzar vamos a definir un nuevo proyecto como siempre lo hacemos, definiendo el npm init instalando express, el body-parser y el ejs por el momento. También crearemos un archivo index.js con la configuración básica de nuestro servidor.
Aquí estamos definiendo una cookie muy sencilla que al momento de llamarse / se creará y almacenará en nuestro navegador.
+
Para verla desde el navegador nos iremos a inspeccionar elemento y haremos uso de una nueva opción del navegador, la de Aplicación
+
+
Aquí vamos a tener la opción de las cookies de nuestro sitio y adentro, la lista completa de cookies generadas ya sea de forma manual o automática por el mismo. Nota como nuestra cookie que acabamos de crear se encuentra disponible.
+
Ahora tenemos la capacidad de crear cookies, pero nuestro servidor aún no es capaz de recuperarlas, para ello debemos hacer uso de una nueva librería cookie-parser, por lo que vamos a ejecutar el siguiente comando:
+
npm i cookie-parser
+
Como siempre debemos configurar la librería para nuestro uso en express
Si volvemos a revisar en el navegador veremos que tenemos la cookie repetida pero ahora tendremos la que contiene un valor.
+
+
Aquí es donde debemos tener cuidado sobretodo al estar probando ya que podemos duplicar nuestras cookies y tener conflictos de sesiones simplemente por no limpiarlas. De momento el cambio no debería afectarnos.
+
Ahora vamos a crear una nueva url que se llame /test_cookie y vamos que contenga lo siguiente:
La diferencia será que ahora se marca la casilla de HTTPOnly:
+
+
El atributo HttpOnly se añade a las cookies de seguridad (cookies LTPA y WASReqURL) que ha creado el servidor.
+
El atributo HttpOnly es un atributo de navegador creado para impedir que las aplicaciones del lado del cliente accedan a cookies para evitar algunas vulnerabilidades de scripts entre sitios. Este atributo se puede configurar ahora en la consola administrativa.
+
De manera simple esto bloquea que archivos de javascript del lado del cliente puedan acceder a los valores de la cookie por seguridad. Imagina que tienes información importante de un usuario, sin esta propiedad la cookie sería accesible desde cualquier script, por tanto un virus en el navegador dejaría la información accesible para los atacantes.
Ya hemos manejado lo básico de cookies dentro de nuestro servidor y del lado del cliente, ahora vamos con algo un poco más elaborado que es el manejo de la sesión de usuario.
+
Para ello usaremos otra librería llamada express-session, para ello ejecuta:
+
npm i express-session
+
Y como siempre vamos a iniciar su valor en el archivo index.js
+
const session = require('express-session'); app.use(session({ secret: 'mi string secreto que debe ser un string aleatorio muy largo, no como éste', resave: false, //La sesión no se guardará en cada petición, sino sólo se guardará si algo cambió saveUninitialized: false, //Asegura que no se guarde una sesión para una petición que no lo necesita }));
+
Nuestro código completo del index.js debe verse de la siguiente manera:
+
const http = require('http'); const express = require('express'); const path = require('path'); const fs = require('fs'); const app = express(); const session = require('express-session'); app.use(session({ secret: 'mi string secreto que debe ser un string aleatorio muy largo, no como éste', resave: false, //La sesión no se guardará en cada petición, sino sólo se guardará si algo cambió saveUninitialized: false, //Asegura que no se guarde una sesión para una petición que no lo necesita })); app.set('view engine', 'ejs'); app.set('views', 'views'); const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({extended: false})); app.use(express.static(path.join(__dirname, 'public'))); const cookieParser = require('cookie-parser'); app.use(cookieParser()); app.get('/', (request, response, next) => { response.setHeader('Content-Type', 'text/plain'); response.setHeader('Set-Cookie', 'mi_cookie=123; HttpOnly'); response.send("Hola Mundo"); response.end(); }); app.get('/test_cookie', (request, response, next) => { response.setHeader('Content-Type', 'text/plain'); response.send(request.cookies.mi_cookie); response.end(); }); const server = http.createServer( (request, response) => { console.log(request.url); }); app.listen(3000);
+
Con lo anterior no estamos creando una cookie, sino que estamos creando una sesión que es almacenada entre nuestro navegador y el servidor. Esto ya que si tratamos de buscar la cookie en el navegador veremos que no aparece.
+
Aquí la ventaja del servidor es que de manera automática, este establece la forma de conexión entre los datos permitiéndonos guardar datos dentro de la sesión, puedes verlo como una cookie que vive del lado del servidor.
+
Ahora bien, al igual que las cookies la recomendación es guardar poca información ya sea por facilidad del servidor y para evitar comprometer información importante del mismo.
+
Más adelante en otros laboratorios veremos de que manera podemos hacer uso más especializado de la sesión, por ahora quédate en la forma de poder crear los datos, modificarlos y eliminarlos.
+
Por lo mismo vamos a añadir 3 nuevas rutas a nuestro navegador:
La primera ruta test_session añadirá un valor en forma de variable a nuestra sesión, el cual podremos acceder de manera inmediata.
+
La segunda ruta test_session_variable nos permite acceder a esa nueva variable de la sesión, en cualquier momento, pero siempre y cuando hayamos creado primero el valor y el servidor no se haya reiniciado.
+
La última ruta logout es la forma en la que destruimos la sesión y vaciamos la información que se encuentra almacenada hasta ese momento.
+
Como ves es muy sencillo el uso y manejo, sin embargo verifica y establece bien los momentos de creación, actualización y eliminación ya que es muy común perder una sesión por no fijarse el ciclo que puede seguir un usuario y se llega a caminos sin salida donde se borró la sesión pero aún se necesitaba la información.
En el laboratorio de introducción al back-end, comenzamos a trabajar con crear un servidor y empezar a servir respuestas desde el mismo. En la última parte incluso pudimos enviar código HTML y conectar parte de lo que hemos trabajado con front-end hasta el momento.
+
Como te había mencionado hasta este punto si bien ya pudiéramos crear un proyecto complejo, aún nos faltan ver algunas cosas que harán nuestra vida más fácil.
+
Ahora que vamos a empezar este camino de optimización, el primer paso es empezar a manejar las rutas del proyecto, si bien esto vamos a irlo mejorando con cada laboratorio que avancemos es momento de empezar a entender como funcionan las URL en un proyecto de desarrollo web.
+
Como ya sabes existen varios métodos de conexión o de peticiones que podemos hacer al servidor, FETCH, GET, POST, PUT y DELETE entre los más conocidos.
+
Este tipo de conexión se hace desde el servidor y somos nosotros los que decidimos de que manera regresar información. Por ejemplo, el espacio en los navegadores web para escribir una dirección o URL, lo que reciben hacen es mandar una petición GET y lo más normal es que estas peticiones nos vuelvan el código HTML ya que si son las que colocamos en la url del navegador son la primera respuesta de acción que veremos.
+
Después de esto veremos de manera muy breve el concepto de las REST API, las cuales a través de los otros métodos de conexión nos permiten enviar o actualizar información, usando los otros estándares que ya hemos mencionado como JSON o XML.
+
Lo importante que empezaremos a ver es que a partir de decidir que método de conexión usaremos podemos ir segmentando la información que tenemos para poderla regresar en diferentes URLs.
+
Esto nos lleva a que todo request debe regresar una respuesta al menos en teoría. El response deberá variar en su formato de regreso pero algo que siempre debe existir es un código de validación de errores.
Es muy importante que te familiarices con estos códigos ya que en desarrollo web lo son todo, muchos malos programadores inventan sus propios estándares confundiendo o haciendo más complicados sus trabajos reinventando la rueda.
+
Empecemos creando un archivo index.js, como en el laboratorio anterior y coloquemos la base de un servidor nuevo.
+
const http = require('http'); const server = http.createServer( (request, response) => { console.log(request.url); //Empezar a declarar las rutas a utilizar }); server.listen(3000);
+
Como en el laboratorio anterior no olvides correr tu servidor mediante la instrucción desde tu terminal:
+
node index.js
+
Recuerda que aún no tenemos nada por lo que el servidor no mostrará nada.
+
Como recordarás este servidor de momento sin importar lo que hagamos para cualquier url nos devuelve la misma respuesta, por lo que es momento de trabajar con las rutas haciendo distinción del request.url.
+
Empecemos sustituyendo el comentario por lo siguiente
+
if (request.url == "/") { response.setHeader('Content-Type', 'text/plain'); response.write("URL index /"); response.end(); }
+
Si ejecutamos el servidor y abrimos en el navegador la url
+
localhost:3000/
+
Veremos nuestro texto resultado, pero si exploramos el elemento del navegador veremos que la página HTML se crea de manera normal.
+
+
Hasta ahora hemos trabajado directamente con el navegador, pero al hablar de variantes con métodos de conexión es una buena práctica cubrir todos los aspectos, para ello veremos una herramienta muy común para poder probar conexiones con el servidor. Cuando no devuelvas exactamente cosas que se verán en el navegador es recomendable utilizarlas. Esta herramienta se llama Postman, pero existen un buen número de herramientas similares que hacen el trabajo.
+
Postman tiene una versión en línea que limita algunas de las pociones que se pueden utilizar por lo que te recomiendo que bajes la versión de escritorio.
+
De preferencia crea una cuenta esto te servirá mucho para el futuro.
+
Para comenzar, Postman es similar a un navegador, en donde necesitamos crear una nueva pestaña y escribir una URL, al igual que en el navegador escribe:
+
localhost:3000/
+
Verás que puedes agregar algunos parámetros adicionales, por el momento no nos preocuparemos al respecto, pero algo que es importante que veas es que el método de conexión lo está realizando por medio de un GET.
+
Para obtener el resultado entonces da clic en el botón Send de la parte superior derecha.
+
+
Aquí veremos una diferencia importante en el navegador con la respuesta, en primer lugar observa el código 200 Ok que aparece reflejado, esto nos indica que en nuestro servidor si no colocamos un código, de manera default siempre se devuelve un código 200, esto no lo olvides ya que puede generar confusiones más adelante.
+
La otra parte es que a diferencia del navegador aquí veremos el response en formato de texto, ya que justamente habíamos declarado response.setHeader('Content-Type', 'text/plain'); lo que indica que el tipo de respuesta es un texto plano.
+
Para ello se utiliza el text/plain, el cual es mejor conocido como los MIME types. Normalmente estamos acostumbrados a que los archivos nos guían en sus extensiones, por ejemplo: .txt,.html,.css,.pdf, etc.
+
Las extensiones en su mayoría nos permite visualizar un tipo de archivo, pero técnicamente no es suficiente con colocar la extensión, sobre todo con archivos binarios, ya que requieren la codificación necesaria, no solo la extensión, para esto nos sirven los MIME types, ayudan a la codificación y decodificación.
+
Para nuestro caso serán necesarios para identificar cuando recibimos, texto, html, json o cualquier otro formato que queramos manejar.
Con el nuevo bloque que escribimos, si en postman entramos a la url
+
localhost:3000/test_json
+
Nuestro resultado será:
+
+
Si bien mantenemos el código 200 Ok, lo que nos interesa es como obtenemos la nueva respuesta, a través del MIME type application/json respondemos con un string el cual una vez decodificado en la respuesta nos lo muestra como JSON.
+
{ code: 200, msg: "Ok" }
+
Aquí necesitamos establecer otro punto importante, el mensaje que enviamos contiene 2 partes, un code que embona con el código 200 resultado, pero es importante que sepas que lo que escribimos es solo para enfatizar el mensaje, pero bien el número de código no afectaría en base al estándar. Lo mismo pasa con msg, cuando estés trabajando con REST APIs entenderás más al respecto.
+
Por último vamos a servir otra url para un código HTML, y vamos a cambiar el if y else por un switch, quedando de la siguiente manera:
+
switch(request.url){ case "/": response.setHeader('Content-Type', 'text/plain'); response.write("URL index /"); response.end(); break; case "/test_json": response.setHeader('Content-Type', 'application/json'); response.write('{code:200, msg:"Ok"}'); response.end(); break; case "/test_html": response.setHeader('Content-Type', 'text/html'); response.write(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Código en HTML</title> </head> <body> <h1>hola mundo desde node</h1> </body> </html> `); response.end(); break; }
+
El resultado será el siguiente:
+
+
Aquí notarás que Postman no cargará el código HTML, sino que solo nos mostrará el contenido tal cual, pero puedes probar en el navegador a ver la respuesta.
+
+
Con lo que hemos visto, espero que te hayas dado cuenta de que el código que ejecutamos en el servidor es secuencial-asíncrono, es decir, las peticiones llegan a nuestro archivo index.js y se procesan buscando una respuesta de salida, si no se encuentra nada, pasa como al inicio que el navegador no sabe que interpretar de regreso, pero si detecta que manejamos la ruta de la url entonces el servidor empieza a saber por donde irse.
+
Hasta el momento hemos definido 3 rutas:
+
/ /test_json /test_html
+
Agreguemos 1 más para genera una ruta POST, para test_json vamos a modificar al case correspondiente, ahora debemos añadir request.method para identificar si es un GET o un POST.
Ahora según el método que seleccionemos tendremos el resultado correspondiente:
+
+
+
Nuestras rutas ahora tienen soporte para diferentes URL y métodos de conexión, pero es imposible hacer que soporten todo, por ello siempre se recomienda manejar el código estándar 404, el cual se utiliza para cuando no se encuentra una ruta en el servidor.
Dentro del código HTML, existe una etiqueta especial que nos permite manejar formularios para enviar dicha información al servidor, esta utiliza la etiqueta especial form.
+
Una forma de verla en su formato más simple es la siguiente:
method: [GET,POST,PUT,DELETE] permiten colocar cualquier tipo de método de conexión que necesitemos para subir como ya hemos venido diciendo la información y alinearla dentro de nuestras rutas. En caso de no escribirla por default utiliza el método GET, pero lo más común es utilizarla en métodos tipo POST ya que todo el contenido se guardará dentro del body del request.
+
action: Contiene la url de formato a llamar, dependiendo el tipo de servidor aquí puede variar, en ocasiones se escribe la url completa incluido el dominio, para nuestro caso basta con empezar después de localhost:3000, utilizando primero / y de ahí el nombre interno de rutas que se tiene.
+
enctype: De momento todavía no lo veremos pero es un formato que permite trabajar para el caso especial de subir archivos al servidor.
+
+
+
+
Los elementos internos:
+
+
Inputs - Son etiquetas html que sirven para crear cualquier componente de formulario, si bien todas son etiquetas iguales, asignarles la propiedad type es lo que hace que empiecen a ser diferentes. Utiliza el archivo en el ejemplo de este laboratorio al final, de referencia para que veas como se declaran y utilizan. Todos los valores que almacenan se guardan en la propiedad value y a diferencia de las otras propiedades HTML que utilizan el id para identificarse, si bien pueden utilizar el id, dentro del formulario utilizan la propiedad name para asignar el valor correspondiente al mandarlo al servidor, es decir que desde el servidor utilizaremos el nombre asignado a esta propiedad para acceder al valor.
+
+
+
+
+
Nota: Cuida mucho en no confundir el elemento button con el submit, ya que el primero solo funciona para añadir alguna funcionalidad dentro del formulario, pero el segundo es el encargado de enviar la información del formulario al servidor.
+
+
+
Nota 2: En internet existen muchas librerías que añaden mejor funcionalidad o facilidad para trabajar con algunos componentes dándoles mayor usabilidad o mejor diseño. Te recomiendo siempre tengas a la mano librerías para datepickers y establezcas un formato unificado para los forms que por lo general ya traen integrado los frameworks de diseño.
+
+
Para que el código de nuestro servidor funciones vamos a tener que separara el GET y el POST, donde para el primer escribiremos el html del formulario que queremos y para el POST procesaremos la respuesta a subir esa información.
+
Empecemos con la base abriendo un nuevo case que involucre la ruta /form_method
Entonces para el caso del GET vamos a aprovechar y hacer algo diferente para trabajar más fácilmente con el código HTML. Esta aún no es la mejor forma de optimizar la carga de código HTML, pero es una buena alternativa sobretodo para cuando trabajas con archivos HTML muy largos que deben estar aislados.
Ahora en nuestro archivo index.js vamos a declarar hasta arriba del mismo el uso de las librerías path y fs la cual vimos en laboratorios anteriores.
+
const http = require('http'); //Ya estaba declarada para nuestro servidor const path = require('path'); const fs = require('fs');
+
Por último de nuevo en el case, dentro del if del método GET, escribiremos lo siguiente:
+
response.setHeader('Content-Type', 'text/html'); const html = fs.readFileSync(path.resolve(__dirname, './form.html'), 'utf8') response.write(html); response.end();
+
Como puedes ver estamos utilizando el mismo header de código html que en la ruta test_html, pero a diferencia de ese aquí estamos utilizando la librería fs para leer el archivo form.html en memoria, codificándose a utf8 y pasándolo como un string para escribirlo en el response.write().
+
Esta es la forma más útil de poder probar y ver nuestro código html y no escribirlo al vuelo pues de lo contrario tendremos que hacer mucha prueba y error antes de verlo como nosotros queremos.
+
Si volvemos a ejecutar el servidor y accedemos al navegador tendremos algo como lo siguiente:
+
+
Ahora solo basta con que desde el POST recibamos la información, de la siguiente manera:
+
let body = []; request .on('data', chunk => { body.push(chunk); }) .on('end', () => { body = Buffer.concat(body).toString(); console.log(body) const indice = Number(body.split('&')[0].split('=')[1]); console.log(indice); const imprimir = body.split('&')[1].split('=')[1]; console.log(imprimir); for(var i = 1; i <= indice; i++){ console.log(imprimir) } response.setHeader('Content-Type', 'application/json'); response.statusCode = 200; response.write('{code:200, msg:"Ok POST"}'); response.end(); });
+
El siguiente código parece muy complejo, pero en realidad no lo es, vayamos punto por punto.
+
Al utilizar NodeJS plano necesitamos procesar la información mandada por el request y generarla en un formato apto para nosotros, esto es una mejor forma de leerlo.
+
Para ello debemos hacer uso de 2 métodos asíncronos para guardar la información del formulario en un arreglo.
+
let body = []; request .on('data', chunk => { body.push(chunk); }) .on('end', () => { body = Buffer.concat(body).toString(); // at this point, `body` has the entire request body stored in it as a string });
+
En estos métodos observa como se utiliza el string data y el string end, estas son como variables o estatus que se ejecutan en diferentes momentos. data ocurre en cada lectura del body, si fuera un archivo sería cada línea de dicho archivo. Y end se ejecuta al terminar de procesar todo el body.
+
Entonces por cada línea iremos agregando al arreglo que definimos como body y una vez finalizado ejecutamos el código que queremos tener.
+
+
Nota: Recuerda que el código es asíncrono por lo que el contenido después de tener el body unificado va a dentro de la función flecha de end y no afuera.
+
+
El resultado obtenido será algo como lo siguiente:
+
indice=10&imprimir=Hola+Mundo
+
Ahora bien el problema está en que debemos separar cada propiedad y además separar cada llave de su valor, para ello utilizamos el código:
Excelente, hemos creado un formulario y hemos procesado su información correctamente en nuestro servidor.
+
Más adelante veremos como hacer de manera más simple este proceso, pero al menos ahora sabes como lo hace NodeJS.
+
En resumen, crear rutas, utilizar métodos de conexión, definir el correcto estándar de subida y bajada es el día a día del desarrollador de back-end ya que es la apertura a que los usuarios puedan conectarse al servidor y ejecutar, acciones, funciones o almacenar información.
En el desarrollo web y en desarrollo del backend ya hemos visto algunas estrategias para poder comenzar a escribir nuestro código, sin embargo en el camino nos hemos encontrado con alguna formas extrañas para hacerlo, ya sea para definir código o para estructurarlo.
+
Si bien no es un problema, lo que buscamos al final es ser lo más prácticos posibles para evitar duplicar código y que este sea lo más sencillo posible.
+
A esto nos lleva el uso de frameworks que no son otra cosa que un conjunto de librerías y buenas prácticas para estructurar proyectos de desarrollo web.
+
En el mercado existen de una gran cantidad y más que decir que uno sea mejor que otro lo importante es decir que utilicemos uno, pues como ya mencionamos harán nuestra vida más fácil y nos ayudarán a estandarizar nuestra forma de trabajo.
Cuando instalamos npm tenemos una herramienta de terminal que por default viene instalada siempre, y esta es mejor conocida como el node package manager o npm, esta herramienta nos permite instalar librerías en nuestros proyectos o crear nuevos proyectos con ciertas configuraciones.
+
Un comando para instalar una librería por ejemplo sería:
+
npm install pm2
+
Aquí estamos llamando a instalar una librería llamada pm2, solo que en el contexto de hacerlo aquí se instalaría en la carpeta de nuestro proyecto.
+
npm install pm2 -g
+
Vamos a instalar la librería de manera global para el ejercicio que estaremos trabajando.
+
La bandera -g hace la misma instalación, pero esto hace que la librería sea global, es decir que sea accesible desde cualquier lugar de nuestra computadora. Internamente Node ya sabe que debe realizar la instalación en una carpeta especial a la cual si se hace la llamada a la librería debe estar disponible.
+
Ahora bien como te mencioné, podemos iniciar un proyecto desde aquí, pero debemos considerar un punto en el control de versiones que no hemos cubierto hasta el momento.
Dentro de lo que hemos visto en nuestros laboratorios de control de versiones, hemos visto como manejar los archivos y como movernos en diferentes ramas para crear flujos de trabajo, sin embargo, algo bastante común es querer evitar subir ciertos archivos al repositorio, ya que pueden ser archivos de seguridad o son archivos que tan solo tenerlos arriba nos quitarán espacio y no es necesario tenerlos.
+
Para poder evitar subir archivos al repositorio, necesitamos un archivo llamado .gitignore, observa que este archivo empieza con un punto y lo único que contiene son los archivos y carpetas que queremos ignorar.
+
Dentro de la comunidad ya existen algunos estándares para estos archivos según los lenguajes que estemos trabajando, para lo que vamos a realizar en este laboratorio, te dejo el archivo para que lo agregues en la raíz de la carpeta de tu proyecto.
+
Para los proyectos de node lo que vamos a querer evitar es subir una carpeta llamada node_modules, esta carpeta contendrá todas las librerías del proyecto. La razón de por que queremos evitar subirlas es por que esta acción se hace siempre que se inicia el proyecto, guardarlas puede crear el conflicto de guardar librerías viejas que a la larga causan más mal que bien y por tanto cada vez que clonamos el repositorio hacemos un fresh install que nos asegura la calidad del proyecto se mantiene.
Ya que tenemos nuestro .gitignore en su lugar, ahora vamos a ejecutar el siguiente comando:
+
npm init
+
Como resultado comenzaremos un pequeño wizard que creará unas configuraciones iniciales para nosotros, por ahora podemos darle enter a todas las opciones.
+
Al final veremos reflejado un nuevo archivo llamado package.json, el resultado debería ser algo como lo siguiente:
El archivo package.json nos dará una visibilidad inicial de como ejecutar y correr nuestro proyecto diferente a lo que hemos visto hasta el momento. Las configuraciones iniciales nos permiten escribir los datos de nuestro proyecto como el nombre, la versión, una descripción y el que nos interesa es el main pues es el que nos dice que archivo inicial se ejecuta. Recuerdas que te comente que en Node podemos utilizar app.js o main.js, aquí es donde realmente hacemos la distinción y en nuestro caso usaremos index.js.
+
Ahora vamos a instalar nuestra primera librería, en este caso express.
+
npm install express -s
+
La bandera -s le dirá a npm que debe agregarla a nuestro package.json, por lo que debes estar a la misma altura al manejarlo, esto permitirá que se vaya haciendo la lista de las librerías que vayamos usando en el proyecto.
+
Si revisamos el resultado final del archivo package.json se vería como lo siguiente:
Nota como se agregó la opción de dependencies y dentro de ella se agregó correctamente express.
+
Un error al inicio es querer modificar la versión de la librería y cambiarla por un asterisco (*), el asterisco representa la última versión disponible de la librería, en el corto tiempo no sucederá nada, pero en el largo plazo esto no es una buena práctica pues conforme avance el tiempo vamos a necesitar la librería específica, por que veremos con el tiempo que las librerías en Node tienden cambiar entre ciertas versiones y esto nos puede llevar a problemas al correr nuestro proyecto. Al tener el número de librería original va a ser más fácil para nosotros poder instalarla entre la jungla de versiones que se desarrollen a futuro.
+
+
Nota: Nunca actualices una librería solo cambiando el número, si tienes suerte no sucederá nada, pero si no, romperás todo tu proyecto y puede llevarte a un efecto domino.
+
+
Ahora que verás la actualización, también es probable que veas un archivo llamado package-lock.json, este archivo es el hermano perdido del primero, puede parecer molesto y puede darte algunos dolores de cabeza en el control de versiones, pero es un archivo sin filtros de como se estructura en tu máquina el proyecto, ya que si quieres pasarlo a otra máquina puedas hacerlo sin batallar con las librerías. De momento no vamos a tomarlo en cuenta pero para tu proyecto ten cuidado con él, ya que aunque lo elimines volverá a aparecer.
Ya que instalamos express vamos a empezar con nuestro archivo index.js, si aún no está creado empieza y añade el siguiente código.
+
const http = require('http'); const express = require('express'); const app = express(); //Middleware app.use((request, response, next) => { console.log('Middleware!'); next(); //Le permite a la petición avanzar hacia el siguiente middleware }); app.use((request, response, next) => { console.log('Otro middleware!'); response.send('¡Hola mundo!'); //Manda la respuesta }); const server = http.createServer( (request, response) => { console.log(request.url); }); app.listen(3000);
+
Este pequeño código nos permitirá crear un servidor para utilizar express y realizar algunas funciones para nosotros.
+
La primera que veremos es conocida como Middlewares, estos son funciones que se van a ejecutar antes de realizar una instrucción o ruta del servidor, piensa en el caso de la autenticación de un usuario, podemos tener una verificación de autenticación en cada ruta de nuestro código, esto no será lo más óptimo pues estaremos duplicando código a diestra y siniestra. Para ello será mejor tener esta función que se ejecute antes de cada ruta y que este centralizada en el mismo pesado de código. Si bien podríamos llamarla simplemente como una función externa, el uso de middlewares proporcionado por express nos ayudará a secuenciar mejor el código más que como un proceso que como una función.
+
Regresando al código que acabamos de agregar, observa que tenemos 2 Middleware, y la sintaxis que utilizan después de configurar express, es el uso del objeto request y response que son los mismos que ya vimos en laboratorios anteriores y aquí están agregando un objeto más, el next. El objeto next es el que me indica que esto es un middleware pues lo único que haremos es que una vez que termine nuestra función que queremos ejecutar en middleware debemos llamar a next(), para decirle a express que avance a la siguiente sección o al siguiente middleware.
+
Por tanto, antes de que nuestro servidor imprima la url del request, deberá imprimir Middleware y Este es otro middleware.
+
Vamos a probarlo, pero para hacerlo vamos a cambiar la forma en la que ejecutamos nuestro código.
Como vimos al inicio del laboratorio, te pedí que instalaras de manera global la librería de pm2, esta librería queremos tenerla instalada fuera del proyecto ya que será lo mismo para cualquier proyecto que tengamos que ejecutar.
+
PM2 es una librería de administración de procesos, dicho de otra forma es una librería que impide que nuestro servidor se apague solo por el echo de cerrar la terminal. Es decir, crea un proceso en segundo plano que no sea dependiente de la terminal.
+
La principal ventaja de usarla es facilitar nuestro flujo de trabajo al evitar estar prendiendo y apagando el servidor constantemente, pero también nos permite añadir configuraciones adicionales para nuestro servidor que iremos viendo poco a poco. Por último con esta librería estaremos añadiendo una capa lo más parecida a cuando vayamos a desplegar nuestro proyecto en producción, y esto es importante ya que entre más igual sea el desarrollo y el pase a producción, más fácil será realizar el despliegue.
+
Por ahora ve aprendiendo los siguiente comandos de inicio:
+
pm2 start index.js --watch //Corre el proyecto y observa cualquier cambio en archivos para actualizar el servidor pm2 stop index.js //Detiene el proceso actual pero no lo borra pm2 delete index.js //Borra el proceso pm2 kill //Detiene y borra todos los procesos en ejecución pm2 logs //Permite ver la consola y los errores de la misma pm2 ls //Visualiza la tabla de procesos de pm2
+
Conforme vayamos avanzando iremos siendo más certeros en los comandos con algunos parámetros adicionales, pero por el momento es suficiente.
+
Corre el servidor con:
+
pm2 start index.js --watch
+
Sí todo corre bien deberás ver una especie de tabla en la terminal con lo siguiente
+
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤ │ 0 │ index │ fork │ 0 │ online │ 0% │ 42.8mb │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
+
Este es el resumen de pm2, aquí verás el estado actual de tu proyecto, cuantas veces se ha reiniciado y cuanta memoria utiliza para correr.
+
Si entramos al navegador y vemos localhost:3000, el resultado será el Hola Mundo.
+
+
Pero en nuestra terminal si ejecutamos pm2 logs veremos lo siguiente:
+
0|index | / 0|index | Middleware! 0|index | Otro middleware!
+
Lo que sucede aquí, es que nuestro servidor aún no maneja rutas, por lo que primero llega a imprimir la url del request, pero una vez que lo hace empieza a ejecutar nuestros middlewares, pon especial atención en que no parece que se estén llamando directamente, y esto es por la variable app que de declaramos. Al menos para este arranque esta variable nos permite conectar en cadena cada uno de los elementos en el archivo haciendo que se ejecute el primer middleware, luego el segundo y así sucesivamente si tuviéramos más.
+
Ahora vamos a modificar nuestro segundo middleware a lo siguiente:
+
app.use((request, response, next) => { console.log('Otro middleware!'); response.status(404); response.send('¡Page Not Found!'); //Manda la respuesta });
+
En el laboratorio anterior teníamos una manera de centralizar los errores 404 de nuestro servidor, por lo que ahora realizaremos lo mismo pero utilizando la estructura de express.
En la forma previa para poder identificar rutas, necesitábamos meter un switch dentro de la función create server e identificar según la url que se estuviera llamando y hacer nuestro segmento de código de acuerdo al método de conexión y la ruta.
+
Ahora con express todo esto será mucho más sencillo, para declarar la ruta default por ejemplo haremos lo siguiente:
Este código lo agregaremos previo a nuestro middleware del 404.
+
Al igual que el laboratorio pasado haremos que envíe un texto simple de respuesta.
+
+
Intenta adaptar el laboratorio anterior a que funcione con express, nota que el método de conexión GET utiliza el app.get(), si necesitaras un POST, entonces necesitarías el app.post().
Ve que la principal diferencia fue haber separado en funciones nuestro código para evitar el switch que teníamos y sus internos del GET y POST.
+
Ahora bien vamos a mejorar la forma en la que trabajamos con nuestro formulario, y esto lo haremos a través de una nueva librería que debemos instalar en nuestro proyecto.
+
npm install body-parser -s
+
Para usarla, la colocaremos debajo de la declaración de la variable app.
El body parser nos permitirá trabajar más fácilmente con las variables recibidas de nuestros request ya que su trabajo le permite codificar el body de una petición de manera sencilla y mas entendible.
+
Lo que teníamos al trabajar con el formulario era lo siguiente:
+
let body = []; request .on('data', chunk => { body.push(chunk); }) .on('end', () => { body = Buffer.concat(body).toString(); console.log(body) const indice = Number(body.split('&')[0].split('=')[1]); console.log(indice); const imprimir = body.split('&')[1].split('=')[1]; console.log(imprimir); for(var i = 1; i <= indice; i++){ console.log(imprimir) } response.setHeader('Content-Type', 'application/json'); response.statusCode = 200; response.write('{code:200, msg:"Ok POST"}'); response.end(); });
Aquí nos estamos deshaciendo de el arreglo adicional que creamos para el body, los split innecesarios que hicimos y quitamos el streaming request para leer todo el formulario.
+
Ahora si observas lo que tenemos es que desde el objeto request accedemos a la variable body y desde ahí podemos acceder a la variable de nuestro formulario indice e imprimir.
+
El uso de request.body se limitará a funciones que manden información por el body ya que no siempre sucede puede que en ocasiones llegue vacío. Estos casos los iremos viendo más adelante.
Ya tenemos varias rutas trabajadas en nuestro proyecto, pero cada una puede representar un caso diferente, tan solo el manejo del formulario podríamos encapsular en un solo archivo para reducir que crezca mucho el index.js, vamos a hacerlo.
+
Crear un archivo nuevo llamado formulario.routes.js. Esto lo haremos dentro de una nueva carpeta a la cual llamaremos routes.
+
Ahora previo a las rutas del formulario al GET y POST, colocaremos lo siguiente:
La anterior es la plantilla básica de archivos de express, cuando queramos que algo tenga continuidad en nuestro servidor podemos usar esta plantilla. Nota que a diferencia de index.js aquí se llama a router y no a app, esto es la forma de express de delegar la responsabilidad en archivos diferentes.
+
Pore tanto el resultado de nuestro archivo deberá quedar como lo siguiente:
Aquí tuvimos que adecuar algunas cosas como sustituir el app por el route y la dirección del archivo form.html que ahora queda afuera de la carpeta routes.
+
Tampoco olvides actualizar el valor del action del form.html
+
action="/formulario/form_method"
+
Si recargamos el navegador y entramos a localhost:3000/formulario/form_method, veremos nuestro formulario con normalidad.
+
Este último cambio nos permite no solo crear la cantidad de niveles que deseemos de rutas y módulos, sino que nos permite crear una estructura lógica total de un proyecto de desarrollo web sin ningún problema.
Hasta el momento hemos trabajado con archivos construidos de HTML que son conocidos como estáticos, este tipo de archivos son útiles hasta cierto punto. Si bien podemos generar todo un sitio a partir de sitios estáticos, la labor de trabajo será bastante fuerte. Para eso nos ayudará el construir el contenido HTML de forma dinámica, para reducir el trabajo y hacer más efectivo lo que queremos mostrar.
Antes de comenzar con el contenido dinámico, vamos a establecer un punto importante dentro de nuestros servidores y de nuestro back-end. Con lo que hemos visto hasta el momento hemos podido leer los archivos HTML, CSS y JS sin ningún problema. En la realidad esto no es tan simple, pues nos lleva a un tema de seguridad importante que tienen todos los servidores.
+
Pensemos en el siguiente caso, tenemos un archivo index.html, el cual queremos servir a nuestros clientes desde el servidor. Hasta ahora lo que hemos visto con NodeJS y Express es que podemos crear una url para leer dicho archivo y pasarlo en formato de texto, si bien esta aproximación es adecuada, es importante entender que sucede. Dentro de nuestro servidor nosotros tenemos una carpeta de proyecto con todos los archivos a utilizar en nuestro proyecto, el archivo index.html, podemos tenerlo en la raíz, o en otra carpeta y según la ruta que utilicemos podemos servirlo, por ejemplo como en el laboratorio pasado dentro del módulo de formulario.
Aquí utilizamos el filesystem para acceder al archivo html y servirlo. La otra forma menos adecuada es servir directamente el html como en el otro caso:
+
app.get('/test_html', (request, response, next) => { response.setHeader('Content-Type', 'text/html'); response.write(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Código en HTML</title> </head> <body> <h1>hola mundo desde express</h1> </body> </html> `); response.end(); });
+
Sea de una forma o de otra, una es menos práctica y la otra nos obliga a servir el archivo desde el filesystem. Este tipo de archivos html son estáticos y por tanto no cambian, además de que si queremos cargarles funcionalidad de CSS o JS para manipular el DOM, estaremos limitados.
+
Para ello NodeJS y express nos permiten crear un folder especial dentro de nuestro proyecto conocido como la carpeta pública, esto permitirá que cualquier archivo dentro pueda ser servido desde el servidor a través de una url.
+
Veamos como configurar esto y ver las posibilidades que nos da.
+
Vamos a crear un nuevo servidor utilizando el npm init. Crea la configuración a tu gusto.
+
Instala express y el body-parser como hasta ahora:
+
npm i express npm i body-parser
+
Crea un archivo index.js que incluya la configuración básica de express y del body-parser.
No olvides agregar el archivo .gitignore del proyecto para evitar subir los /node_modules
+
Para ejecutar el servidor vamos a utilizar pm2
+
pm2 start index.js --watch
+
La bandera --watch nos permitirá que cuando haya cualquier cambio en la carpeta del proyecto, en particular cambios en index.js se reinicie el servidor de manera automática.
+
Si quieres acceder a la consola usando pm2 utiliza:
+
pm2 logs
+
+
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤ │ 0 │ index │ fork │ 0 │ online │ 21.9% │ 43.4mb │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
+
Ya que tenemos la base de nuestro servidor vamos a crear la configuración de la carpeta pública.
+
Esto lo haremos añadiendo la siguiente instrucción:
Lo que estamos haciendo aquí es declarar una carpeta de nuestro proyecto y decirle al servidor que todo lo que contenga podrá servirse de manera directa como público.
+
En otras palabras, estamos exponiendo esta carpeta al exterior, es por eso que te menciono la parte de seguridad, cuida mucho que archivos subes aquí pues serán accesibles desde cualquier navegador.
+
Para probar el cambio, crea la carpeta public en el proyecto.
+
+
Dentro de esta carpeta vamos a crear un archivo index.html con lo siguiente:
+
<!DOCTYPE HTML> <html> <body> <p>Before the script...</p> <p>...After the script.</p> </body> <script src="./script.js"></script> </html>
+
También agregaremos un archivo script.js con lo siguiente:
+
alert( 'Hello, world!' );
+
Esta configuración es la misma de los primeros laboratorios del curso pero ahora contenido en nuestro servidor.
+
Guarda los archivos, si estas ejecutando pm2 como te indique no necesitas realizar ningún reinicio.
+
Ahora veamos como acceder, desde el navegador utilizar la url:
+
http://localhost:3000/index.html
+
El resultado:
+
+
Si recuerdas, en nuestros intentos anteriores podíamos cargar el html, pero no el javascript, ahora con los archivos públicos el navegador sabe como orientarse y puede cargar el archivo script.js. Es más intenta acceder a la url:
+
http://localhost:3000/script.js
+
+
El resultado es el contenido del archivo, en este caso nuestro código en javascript.
+
Ahora será posible para tí, poder cargar cualquier tipo de archivo CSS, JS o incluso imágenes.
+
Lo común es estructurar dentro la carpeta pública una carpeta de assets, y a esta separar, css, js, fonts, imgs, etc.
+
Dependerá del proyecto la cantidad de archivos que tengas.
+
+
Nota: Una buena práctica es cargar todos los archivos que necesite el sitio dentro de la carpeta pública, pero un error común al inicio es guardar archivos que pudieran tener alguna situación de seguridad como documentos oficiales u otros, aquí entra un tema importante de seguridad que no abarcaremos, más que decir que si un archivo no es para cargar el sitio la recomendación es no guardarlo en la carpeta pública incluso no dentro del repositorio del proyecto.
+
+
Prueba a agregar otros archivos y observa el potencial de la carpeta pública.
Ahora vamos a comenzar con el contenido HTML dinámico. Para ello utilizaremos una librería muy conocida en el ámbito de NodeJS que se llama EJS, para instalarla ejecuta:
+
npm i ejs
+
Al igual que la carpeta pública necesitamos realizar una configuración para decirle a NodeJS y a express que esta sera la fuente de plantillas dinámicas que utilizaremos.
+
Para ello después de configurar express agregaremos lo siguiente:
Aquí definimos un view engine y la carpeta de donde saldrán estos archivos, en este caso la carpeta views.
+
Es importante mencionar que existen varios engines para html dinámico, por ejemplo: uno muy conocido es pug. En nuestro caso usaremos EJS que es la base para trabajar con express cuando se inicia. Si bien existen varios, express solo nos permite trabajar con 1 engine por proyecto, decide bien cuando trabajes con tu proyecto.
+
Por último vamos a crear la carpeta views dentro del proyecto, esto tendrá una mayor extensión en el próximo laboratorio, pero por ahora ubica que todo lo que va en esta carpeta será código html del proyecto.
+
+
Algo que puede llegar a confundirte es que si ya tenemos la carpeta pública donde podemos agregar archivos HTML, por que necesitamos esta nueva carpeta views para HTML.
+
Aquí va a depender de la forma en que construyamos el proyecto, por ejemplo, si utilizamos REACT, lo ideal es que todo va dentro de la carpeta pública, pero esto es por que todo el ecosistema de react nos pide que hagamos un proyecto aparte para trabajar solo en la interfaz.
+
Esto significa que si trabajamos con REACT, VUE, Angular o frameworks de desarrollo de front-end, no utilizaremos los motores de html dinámico como EJS o PUG.
+
Existe mucha discusión al respecto sobre cual forma es mejor que otra, en la realidad la respuesta dependerá del equipo de desarrollo y sus conocimientos, como toda herramienta tiene sus beneficios y desventajas trabajar de una manera u otra.
+
Al trabajar con el engine de html dinámico, vamos a ver que mientras trabajemos con estos archivos, estos siguen del lado del servidor, esto quiere decir que mientras utilicemos la sintaxis de los mismos quien los tiene es el servidor y no se han cargado en el navegador y mucho menos en el DOM.
+
Esto es importante por que a veces queremos que se ejecute código de javascript del lado del cliente, pero si no entendemos en que parte del proceso está el HTML vamos a tardarnos en entender el error.
+
Vamos a servir el mismo html que creamos en la carpeta pública, dentro de la carpeta views crea un archivo index.ejs, nota la extensión ejs en lugar de html.
+
<!DOCTYPE HTML> <html> <body> <p>Before the script...</p> <p>...After the script.</p> </body> <script src="./script.js"></script> </html>
+
Ahora vamos a crear una url en nuestro servidor que sirva a esta vista creada, la url la llamaremos test_ejs
El resultado es como el anterior, pero la diferencia es que estamos cargando el html desde nuestro archivo ejs.
+
Hasta ahora no hemos echo nada extraordinario. Pero ahora veremos las capacidades que nos ofrece el uso de EJS en nuestros proyectos.
+
Vamos a actualizar nuestro index.ejs con lo siguiente:
+
<!DOCTYPE HTML> <html> <head> <style> html{ background-color: black; color: red; } </style> </head> <body> <p>Before the script...</p> <p>...After the script.</p> <footer> <p>This is a footer</p> </footer> </body> <script src="./script.js"></script> </html>
+
Como verás tenemos un style, un footer y nuestro script.
+
Una de las capacidades del EJS es crear plantillas para pedazos del código HTML. Visto de otro modo es una forma de simplificar el archivo index.ejs recortando en archivos más pequeños la funcionalidad que necesitemos, esto no está limitado, puede ser a cualquier archivo que nosotros queramos, como ejemplo vamos a dividir los elementos del archivo.
+
Dentro de la misma carpeta views, crea los siguientes archivos con su respectivo contenido del index.ejs.
Nota como simplificamos el código HTML, importando los archivos ejs que ya creamos, vamos un poco más allá y vamos a crear otro archivo ejs llamado footer.ejs con el contenido del footer.
+
<footer> <p>This is a footer</p> </footer>
+
Ahora veremos que no debemos actualizar index.ejs, sino body.ejs, esto nos permitirá embeber código de ejs dentro código ya segmentado de ejs.
+
Aquí las posibilidades ya son únicas, puedes cortar todo un sitio web por ejemplo separando la barra de navegación en 1 solo archivo, separar las secciones de una landing page como el hero section, los formularios, el footer, entre muchas otras combinaciones.
+
La configuración rápida que te recomiendo es segmentar los css y scripts de la página, pues aunque son archivos separados como nuestro script.js, vamos a ver que una página incluye varios archivos así como librerías y es más fácil segmentarlos en 1 solo archivo para todo el sitio que tenerlos duplicados en diferentes páginas web.
+
Bien echo el código EJS permite tener múltiples sitios con diferentes diseños y funcionalidades en un solo proyecto, mal echo es un desastre de múltiples carpetas que no se sabe a donde llevan.
+
Algo que no hemos visto hasta ahora es la particular sintaxis de EJS, si quieres ver toda la documentación al respecto no olvides consultar su página oficial.
+
Cuando hablamos de código de EJS veremos que siempre viene acompañado de los siguientes símbolos.
+
<% %> <%- %> <%= %>
+
Estos podemos verlos como una especie de etiquetas super cargadas de HTML que son las que incluyen el código de EJS.
+
La etiqueta de EJS pueden variar con los símbolos, cuando no lleva nada, utiliza código Javascript completo, - y = la diferencia principal que vamos a tener en cada uno es que al usar el - se puede escribir código de javascript a evaluar antes de cargar, mientras que = solo va a tomar el valor de variables como veremos a continuación.
+
Vamos a pensar que queremos cargar una lista de frases dentro de nuestro archivo body.ejs. Para hacerlo vamos a cargar información que viene desde nuestro servidor.
+
Desde nuestro archivo index.js vamos a actualizar la ruta con lo siguiente:
Aquí declaramos una lista de frases, y en nuestro response mejoraremos el render. La función render carga el archivo ejs que le digamos en este caso el index.ejs, nota que no es necesario escribir la extensión del archivo. Como segundo parámetro que puede existir o no podemos agregar un JSON para pasar información al archivo ejs. Aquí es el punto donde te mencionaba que el archivo sigue existiendo en el servidor, todo este proceso de cargar el ejs en partes, pasar la información y dejar un código específico de HTML se hace en un pre renderizado del lado del servidor, para que al final se pase el código HTML como uno solo al cliente.
+
Dentro del body.ejs entonces necesitamos cargar:
+
<p>Before the script...</p> <p>...After the script.</p> <% for(var i = 0; i < frases.length; i++){ %> <p><%= frases[i] %></p> <% } %> <%- include('footer.ejs') %>
+
Aquí vamos a incluir código de Javascript dentro del HTML, pero utilizando la sintaxis de EJS, podemos crear un ciclo for, y dentro del mismo podemos agregar una etiqueta HTML, para que dentro de la misma coloquemos el valor de la frase que necesitamos.
+
Como todo es cuestión de práctica para que te familiarices con todas las formas en que puedes escribir tu código HTML, pero ve que entre poder segmentar en archivos y utilizar código de Javascript para escribir el HTML la lógica del sitio se hace más simple.
+
Si abres el navegador en:
+
http://localhost:3000/test_ejs
+
El resultado será el siguiente:
+
+
Experimenta y revisa la documentación de EJS para que te familiarices más con el contenido.
+
Al final del laboratorio no olvides detener pm2 utilizando
+
pm2 stop index.js pm2 delete index.js
+
O el comando que elimina toda aplicación corriendo
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/docs/backend/node/tutorials/intro_web/Lab17BD/index.html b/deprecated/old-docs-build/docs/docs/backend/node/tutorials/intro_web/Lab17BD/index.html
new file mode 100644
index 0000000..96db7b3
--- /dev/null
+++ b/deprecated/old-docs-build/docs/docs/backend/node/tutorials/intro_web/Lab17BD/index.html
@@ -0,0 +1,96 @@
+
+
+
+
+
+Interacción con la Base de Datos | TC2005B
+
+
+
+
+
+
+
Para este laboratorio vamos a comenzar con el trabajo de conectarnos con una base de datos relacional.
+
El objetivo de este laboratorio es demostrar como realizar fácilmente la conexión y poder interactuar con una base de datos.
+
El laboratorio asume varios conocimientos que deben de tenerse a este punto por lo que si tienes dudas previo a, o desconoces de los temas te invito a que los revises:
+
Pre-requisitos:
+
+
Conocimiento general de bases de datos relacionales.
+
Conocimiento básico del lenguaje SQL.
+
Tener instalado una versión de MariaDB en tu computadora.
+
De preferencia tener una pequeña base de datos para poder hacer la conexión y pruebas fuera del laboratorio.
+
+
Si bien estaremos utilizando MariaDB para la realización de este laboratorio es importante mencionar que solo es por usar una base de datos, en la realidad el conocimiento aplicado puede aplicarse a cualquier tipo de base de datos relacional, solo deberías poder encontrar el conector correspondiente a tu base de datos de elección.
+
Conexión con MariaDB
+
Vamos a iniciar un nuevo proyecto con npm init y vamos a instalar las librerías de express y el body-parser, así como vamos a agregar una nueva librería.
+
npm i mariadb
+
Una vez tengamos nuestro package.json preparado vamos a crear la plantilla base de nuestro servidor.
También no olvides que si trabajas con un repositorio debes agregar el archivo .gitignore.
+
Ahora bien, vamos a comenzar creando nuestra configuración para MariaDB, de momento lo realizaré directamente desde la raíz del proyecto, pero lo mismo que estoy haciendo debe hacer desde la carpeta de modelos de tu proyecto, esto cuando estemos trabajando con MVC.
+
Para comenzar con el código vamos a crear una conexión con la base de datos, sobre la ruta / vamos a añadir lo siguiente:
Deberás sustituir los valores de user y pass por los que hayas configurado en tu base de datos al momento de instalarla. Recuerda que para entornos de producción lo ideal es tener contraseñas largas para añadirle dificultad al acceso de la base de datos.
+
Ahora antes de hacer cualquier cosa vamos a añadir unos datos dentro de MariaDB para poder probarlo.
+
+
La base de datos que vamos a crear se llamará test
+
Desde tu DBMS favorito ejecuta los siguientes comandos:
+
CREATE DATABASE IF NOT EXISTS test; USE test; CREATE TABLE IF NOT EXISTS books ( BookID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, Title VARCHAR(100) NOT NULL, SeriesID INT, AuthorID INT); CREATE TABLE IF NOT EXISTS authors (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT); CREATE TABLE IF NOT EXISTS series (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT); INSERT INTO books (Title,SeriesID,AuthorID) VALUES('The Fellowship of the Ring',1,1), ('The Two Towers',1,1), ('The Return of the King',1,1), ('The Sum of All Men',2,2), ('Brotherhood of the Wolf',2,2), ('Wizardborn',2,2), ('The Hobbbit',0,1);
+
Para validar que se ejecutó correctamente la creación de la base de datos revisa con lo siguiente:
También podemos ver el contenido de books por ejemplo:
+
DESCRIBE books;
+
El resultado sería:
+
+----------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+--------------+------+-----+---------+----------------+ | BookID | int(11) | NO | PRI | NULL | auto_increment | | Title | varchar(100) | NO | | NULL | | | SeriesID | int(11) | YES | | NULL | | | AuthorID | int(11) | YES | | NULL | | +----------+--------------+------+-----+---------+----------------+
+
Por último hagamos una consulta de prueba para estar seguros que todo funciona correctamente:
+
SELECT * FROM books;
+
+--------+----------------------------+----------+----------+ | BookID | Title | SeriesID | AuthorID | +--------+----------------------------+----------+----------+ | 1 | The Fellowship of the Ring | 1 | 1 | | 2 | The Two Towers | 1 | 1 | | 3 | The Return of the King | 1 | 1 | | 4 | The Sum of All Men | 2 | 2 | | 5 | Brotherhood of the Wolf | 2 | 2 | | 6 | Wizardborn | 2 | 2 | | 7 | The Hobbbit | 0 | 1 | +--------+----------------------------+----------+----------+
Antes de guardar index.js es probable que veas 2 errores en el código, y si no lo remarca tu editor, no te preocupes aquí te digo que el código no va a funcionar.
+
Lo que sucede es la forma en que estamos añadiendo la función que resuelve /test_db, si podemos simplificar la función tendríamos lo siguiente:
+
(request, response, next) => {})
+
La anterior es lo que ya vimos como una función anónima, pues al momento no le hemos generado un nombre, y como ya hemos visto es la forma en que podemos resolver las funciones dentro de nuestras rutas.
+
Ahora debemos entender un poco como funcionan las Bases de Datos. Independientemente de donde nos conectemos sea en la misma computadora con localhost o a otro servidor, el tiempo que tarda la base de datos en responder es desconocido, y esto es importante que lo entendamos pues al conectarte a cualquier otro espacio no controlamos este tiempo. Esto pueden ser segundos o milisegundos pero al final no es un tiempo constante.
+
Cuando ejecutamos un programa la secuencia de instrucciones del código son secuenciales, pero el problema de ciertas llamadas a conexiones externas al no controlar el tiempo de respuesta puede provocar que se tarde mucho en responder.
+
En programación general, simplemente escribimos el código y no nos preocupamos por esto pero ya en un desarrollo más avanzado como desarrollo web y conexión entre servidores veremos que tenemos que adecuarnos a otro estilo de programación.
+
Aquí es donde surgen las funciones asíncronas las cuales su objetivo es específicamente resolver el conflicto de tiempo de espera con código que puede tardar mucho tiempo, algunas acciones que pueden generar un tiempo de espera por mencionar algunas sería:
+
+
Conexión o ejecución de comandos en una BD.
+
Escritura/lectura de un archivo
+
Conexión a internet
+
+
Pueden existir más casos pero al menos estos son los más representativos.
+
Ahora, para definir una función asíncrona es muy sencillo, vamos a actualizar nuestra función a lo siguiente:
+
async (request, response, next) => {})
+
Dentro de esto vamos a tener que agregar la palabra reservada async que le permitirá a nuestra función saber que puede ejecutar código asíncrono.
+
Si volvemos el código completo de nuestra ruta se vería de la siguiente manera:
Veremos entonces, que ahora sí se resuelve el conflicto, y este venía específicamente en la sección:
+
await conn.query("SELECT * FROM books");
+
Aquí estamos haciendo uso de la palabra reservada await, para poder usar el await, es necesario hacer uso del async, por tanto se complementan la una a la otra en el formato de async/await.
+
Si bien al usar el async, estamos diciendo que nuestra función es asíncrona, puede o no utilizarse el await internamente.
+
Como resultado y conclusión te recomiendo que todas las funciones de tu proyecto las definas como async para que en cualquier momento puedas hacer uso del await si lo necesitas, y en caso de que no, pues tu código ya estaría preparado para ello.
+
También debemos resaltar que sucede cuando el código llega a la línea del await, lo que pasa es que como la palabra reservada nos dice, el código entra en un modo espera a que se ejecute la instrucción externa, en este caso el query a la base de datos y hasta que no termine podemos continuar donde nos quedamos.
+
Esto nos permite mantener un código secuencial para ello. Algo que mencionaremos pero no implementaremos, es que antes de que existiera async/await utilizábamos algo conocido como promesas y callbacks, en ocasiones se puede utilizar pero el código se vuelve más difícil de leer.
+
Sin embargo todo el código en formato de promesas puede convertirse en async/await y viceversa.
+
Ahora sí vamos a guardar el archivo index.js. Vamos a ver en el navegador el resultado:
+
+
Éxito, hemos conectado nuestro proyecto con nuestra base de datos y ahora podemos ejecutar comandos a la base de datos.
+
Siguientes pasos:
+
Lo siguiente es que pases tu conexión de este laboratorio a tu modelo usando la arquitectura Modelo-Vista-Controlador.
+
También necesitas cuidar mucho como proteger la información, pues existen varios puntos de seguridad a cuidar, el más elemental es la inyección de sql, la cual te pido investigues y para validar el conocimiento de este laboratorio, tomes conciencia de la implicación que tendría.
+
Hemos abierto una puerta a mucho poder dentro de nuestras aplicaciones web, pero eso también nos lleva a tener que proteger mucho la información que estamos obteniendo.
+
La documentación que puedes consultar para entender que otros comandos contiene la librería de mariadb puedes encontrarlos desde aquí.
Con lo que hemos aprendido en prácticas anteriores hemos conocido nuestro front-end y nuestro back-end. Hemos logrado crear una estructura que nos permite trabajar con nuestros proyectos y más allá hemos logrado conectarnos con una fuente de datos para transmitir información. Hasta el momento hemos cubierto las piezas de lo que significa el desarrollo web, pero ahora es momento de empezar a pegar todo lo que hemos aprendido y realmente empecemos a crear proyectos extraordinarios.
+
En esta práctica trabajaremos en el manejo de sesión de usuario, que como vimos en lecciones anteriores hace uso de las cookies para manejar la sesión. Por sí sola la sesión no nos sirve de nada, necesitamos haber establecido un proceso para ellos, ya que empiezas a conocer sobre UML y su notación, vamos a empezar a trabajar con Diagramas de Secuencia para definir nuestros casos de Uso.
+
+
Dentro de este diagrama podemos identificar el caso de uso de registrar usuario.
+
Dependiendo de la forma en que trabajemos podemos separar este diagrama en partes o trabajarlo todo como uno solo tal y como viene en el ejemplo, un buen ingeniero de software no piensa en los detalles de cuantos o como mostrarlo, sino que el diagrama explique tal cual como está construido el elemento de software.
+
Vamos a tomar como referencia nuestro laboratorio de MVC para practicar, te dejo una copia que puedes descargar para comenzar con la plantilla que ya teníamos.
En primer lugar vamos a establecer el camino a seguir cuando el usuario sigue la ruta /registro con un GET. Como ya hemos visto el uso del GET en una url, por lo general hace que carguemos una vista o un EJS.
+
Vamos a nuestra carpeta de views y dentro de usuarios ya teníamos definido registro.ejs pero no agregamos ningún código de EJS. Vamos a agregarlo:
Usaremos este mismo ejs para cargar más adelante el login, observa como utilizamos el EJS para diferenciar entre un usuario ya existente y uno que apenas va comenzando.
+
Antes de correr nuevamente la aplicación en el navegador, nos hace falta agregar la función en usuarios.controller.js y actualizar usuarios.routes.js.
+
Para hacerte entender el diagrama y la construcción del mismo, voy a obviar algunos pasos para obligarte a que lo estés revisando y sepas donde colocar el método, como este es el primero te daré una ayuda. En usuarios.controller.js agrega lo siguiente:
Nota: Para la siguiente sección asegúrate que tu base de datos este corriendo y este bien configurada como vimos en el laboratorio anterior.
+
+
Ahora bien, ya tenemos nuestra vista para que el usuario introduzca información, ahora es momento de obtener dicha información a través del form que contiene nuestro EJS.
+
Del lado del EJS, no necesitamos agregar nada más, pero ahora debemos recibirlo en nuestra ruta y controlador correspondiente, vamos a añadir el código, recuerda verificar el diagrama de secuencia para saber exactamente en donde.
Vamos nuevamente a nuestro navegador y vamos a intentar cargar nuevamente la vista y agreguemos la información, al enviar el formulario deberíamos obtener:
+
En el navegador veremos:
+
+
Y en la consola deberíamos ver:
+
0|index | user 0|index | demo
+
Muy bien, logramos subir nuestros datos al servidor.
+
Ahora vamos a ver un paso que no hemos realizado hasta ahora y es como definir nuestros modelos, dentro haciendo la conexión a la Base de Datos.
+
Si tomamos como base nuestro ya definido usuarios.model.js, teníamos lo anterior:
Aquí definimos una función para manejar los datos, pero idealmente una clase de base de datos y un modelo incluyen las Entidades y las funciones que ejecutan. Las entidades no son más que las propiedades que contienen nuestras tablas de la base de datos, en otras palabras el resultado de los queries que vamos a obtener y por lo general van alineados a las tablas, en queries complejos puedes crear entidades complejas que incluyan más datos que una tabla normal, pero una de las razones de "duplicar la información", es que necesitamos protegernos ante cualquier ataque a la base de datos por lo que evitamos responder con los datos tal cual como vienen de la misma.
+
Aquí es donde podemos hacer uso de las clases y objetos de javascript, creando algo como lo siguiente:
+
exports.User = class { //Constructor de la clase. Sirve para crear un nuevo objeto, y en él se definen las propiedades del modelo constructor(my_username, my_name, my_password) { this.username = my_username; this.name = my_name; this.password = my_password; } //Este método servirá para guardar de manera persistente el nuevo objeto. async save() { try { const connection = await db(); const result = await connection.execute( `INSERT INTO users (username, name, password) VALUES (?, ?, ?)`, [this.username, this.name, this.password] ); await connection.release(); return result; } catch (error) { throw error; // Re-throw the error for proper handling } } //Este método servirá para buscar un usuario por username //Es estático ya que a diferencia de save(), el primero se guarda al crear un usuario siempre, pero en este segundo podemos buscar un usuario sin crear un nuevo objeto usuario. static async findUser(username) { try { const connection = await db(); const result = await connection.execute('Select * from users WHERE username = ?', [username]); await connection.release(); return result; } catch (error) { throw error; // Re-throw the error for proper handling } } }
+
Ahora verás que dentro de save() y findUser() hacemos referencia a una variable db que no existe, y esto es por que la configuración de nuestra bd la dejamos en el archivo index.js, idealmente necesitamos separar la configuración en un archivo aparte, por lo que dentro de una nueva carpeta del proyecto a la que llamaremos utils crearemos el archivo database.js.
+
La carpeta utils contiene por lo general archivos de configuración o archivos globales que se utilizan a través de todo el proyecto. Poco a poco ahondaremos más en esta carpeta.
+
+
Dentro de database.js vamos a abstraer la conexión que ya teníamos de la base de datos en index.js
Las primeras 2 partes son las mismas que tenemos en el index.js para la configuración, pero para exportar la función observa que definimos una función asíncrona para facilitarnos la conexión y en caso de tener algún error siempre es importante regresar el error, esto para casos alternativos como el de nuestro diagrama de secuencia.
+
Ahora ya tenemos la conexión de nuestro modelo con el archivo de configuración de la base de datos. Como mención especial, logramos ejecutar el código manejando errores, y también hacemos uso de await connection.release();, esto para liberar la conexión al momento de terminar, ya que si bien en los casos normales se borra recuerda que tenemos un número limitado de conexiones, así que hay que estar seguros de eliminar la conexión una vez que dejamos de usarla.
+
Por último no olvide agregar o importar la configuración hasta arriba en tu modelo:
+
const db = require('../utils/database.js');
+
Ahora deberemos actualizar nuestro controlador con lo siguiente para poder registrar el usuario
+
module.exports.post_registro = async(req,res) =>{ try { const username = req.body.username; const name = req.body.name; // Assuming you have a 'name' field in the request body const password = req.body.password; const user = new model.User(username, name, password); const savedUser = await user.save(); res.status(201).redirect("/usuarios/login"); } catch (error) { console.error(error); res.status(500).json({ message: "Error registering user!" }); // Idealmente se crea una plantilla de errores genérica } }
+
Antes de continuar, nos hace falta algo, ya tenemos el software, pero no hemos declarado nuestra base de datos ni la tabla dentro de la misma.
+
CREATE DATABASE IF NOT EXISTS users_test; USE users_test; CREATE TABLE IF NOT EXISTS users ( ID INT NOT NULL PRIMARY KEY AUTO_INCREMENT, username VARCHAR(100), name VARCHAR(100), password VARCHAR(100) );
+
Ya que hemos guardado nuestra base de datos, guarda todo y vamos a probar en el navegador:
+
+
Si el resultado es correcto entonces, deberíamos ver la url de login
+
+
Pero más importante aún, debemos revisar nuestra base de datos:
+
+
Lo hemos conseguido, tenemos una vista con EJS, que manda datos y los guarda en la base de datos usando el modelo. Todo un uso completo de la arquitectura MVC.
Ahora que hemos alcanzados nuevos conocimientos viene la apertura a nuevos retos, y como podrás ver tenemos uno muy importante que cubrir, la contraseña, literalmente.
+
Como en todo buen sistema lo ideal es tener un sistema de encriptación adecuado, no es objetivo del curso que empieces a crear tu propio algoritmo de encriptación y si tu enfoque no es en ciberseguridad, te recomiendo que uses uno de los que ya existen en la industria pues además de ser ya probados ayudan estandarizar uno de los puntos más importantes de nuestro sistema.
+
Como ya lo hemos echo previamente, haremos uso de una nueva librería, está nos ayudará a este paso de encriptación, para instalarla haremos uso de la instrucción que ya conocemos:
+
npm i bcryptjs
+
Esta librería no requiere que la configuremos desde el index.js, más bien podemos usarla desde nuestro modelo directamente:
+
Primero vamos a declarar la librería en la parte superior debajo de la declaración del archivo de configuración de la base de datos:
+
const db = require('../utils/database.js'); const bcrypt = require('bcryptjs');
Aquí estamos generando una contraseña a través de una llave hash, el 12, que estamos añadiendo es el número de rondas de lo que se conoce como salteo, y quiere decir que 12 veces se agrega información aleatoria a la contraseña para que sea más difícil de romper. Este curso no cubre los conceptos teóricos de ciberseguridad, si tienes más duda del algoritmo de hash y el salteo pregunta a tu profesor o investiga en internet.
+
Así entonces debemos actualizar la función save() a lo siguiente:
Si volvemos a guardar el formulario deberíamos ver algo como lo siguiente:
+
+
He usado la misma contraseña demo, e incluso si tu haces lo mismo verás que el resultado es diferente, a esto nos referimos con añadirle sal a la contraseña creando un resultado único aunque utilicemos la misma contraseña, veamos el ejemplo añadiendo una más con el mismo demo.
+
+
Observa que aunque empieza similar, eventualmente cambia, esto se debe a varias razones, el tipo de contraseña, que es la misma, el algoritmo que estamos ejecutando y el número de rondas de salteo. Aquí no hay una fórmula perfecta, en ciberseguridad siempre podemos añadir más, pero ese extra es procesamiento añadido así que ten cuidado en no exceder demasiado.
+
Hasta aquí hemos cubierto todo nuestro diagrama de secuencia, agrega los casos alternos para completar el ejercicio de la práctica.
Así cuando agreguemos un nuevo usuario o entremos directamente a la url veremos lo siguiente:
+
+
Como el código del ejs ya estaba preparado no necesitamos realizar más adecuaciones en el GET.
+
Ahora vamos al POST, la función do_login, aquí queremos validar el usuario y la contraseña introducidas por el usuario. Por tanto vamos a agregar lo siguiente:
Si guardamos todo y ejecutamos el proceso veremos nuestro login y en el caso correcto debería cargar la siguiente vista:
+
+
Aquí seguimos un paso a paso del inicio de sesión:
+
+
Buscamos al usuario en la base de datos.
+
Si NO encontramos usuario regresamos al inicio de sesión.
+
Comparamos las contraseña con el método compare de bcrypt, que recibe una contraseña plana y el hash.
+
Si NO hacen match las contraseñas regresamos al inicio de sesión.
+
Si encontramos usuario manejamos la sesión del usuario en el servidor y cargamos la vista de logged.
+
+
Hemos logrado iniciar la sesión con el usuario de nuestra base de datos, pero ¿qué pasa si regresamos a /usuarios/login?. Volveremos al formulario de inicio de sesión, esto no es un comportamiento adecuado, pues el servidor ya está manejando nuestra sesión, por lo que debemos actualizar render_login a lo siguiente:
Ahora bien, por facilidad, vamos a crear una nueva ruta para /usuarios que se llame /logged. Este paso lo haré directo pues ya debes estar acostumbrado a crear rutas y controladores:
Aquí no necesitamos volver a hacer el login, pues ya tenemos iniciada la sesión, peri sí necesitamos buscar al usuario.
+
Intenta a iniciar sesión nuevamente, y accede a nuestra nueva url:
+
+
Esto funcionará mientras la sesión esté activa, pero si reinicias el servidor, e intentas entrar serás redirigido al inicio de sesión, pero ahora necesitamos distinguir cuando hemos iniciado sesión y cuando no de una manera más sencilla.
Como ya mencionamos previamente la carpeta utils del proyecto contiene archivos y funciones que son utilizadas en todo el proyecto no solo por un solo tipo de archivo (vistas, controladores,modelos, rutas). Y como hemos platicado de prácticas anteriores tenemos middlewares, bloques de código o funciones que se ejecutan antes de realizar la función principal.
+
Este es el mejor momento de usar un middleware ya que queremos una función genérica que verifique nuestra sesión con el servidor, para ello vamos a crear dentro de la carpeta utils un archivo que llamaremos is-auth.js el cual deberá contener lo siguiente:
Is-auth está diseñado para verificar la sesión, si por alguna razón la validación falla redirige directamente al login, pero en caso de que no, permite pasar.
+
Para proteger nuestras rutas, lo que debemos hacer es primero declarar el nuevo archivo:
Nuevamente, al llamarse /logged, se ejecutaría primero el middleware verificando la sesión, y en caso de que funcione entonces pasaría a get_logged, pero en caso de que no se quedaría en el middleware redirigiendo al loggin.
+
Para sistemas administrativos esto es ideal por que te permite bloquear todas las urls de acceso no autorizado e incluso personalizar los mensajes de error para que el usuario sepa en donde está.
+
Todavía existen varias optimizaciones que podemos realizar como agregar un token de seguridad para ataques CSRF, o el manejo de errores para el inicio de sesión y que el usuario sepa que algo salió mal, incluso mejoras sustanciales al código para hacerlo más genérico, como siempre es cuestión de práctica y que te vayas adaptando a los nuevos conceptos.
Con lo que hemos aprendido en prácticas anteriores hemos consolidado nuestro conocimiento en backend para comenzar a proteger la información de los usuarios de nuestro sitio web. Hemos logrado crear una estructura para evitar que la usuarios no registrados puedan entrar al sitio web utilizando middlewares y que protegen cada una de nuestras rutas. Hasta el momento hemos cubierto las primeras etapas de la seguridad del desarrollo web, pero ahora es momento de empezar a reforzar aún más la seguridad para mantener la integridad del sitio y de la información.
+
En esta práctica trabajaremos en el manejo de roles y privilegios, que como vimos en lecciones anteriores ya hemos logrado mantener una sesión en el servidor y saber si un usuario se encuentra logueado o no. Pero ahora ¿qué necesitamos para que usuarios con diferentes privilegios puedan acceder a sus vistas correspondientes?.
+
+
Dentro de este diagrama podemos identificar el modelo de entidad relación que vamos a utilizar en este laboratorio.
+
Vamos a tomar como referencia nuestro laboratorio de MVC para practicar, te dejo una copia que puedes descargar para comenzar con la plantilla que ya teníamos.
En primer lugar vamos a crear las tablas de nuestra base de datos que están en nuestro modelo a través del siguiente script:
+
USE users_test; CREATE TABLE IF NOT EXISTS asigna ( username varchar(20) NOT NULL, idrol int(11) NOT NULL, created_at timestamp NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci; CREATE TABLE IF NOT EXISTS posee ( idrol int(11) NOT NULL, idpermiso int(11) NOT NULL, created_at timestamp NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci; CREATE TABLE IF NOT EXISTS privilegio ( id int(11) NOT NULL, permiso varchar(40) NOT NULL, created_at timestamp NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci; CREATE TABLE IF NOT EXISTS rol ( id int(11) NOT NULL, nombre varchar(40) NOT NULL, created_at timestamp NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci; CREATE TABLE IF NOT EXISTS tiene ( id int(11) NOT NULL, username varchar(20) NOT NULL, created_at timestamp NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci; CREATE TABLE IF NOT EXISTS tropa ( id int(11) NOT NULL, clase varchar(50) NOT NULL, nivel int(11) NOT NULL, imagen varchar(255) NOT NULL, vida int(11) NOT NULL, ataque int(11) NOT NULL, created_at timestamp NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci; CREATE TABLE IF NOT EXISTS usuario ( username varchar(20) NOT NULL, nombre varchar(200) NOT NULL, password varchar(400) NOT NULL, created_at timestamp NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci; -- -- Índices para tablas volcadas -- -- -- Indices de la tabla `asigna` -- ALTER TABLE `asigna` ADD PRIMARY KEY (`username`,`idrol`), ADD KEY `idrol` (`idrol`); -- -- Indices de la tabla `posee` -- ALTER TABLE `posee` ADD PRIMARY KEY (`idrol`,`idpermiso`), ADD KEY `idpermiso` (`idpermiso`); -- -- Indices de la tabla `privilegio` -- ALTER TABLE `privilegio` ADD PRIMARY KEY (`id`); -- -- Indices de la tabla `rol` -- ALTER TABLE `rol` ADD PRIMARY KEY (`id`); -- -- Indices de la tabla `tiene` -- ALTER TABLE `tiene` ADD PRIMARY KEY (`id`,`username`,`created_at`), ADD KEY `id` (`id`), ADD KEY `username` (`username`); -- -- Indices de la tabla `tropa` -- ALTER TABLE `tropa` ADD PRIMARY KEY (`id`); -- -- Indices de la tabla `usuario` -- ALTER TABLE `usuario` ADD PRIMARY KEY (`username`); -- -- AUTO_INCREMENT de las tablas volcadas -- -- -- AUTO_INCREMENT de la tabla `privilegio` -- ALTER TABLE `privilegio` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT de la tabla `rol` -- ALTER TABLE `rol` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- AUTO_INCREMENT de la tabla `tropa` -- ALTER TABLE `tropa` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -- -- Restricciones para tablas volcadas -- -- -- Filtros para la tabla `asigna` -- ALTER TABLE asigna ADD CONSTRAINT asigna_ibfk_1 FOREIGN KEY (username) REFERENCES usuario (username), ADD CONSTRAINT asigna_ibfk_2 FOREIGN KEY (idrol) REFERENCES rol (id); -- -- Filtros para la tabla `posee` -- ALTER TABLE posee ADD CONSTRAINT posee_ibfk_1 FOREIGN KEY (idrol) REFERENCES rol (id), ADD CONSTRAINT posee_ibfk_2 FOREIGN KEY (idpermiso) REFERENCES privilegio (id); -- -- Filtros para la tabla `tiene` -- ALTER TABLE tiene ADD CONSTRAINT fk_tropas FOREIGN KEY (id) REFERENCES tropa (id), ADD CONSTRAINT fk_usuarios FOREIGN KEY (username) REFERENCES usuario (username);
+
Excelente!, ahora como toda base de datos necesitamos llenarla de información y para ello vamos a utilizar el siguiente script:
+
-- -- Volcado de datos para la tabla `usuario` -- INSERT INTO `usuario` (`username`, `nombre`, `password`, `created_at`) VALUES ('andrea', 'Andrea Medina Rico', '$2a$12$XTN3lOvdUVferZxggiaAJOLTqV4NPsxOEtyYgQGLjZAU1YGbVPvqC', '2024-03-11 18:48:01'), ('antonio', 'José Antonio López Saldaña', 'arteycultura', '2024-03-07 18:15:12'), ('kevin', 'Kevin Josué Martínez Leyva', 'tony123', '2024-03-11 18:26:13'), ('nico', 'Nicolás Hood Figueroa', '$2a$12$1JQ3yh2yev2TTs5jpDkz5uUDHSj2nkG7tv32T7evN9cM44V8DNMR2', '2024-03-11 18:46:42'), ('sebas', 'Sebastián Colín De La Barreda', '$2a$12$7Ij6iZ4wV4VziolRU/T5OOub.gAu0zSP.8Y7PTqZalosaaSkX8kyO', '2024-03-11 18:49:07'); -- -- Volcado de datos para la tabla `privilegio` -- INSERT INTO `privilegio` (`id`, `permiso`, `created_at`) VALUES (1, 'crear_tropa', '2024-03-12 17:58:45'), (2, 'ver_clan', '2024-03-12 17:58:45'); -- -- Volcado de datos para la tabla `rol` -- INSERT INTO `rol` (`id`, `nombre`, `created_at`) VALUES (1, 'miembro', '2024-03-12 17:58:09'), (2, 'colider', '2024-03-12 17:58:09'), (3, 'lider', '2024-03-12 17:58:15'); -- -- Volcado de datos para la tabla `tropa` -- INSERT INTO `tropa` (`id`, `clase`, `nivel`, `imagen`, `vida`, `ataque`, `created_at`) VALUES (1, 'Bárbaro', 1, 'https://static.wikia.nocookie.net/clashofclans/images/8/87/Avatar_Barbarian.png', 10, 9, '2024-03-07 18:06:03'), (2, 'arquera', 1, 'https://static.wikia.nocookie.net/clashofclans/images/6/68/Avatar_Archer.png', 8, 12, '2024-03-07 18:43:28'), (3, 'Pekka', 1, 'https://static.wikia.nocookie.net/clashofclans/images/5/54/P.E.K.K.A_info.png', 180, 300, '2024-03-07 19:06:10'), (4, 'Montapuercos', 1, 'https://pm1.aminoapps.com/6307/1debaffe6fcbe0764f01a6fd4f4085832510f31d_hq.jpg', 50, 40, '2024-03-07 19:12:24'); -- -- Volcado de datos para la tabla `asigna` -- INSERT INTO `asigna` (`username`, `idrol`, `created_at`) VALUES ('andrea', 3, '2024-03-12 17:59:54'), ('nico', 1, '2024-03-12 17:59:54'), ('sebas', 2, '2024-03-12 18:00:06'); -- -- Volcado de datos para la tabla `posee` -- INSERT INTO `posee` (`idrol`, `idpermiso`, `created_at`) VALUES (2, 2, '2024-03-12 18:00:55'), (1, 1, '2024-03-12 18:00:44'), (3, 2, '2024-03-12 18:00:44'); -- -- Volcado de datos para la tabla `tiene` -- INSERT INTO `tiene` (`id`, `username`, `created_at`) VALUES (1, 'antonio', '2024-03-07 18:19:20');
Nota: Para la siguiente sección asegúrate que tu base de datos este corriendo y este bien configurada como vimos en el laboratorio anterior.
+
+
Para poder obtener los permisos del usuario, tenemos que agregar a nuestro usarios.model.js el método getPermisos para obtener la información de nuestra base de datos, por lo que agrega ahí el siguiente método:
+
//Este método servirá para obtener los permisos de un usuario static async getPermisos(username) { try { const connection = await db(); const result = await connection.execute('Select permiso FROM privilegio pr, posee po, rol r, asigna a, usuario u WHERE u.username = ? AND u.username = a.username AND a.idrol = r.id AND r.id = po.idrol AND po.idpermiso = pr.id', [username]); await connection.release(); return result; } catch (error) { throw error; // Re-throw the error for proper handling } }
+
¿Y cuándo lo ejecutamos? Esta información debe ser obtenida en etapas tempranas en las que un usuario ingresa al sistema por lo que lo mejor es hacerlo cuando el usuario hace login, modifica el el controlador do_login de la siguiente manera:
+
module.exports.do_login = async (req, res) => { try { const usuarios = await model.User.findUser(req.body.username) if (usuarios.length < 1) { res.render("usuarios/registro", { registro: false }); return; } const usuario = usuarios[0]; //const doMatch = await bcrypt.compare(req.body.password, usuario.password); const doMatch = (req.body.password == usuario.password) ? true : false if (!doMatch) { res.render("usuarios/registro", { registro: false }); return; } // Se agrega método para obtener el permiso del usuario const permiso = await model.User.getPermisos(usuario.username); if (permiso.length == 0) { req.session.error = "Usuario y/o contraseña incorrectos"; res.render("usuarios/registro", { registro: false }); return; } /* Cuando tengamos la confirmación del permiso se guarda la sesión y los permisos en nuestro req.session */ req.session.permisos = permiso; req.session.username = usuario.username; req.session.isLoggedIn = true; console.log(req.session) res.render('usuarios/logged', { user: usuario }); } catch (error) { res.render("usuarios/registro", { registro: false }); } }
+
Mostrar/ocultar elementos de la interfaz gráfica dependiendo del permiso del usuario
+
Vamos a continuar para generar una interfaz gráfica dinámica de acuerdo al permiso del usuario, modifica el último apartado de do_login en el render de la siguiente manera:
Por último vamos a adecuar un poco el login para que nuestros datos funcionen con los de la base de datos, en primer lugar actualiza el query de findUser para usar la tabla de usuario y no la de user del laboratorio anterior en usuarios.model.js:
+
Select * from usuario WHERE username = ?
+
Ahora bien si vamos al navegador y hacemos login con el siguiente usuario:
+
user: andrea pass: $2a$12$XTN3lOvdUVferZxggiaAJOLTqV4NPsxOEtyYgQGLjZAU1YGbVPvqC
+
Debido a que insertamos a Andrea directamente no conocemos su contraseña así que veremos que sucede cuando no aplicamos bien la autenticación de la contraseña pero efectuamos bien el método.
+
Sí hacemos login desde el navegador veremos como resultado que el permiso asignado a andrea es visible.
+
+
Proteger las rutas dependiendo del permiso del usuario
+
Pero ahora ¿qué pasa si queremos bloquear toda una ruta, es decir, si el usuario intenta acceder desde el navegador a la ruta aunque este logueado queremos que verifique si tiene el permiso o no para acceder, por lo que en nuestras rutas, como ya lo vimos en laboratorios pasados, vamos a agregar un middleware por cada permiso
+
Crea un nuevo archivo dentro de utils llamado can-create.js que será el middleware para verificar este permiso.
+
module.exports = (request, response, next) => { let canCreate = false; for (let permiso of request.session.permisos) { if (permiso.permiso == 'crear_tropa') { canCreate = true; } } if (canCreate) { next(); } else { return response.redirect('/usuarios/logout'); } }
+
Para el otro permiso que tenemos disponible en nuestra base de datos crea un archivo que se llame can-view.js que será el middleware para el permiso.
+
module.exports = (request, response, next) => { let canView = false; for (let permiso of request.session.permisos) { if (permiso.permiso == 'ver_clan') { canView = true; } } if(canView) { next(); } else { return response.redirect('/usuarios/logout'); } }
+
Si te fijas en nuestros middlewares estamos usando una nueva ruta que es logout, agrégalo en tu archivo de usuarios.routes.js:
Perfecto ya tenemos toda la estructura que necesitamos para validar un permiso, pero falta que cuando el usuario se registre se le asigne un permiso, por lo que, vamos a modificar usuarios.model.js el método de save() de la siguiente manera:
Bien prueba tu laboratorio! Registra un usuario y verifica que se haya agregado el permiso del usuario.
+
Como por ahora estamos asignando de manera directa que el usuario tenga el permiso de crear tropa solamente, el usuario no puede acceder a usuarios/obtener_usuarios y lo mandará al logout.
+
Prueba también una vez logueado la ruta de usuarios/editar_usuario, a la cual, el usuario si tendrá acceso.
+
¿Estás listo? No esperes más experimenta con otro permiso, crea su middleware correspondiente y a haz las validaciones correspondientes tanto a nivel ruta como a nivel de interfaz gráfica.
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/docs/backend/node/tutorials/intro_web/Lab1HTML/index.html b/deprecated/old-docs-build/docs/docs/backend/node/tutorials/intro_web/Lab1HTML/index.html
new file mode 100644
index 0000000..62e0faf
--- /dev/null
+++ b/deprecated/old-docs-build/docs/docs/backend/node/tutorials/intro_web/Lab1HTML/index.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+Intro a Desarrollo Web con HTML | TC2005B
+
+
+
+
+
+
+
El desarrollo web se basa en programación por etiquetas, estas vienen de la forma.
+
<tag></tag>
+
Esta sintaxis fue creada por el creador de la WWW Tim Berners Lee, y se ha vuelto el modelo estándar de desarrollo web que los navegadores hoy en día utilizan para todo lo que hoy en día se encuentra disponible en Internet.
+
Si bien los navegadores web utilizan el HTML como estándar, no es el único protocolo de comunicación para obtener información de un servidor.
+
+
¡Recuerda! En desarrollo web la unidad mínima de trabajo es una arquitectura Cliente-Servidor, desde la cual el cliente funciona como nuestro navegador y desde aquí empezaremos a hacer peticiones a un servidor del cual podemos obtener archivos estáticos (html,css,js,jpg,png,etc.) o resultados procesados por la entidad de nuestro servidor.
+
+
Es muy importante que desde ahora tomes en cuenta en donde se encuentra la información y en que momento para que puedas resolver errores, es decir si estoy viendo un HTML ¿quién lo está cargando?, ¿el servidor me ha devuelto una respuesta?, preguntas como las anteriores nos ayudan a ubicar y entre mas conceptos añadimos más fácil se vuelve añadir más complejidad.
+
Estándares adicionales de transmisión de información HTML, XML, JSON
+
Para entender este punto debemos localizarnos en las capas del modelo de redes OSI e identificar el protocolo TCP/IP.
+
Este protocolo envía paquetes de información entre el cliente y el servidor y esto nos genera lo que conocemos como requests.
+
Los requests no son más que llamadas url que nosotros escribimos en el navegador o que mandamos llamar con cada interacción que tenemos en una página web.
+
Es entonces que lo que vamos a procesar en el servidor son estas urls, y todas aquellas que no se encuentren son lo que conocemos como el famoso 404 not found, más adelante veremos estos códigos de error en profundidad.
+
El siguiente paso al hacer un requests es que debemos saber que nos debe regresar algo, incluso si ocurre un error, y lo ideal es que nos devuelva en un formato estándar que podamos entender.
Otro estándar utilizado en la industria que si bien es más viejo más no es obsoleto es XML. Este estándar tiene un cierto parecido con HTML en que utiliza un sistema de etiquetas, más la diferencia con HTML es que este se centra en modelar información general, que es definida por los ingenieros de software.
Como puedes ver HTML tiene su sintaxis específica como si fuera un lenguaje de programación, mientras que XML puede modelar cualquier tipo de dato con la diferencia que esto no será visible en el navegador.
+
Por último el otro tipo de estándar más usado es conocido como JSON, este estándar es más nuevo y busca ser una evolución a XML en el sentido de reducir el duplicado con el uso de etiquetas manejando la información en una estructura de datos como la pudiéramos encontrar en otros lenguajes de programación: listas, objetos, diccionarios o tablas hash. Ver estándar JSON
Existen muchos otros estándares de manejo de información para TCP/IP que puedes encontrar por ejemplo uno muy popular últimamente es GraphQL, cada uno tiene sus reglas y veremos algunos durante el curso.
Como ya mencionamos HTML utiliza las etiquetas para poder funcionar, para poder utilizarlas tenemos etiquetas que abren y cierran o etiquetas que cierran en la misma.
+
<tag></tag> <!--Etiqueta que abre y cierra--> <tag/> <!-- Etiqueta que abre y cierra al declararse -->
+
La diferencia entre cada una es que la primera admite contenido adentro de la misma, mientras que la segunda solo utiliza la etiqueta para manejar su información sin utilizar contenido interno.
+
Es así que la etiqueta más básica en HTML podríamos manejarla como la siguiente
Una vez que tenemos una etiqueta básica veremos que dentro de los símbolos de la etiqueta que abre es decir, dentro de <> podemos tener atributos, estos dependerán de cada etiqueta, algunos son globales y otros son específicos.
Aquí estamos declarando el atributo id cuyo valor será el de "family".
+
Los atributos globales que toda propiedad de HTML puede tener son el id y el class.
+
+
El id como su nombre indica es un identificador único, y así como en los lenguajes de programación con las variables la idea es que solo exista 1 por archivo HTML, esto ayuda para hacer referencia específica a una etiqueta cuando usamos estilos CSS o para acceder a un valor si lo llamamos desde un archivo JS.
+
El class, es un atributo utilizado más por el estilo CSS, este lo veremos con más profundidad en próximos laboratorios.
+
+
Otro ejemplo de etiqueta en HTML usando el atributo id sería una tabla como la siguiente
Para ver algunos otro atributos podemos ver el siguiente ejemplo con la etiqueta a
+
<a href="https://html6.com" target="_blank" rel="nofollow" title="HTML6"> HTML Editor </a>
+
Las propiedades que tenemos es el href, target, rel,title. Primero entendamos que la etiqueta a sirve para formar un hipervínculo dentro de la página, es decir es un link que nos lleva a otro lugar.
+
+
El atributo href recibe la url de la página a donde queremos ir.
+
El atributo target es opcional y en este caso abre una nueva pestaña del navegador con la indicación _blank.
+
El atributo rel igualmente es opcional y por default una etiqueta a utiliza el nofollow, esto sirve para que en caso de los buscadores como Google cuando entran a revisar las páginas y detectan estas etiquetas crean un mapa de a que sitios lleva la página, esto ayuda a la página destino a posicionarse mejor en internet, existe todo un tema al respecto dentro de marketing y SEO (Search Engine Optimization) para que funcione correctamente.
+
El atributo title ayuda a mostrar al usuario un título si pasa por encima el cursor.
Además de las páginas como un todo HTML, lo más importante a manejar son los form o formularios, ya que estos son los que permiten guardar información una página web.
+
La estructura básica de un formulario completo es la siguiente
La etiqueta form recibe varios atributos importantes
+
<form action="/age.php" method="post"></form>
+
+
El action es la url que vamos a llamar y desde donde se subirá la información que vamos a enviar del formulario.
+
El method será el tipo de request que hará la url es decir podrá ser de tipo: GET,POST,PUT,DELETE.
+
+
+
Nota: Cuando llames la url, asegúrate que tu servidor pueda manejar la llamada, el error más común es hacer una llamada POST y que el servidor solo procese la url mediante un GET.
+
+
+
En el ejemplo de código la url es /age.php, esto funciona para servidores desarrollados en php, pero otro tipo de urls pueden ser de formato sin extensión como /age solamente.
+
+
Dentro del formulario observa que tenemos etiquetas de tipo input, todas estas etiquetas tienen gran variación de parámetros ya que nos permiten hacer campos de texto, listas, botones, entre otros. Investiga las posibilidades que puedes realizar con los input y podrás guardar cualquier tipo de información incluso archivos en tu servidor y tu base de datos.
Para este laboratorio vamos a simplificar un poco nuestra arquitectura y solo trabajaremos con rutas y controladores, pues vamos a crear un archivo público y no haremos conexión con ninguna fuente de información.
+
Empecemos configurando un proyecto desde 0, a estas alturas ya debes de saber como:
+
npm init -y
+
Previo a otras sesiones haremos uso del -y para evitar agregar los parámetros y que sea más rápida la ejecución del init.
+
Introduce los valores generales que necesites del proyecto para el archivo principal usaremos index.js.
+
+
+
Ahora vamos a instalar las librerías básicas que necesitamos para este proyecto.
+
npm i express npm i body-parser npm i multer
+
Al momento hemos usado la librería de express y el body-parser que no son nuevas para nosotros, la nueva librería que usaremos es la de multer.
+
Multer es una librería que nos permite manejar archivos dentro de nuestros formularios para poder subirlos al servidor, pero quizás te estés preguntando. ¿Por qué necesitamos hacer todo esto si tengo el input file disponible en mi formulario?. Si bien esto es correcto, al momento de subir al servidor veremos que esto no funciona, y de echo cuando analizamos la razón tiene todo un sentido de lógica.
+
Cuando manejamos un formulario simple por lo general estamos utilizando texto, por más complejo o grande que este sea siempre el texto será en cuestión de tamaño pequeño comparado con un archivo que tan solo en su base puede llegar a pesar más que el texto de un formulario.
+
Piensa en diferentes tipos de archivos, imágenes, videos, binarios, zips, entre otros si subiéramos de golpe esto al servidor tardaría mucho y se bloquearía nuestra interfaz como sucede en algunos sitios.
+
La magia de todo esto ocurre cuando "partimos" en pedazos nuestros archivos y los subimos poco a poco al servidor, aquí es donde entra multer que acepta cada uno de estos pedazos, reconstruye nuestro archivo y lo guarda en nuestro servidor.
+
Esta es la única manera a nivel teórica en como podemos subir archivos, librerías quizás existan otras pero dentro del mundo de nodeJs es la más común.
+
Vamos a cargar la base de nuestro index.js con lo siguiente:
+
const express = require('express'); const multer = require('multer'); var path = require('path'); const app = express(); const port = 5000; const log = console.log const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({extended: false})); app.use(express.static(path.join(__dirname, 'public'))); app.listen(port, () => { //server starts listening for any attempts from a client to connect at port: {port} console.log(`Now listening on port ${port}`); });
+
Un cambio que estamos haciendo es usar otro puerto para la ejecución del servidor en 5000.
+
Ya que estamos sirviendo la carpeta pública, vamos a crearla dentro del proyecto y dentro de ella vamos a crear un archivo index.html, con el siguiente contenido.
En el action vamos a definir la ruta que vamos a utilizar para subir nuestro archivo, en este caso será el /upload_file.
+
También vamos a definir el action del envío de formulario como POST.
+
Por último necesitamos definir el formulario como multi-parte esto para el envío de los archivos.
+
El multipart/form-data, es la forma que te comenté antes donde le decimos a nuestro formulario que parta en pedazos la petición para subir archivos, esto rompe en varios paquetes TCP/IP y los manda al servidor para hacer su trabajo, es entonces donde multer entra a reconstruir cada paquete y recuperar el archivo.
+
Si ejecutamos el servidor y accedemos a la ruta de index.html, nos deberá aparecer lo siguiente:
+
+
No es la mejor interfaz para nuestro proyecto pero funcionalmente servirá.
Ya que tenemos nuestro form armado y corriendo vamos además de una carpeta public, aquí vamos a guardar los archivos que subamos en nuestro form.
+
+
Como mencioné, no nos enfocaremos en una arquitectura completa para este laboratorio, pero al menos haremos el uso de rutas y controladores por facilidad. Para ello debemos crear a la altura de index.js otro archivo al que llamaremos index.controller.js,
+este archivo deberá contener de momento lo siguiente:
Aquí recuerda que estamos definiendo el POST del formulario de index.html y también la ruta del action /upload_file.
+
Si cargamos un archivo y damos clic en Upload deberemos ver algo como lo siguiente.
+
+
Ya tenemos la ruta preparada, ahora vamos con lo que necesitamos para el laboratorio.
+
Ahora bien, vamos a cargar el archivo que agregamos a nuestro proyecto, como ya mencionamos, usaremos multer para tomar el multipart de nuestro formulario y recibir el archivo.
+
Para ello necesitamos configurar en donde se guardará el archivo y con que nombre. Esto lo haremos de forma muy lineal con la siguiente configuración, dentro de nuestro archivo index.controller.js arriba de la función upload_file.
+
const multer = require('multer'); // Using Promise API const storage = multer.diskStorage({ destination: function (req, file, callback) { console.log("File Destination:", './public/'); // Log the destination path callback(null, './public/'); }, filename: function (req, file, callback) { console.log("Uploaded File:", req.body); // Log received form data return callback(null, file.originalname); } }); const upload = multer({ storage: storage }).array('file', 1);
+
La siguiente definición carga la librería de multer y especifica que el destino del archivo sea la carpeta pública que definimos hace unos pasos. Y para el archivo no haremos ningún cambio significativo, pasaremos el nombre que recibimos desde el inicio.
+
Ahora dentro de nuestra función de /upload_file vamos a sustituir el
La línea más importante de todo este código es la siguiente:
+
var upload = multer({ storage : storage }).array('file',1);
+
Aquí no solo llamamos a nuestra configuración de multer, sino que vamos a recibir un arreglo de archivos que vienen de el index.html y el string 'file' es el id que otorgamos en el formulario al input en su propiedad de name recuerda siempre esto ya que el primer error que se comete al estar aprendiendo en los formularios es definir estos ids.
+
Para nuestro caso solo vamos a subir un archivo pero multer nos permite agregar múltiples archivos desde la propiedad del file a partir de nuestro form. Te dejo esta configuración en caso de que en otros proyectos quieras trabajar con múltiples archivos, funciona prácticamente igual.
+
var pathDest = req.files[0].destination.slice(1)
+
En esta línea puedes ver como funciona el arreglo puesto que al llamar req.files[0] lo que estamos haciendo es llamar al archivo según hayamos subido y aunque sea 1 sabemos que el primero será el de la posición 0.
+
Nuevamente vamos a ejecutar nuestro servidor y si volvemos a probar el resultado será el mismo pero dentro de nuestro proyecto pasará lo siguiente.
+
+
Como puedes observar el archivo que hayamos puesto se ha subido correctamente a nuestra carpeta pública.
+
Como no hemos puesto ninguna limitación en cuestión de archivos realmente podemos subir lo que sea pero para efectos prácticos y que te quede más claro te recomiendo comiences con una imagen sea .jpg o .png.
El siguiente paso quizás sea un poco obvio pero es el punto de partida para lograr identificar las variaciones con las que estaremos trabajando en el laboratorio.
+
De momento tenemos cargada la imagen_prueba.png o en tu caso el archivo que hayas subido.
+
Teniendo activo nuestro servidor ¿Cómo podemos ver este archivo?. Siempre es importante que tengas visibilidad en como acceder un archivo particular.
+
Para esto debemos considerar donde se encuentra guardado nuestro archivo y para ello olvida las rutas absolutas del sistema en donde este alojado el archivo puesto que estas rutas no son con las que trabajamos en el proyecto.
+
Las rutas que utilizamos son las rutas relativas y estas se construyen a través de las URL que vamos generando. Por default la carpeta public expone los archivos dentro de esta carpeta y por lo general son imágenes genéricas, templates de html, css, archivos js entre otros.
+
Si quiero acceder a un archivo dentro de esta carpeta basta con que agregue {{dominio}}/{{ruta_desde_public}}
+
http://localhost:5000/imagen_prueba.png
+
En mi caso el resultado en el navegador es el siguiente:
En el paso anterior manejamos los archivos estáticos, pero cuando trabajamos con subida de archivos lo ideal es que estos archivos no queden expuestos, y regresamos a lo mismo, todo en la carpeta public queda expuesto.
+
Otro riesgo que corremos al colocar todo en public es que si trabajamos con un repositorio este comenzará a crecer y podemos enfrentarnos a alcanzar el límite de tamaño del repositorio que normalmente va al rededor de los 2GB.
+
Lo ideal en estos casos es que vayamos creando una carpeta fuera del repositorio y desde ahí manejarlo.
+
Para el laboratorio simplemente lo haremos fuera de la carpeta public pero lo haremos dentro del proyecto.
+
Nota: Recuerda tener siempre en cuenta el uso de los archivos en general, cada caso es diferente y debes estar preparado para el escenario correspondiente.
+
Para comenzar vamos a expandir nuestro index.html con un nuevo formulario debajo del que ya tenemos.
Con el resultado anterior observamos que la misma imagen queda arriba dentro de nuestra carpeta privada. Pero para acceder a ella es donde tenemos que empezar a localizar lo que pasa en nuestra aplicación.
+
Con lo que vimos de la carpeta public podemos acceder a este archivo pero la incógnita que nos queda es si hacer este proceso de la carpeta private es lo que necesito para proteger mis archivos, ¿Cómo doy acceso a ellos cuando se necesiten?
+
Para esto es que no podemos dar acceso público, pero podemos dar un acceso a través de una URL.
+
Este es el proceso de control ya que al dar acceso mediante URL podemos agregar tanto procesamiento adicional al archivo en caso de ser necesario o en su defecto protegerlo con el sistema de autenticación que definamos para el API.
Hasta ahora, solo habíamos utilizado 2 formas para obtener información de nuestro request:
+
req.body //POST req.query //GET
+
Ahora añadiremos el req.params que nos permite agregar un parámetro sin importar el tipo de conexión a través de la url y separarlo por /, por tanto podemos recibir tantos parámetros como deseemos.
+
Dentro de nuestro controller debemos agregar la librería path al inicio.
+
var path = require('path');
+
Y al final colocamos nuestra función para acceder al archivo privado.
Con el uso del método de express res.sendFile podemos interpretar un archivo y cargarlo del lado del cliente.
+
Este último paso es bastante sencillo en términos de solo realizar la canalización del archivo a la carpeta private pero tomando en cuenta que esto nos da control de acceso y de procesamiento en el proceso de consultar archivos que no están directamente abiertos al público nos permite hacer muchas cosas a largo plazo.
+
De esta manera podemos trabajar con archivos en nuestro servidor, utilizando multer y haciendo variaciones, un proyecto de manejo de archivos tiene varias consideraciones importantes y adicionales que puedes manejar, revisa esto para evitar caer en problemas según el tipo de arquitectura o servidores que estés utilizando.
Dentro de lo que hemos trabajado hasta ahora tenemos un servidor con la capacidad de procesar llamadas a una fuente de información como una Base de Datos, manejar archivos y usar todo el poder de procesamiento de NodeJS para ejecutar algoritmos diferentes.
+
Dentro de todo esto hemos implementado un patrón de arquitectura llamado MVC, donde en la parte de la Vista hemos utilizado EJS para dar formato a nuestros archivos HTML.
+
Algo que comentábamos anteriormente en relación al EJS, es que todo el código que añadimos al archivo HTML es para ejecutar un pre-renderizado del HTML final, por lo que una vez que el HTML llega al navegador se pierde todo rastro de haber usado EJS y tenemos un archivo estático. Esta línea es importante pues es lo que hace el manejo del sistema de templates, y también la diferencia principal entre como trabaja esta parte en contraste con React por ejemplo.
+
Al momento de servir un EJS lo que hacemos es usar la función render y a través de ella hemos pasado información del servidor al archivo HTML.
+
res.render("/index",{ data:myData });
+
Este formato es ideal al momento de trabajar, sin embargo las aplicaciones hoy en día requieren de más demanda de información en menor cantidad de tiempo.
+
Un caso muy particular es que si cargamos una tabla y esta añade un miembro adicional, necesitamos recargar la página, pasar por todo el proceso de renderizado del ejs, para volver a ver el resultado actualizado.
+
El problema es que hoy esperamos que de manera automática se empiece a recargar la información y ya esté disponible para utilizarla.
+
Otro caso de uso al respecto es con el uso de los formularios, al momento de presionar el botón de Enviar en un submit nos encontramos que la página se recarga, aunque en el servidor recibimos un POST, se espera una respuesta aunque sea redirigir a la misma página.
+
Aquí empezamos a ver un patrón donde el cliente necesita información de otra manera, no solo a través del renderizado del HTML.
+
Por tanto Javascript puede enviar request al servidor y cargar información en el momento que sea necesario quitando esa dependencia del HTML.
+
Para realizar esto debemos separar en 2 pasos la lógica de como construimos la aplicación, la primera es a través del uso de APIs que hemos ido mencionando poco a poco en el transcurso de nuestros laboratorios y la otra es que el código HTML contenga un archivo JS desde el cual se hagan estas nuevas peticiones al servidor.
+
Algunos ejemplos de casos de uso que podemos nombrar para hacer request al servidor podrían ser:
+
+
Enviar una orden.
+
Cargar información de usuario.
+
Recibir las últimas actualizaciones del servidor.
+
+
¡Y lo más importante sin la necesidad de recargar la página!
+
El término AJAX viene de (Asynchronous Javascript And XML) para request al servidor desde Javascript. Interesante con el término es que no necesitamos utilizar XML, esto es por que el término viene de los old times, y se ha popularizado en cuestión del nombre.
+
Existen múltiples formas de enviar un request al servidor y obtener información del servidor.
+
Usando el método fetch(), esta sería la forma más moderna y versátil. Lo único que debes saber es que no está soportado por viejos navegadores, pero hoy en día ya es un poco complicado encontrar un navegador que no lo tenga.
+
La sintaxis básica es:
+
let promise = fetch(url, [options])
+
url - La url de acceso, si estamos en nuestro proyecto no es necesario agregar https:localhost:3000 o la ip o dominio correspondiente, basta con comenzar con la ruta a la que queremos llegar.
+options - Parámetros adicionales como el método de conexión, headers, etc.
+
Una plantilla básica entonces para hacer una llamada AJAX al servidor sería lo siguiente:
+
let response = await fetch(url); if (response.ok) { // if HTTP-status is 200-299 // get the response body (the method explained below) let json = await response.json(); } else { alert("HTTP-Error: " + response.status); }
+
Por último la respuesta que obtenemos de la llamada puede ser convertida de diferentes maneras o formatos:
+
+
res.text() - Leer la respuesta y responder como texto.
+
res.json() - Parsear la respuesta a JSON.
+
res.formData() - Regresar la respuesta como objeto FormData
+
res.blob() - Regresar respuestas como Blob (datos binarios con tipo)
+
response.arrayBuffer() - Regresar la respuesta como ArrayBuffer (representación de bajo nivel de datos binarios)
+
adicionalmente, response.body es un objeto ReadableStream se permite que leas cada pedazo paso a paso.
Para comenzar nuestro laboratorio utilizaremos una plantilla base para no empezar desde 0 y poder avanzar en lo que nos pide el laboratorio sobre AJAX, pero vamos a explicar un poco lo que necesitamos.
Coloca el proyecto y ejecuta el npm install, si ejecutas el proyecto podrás acceder a la ruta index viendo algo como lo siguiente:
+
+
Si desde postman ejecutamos la url de /products podremos ver el resultado como el siguiente.
+
+
Esto significa que nuestro proyecto tiene un API muy sencilla corriendo y es momento en que podemos empezar a trabajar.
+
Si investigas un poco la base de index.js te darás cuenta que no estamos usando para esta plantilla el EJS. Al contrario tenemos un archivo index.html en la carpeta pública en donde realizaremos la conexión con el API.
+
+
Nota: El echo que no estemos utilizando EJS, no significa que este no pueda ejecutar llamadas AJAX, pero primero debes entender bien donde se ejecuta la llamada para poder ejecutarla. La combinación entre EJS y AJAX le va a dar mucho más poder a tu proyecto. Y no olvide que este es el fundamento para poder trabajar con REACT.
Después de estar trabajando dentro del servidor es momento de regresar a nuestro navegador, prepara la consola pues la estaremos usando para este laboratorio.
+
Abre el archivo script.js dentro de nuestra carpeta pública.
+
Ya hemos hablado y utilizado eventos del lado del cliente en Javascript, y para este laboratorio utilizaremos un evento muy particular llamado load, este evento nos permite ejecutar código una vez que la página se ha cargado por completo.
+
Esto lo hacemos ya que si ejecutamos una función mientras la página no se ha cargado por completo podemos empezar a tener problemas de sincronización o problemas al llamar variables que aún no se cargan.
Este evento tiene 2 sentidos particulares, aparte de hacer clic en los datos lo estamos llamando al momento de hacer click en el botón de submit de nuestro formulario. Si lo dejamos tal cual el formulario se enviará a nuestro servidor y tendremos un conflicto, pero este es el momento de interceptar el envío al servidor mediante:
+
event.preventDefault();
+
Lo que hace esta línea es justo como su nombre lo dice, evita que se termine de ejecutar el evento default si es que contiene, del elemento al que se le aplica el listener.
+
En otras palabras estamos evitando que de manera automática se envíe el formulario al servidor y estamos tomando el control de lo que queremos que suceda.
+
Ahora bien fuera de nuestro evento load vamos a agregar los siguiente métodos.
Dentro de esta, vamos a almacenar los productos nuevos que vayamos creando.
+
+
Nota: No olvides que la lista de productos que tenemos se reinicia cada vez que se reinicia el servidor, por lo que nuevos productos solo se almacenan mientras no realicemos ninguna modificación al código.
Este método, aunque largo nos permite tomar los valores al momento del formulario y validar los campos, si todos los campos son correctos entonces regresamos un objeto Producto con el mensaje de error o éxito correspondiente.
+
Por último vamos a agregar la siguiente función asíncrona:
+
async function addProduct(product){ const url = '/add_product'; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(product) }); if (response.ok) { const data = await response.json(); log('Product added successfully:', data); // Clear the form or display a success message } else { alert('Error adding product: ' + response.statusText); } }
+
Y aquí es donde empezará la magia, agregar un producto recibe un objeto de nuestra clase Product. A partir de ahí viene al fin nuestra llamada AJAX, la cual recibe la url que usaremos del API /add_product, el método de conexión POST, el header para indicar que enviaremos la conexión en formato JSON y como no estamos mandando el formulario necesitamos transformar nuestro nuevo producto al body de la aplicación en un formato legible que en este caso es string.
+
Observa que la ejecución del fetch utiliza un await ya que no sabemos en que momento va a regresar la llamada al servidor, por lo que dejamos que suceda en sus propios términos y al regresar con el éxito o error lo validamos para verlo en la consola.
+
Por último necesitamos terminar de actualizar nuestro evento de clic, con lo siguiente:
+
submitButton.addEventListener('click', function(event) { event.preventDefault(); var result = generateProduct(); addProduct(result.product); });
+
Una vez que prevenimos la ejecución default del evento del formulario, generamos el producto con los datos del mismo y lo enviamos a través de una llamada por AJAX.
+
Intenta recargar el servidor y agrega un nuevo producto, observa el resultado.
+
+
El resultado deberá visualizarse en la consola, por lo que éxito hemos guardado un producto sin la necesidad de recargar la página.
+
Pero ahora, ¿cómo vemos en pantalla nuestra lista de datos?
Al momento de cargar nuestro formulario podríamos hacer otra llamada AJAX para obtener los productos, también hemos colocado un botón que actualice los productos y nos los muestre en pantalla.
+
Para poder hacer ambas cosas vamos a crear la siguiente función en la parte inferior de nuestro código.
+
async function loadProducts(){ let response = await fetch('/products'); if (response.ok) { // if HTTP-status is 200-299 let json = await response.json(); //log(json) const codeResult = document.getElementById('codeResult'); codeResult.innerHTML = JSON.stringify(json.products); } else { alert("HTTP-Error: " + response.status); } } async function getProducts(){ let response = await fetch('/products'); if (response.ok) { // if HTTP-status is 200-299 let json = await response.json(); //log(json) return json.products; } else { return []; } }
+
Aquí estamos generando dos funciones que en esencia hacen lo mismo pero el resultado es diferente, la primer función hace la llamada AJAX a /products para obtener la lista y la pinta en el contenedor que hemos designado para ello.
+
La segunda función hace lo mismo pero en vez de pintar en un lugar particular solo nos devuelve como resultado la lista de productos, esta la usaremos más adelante.
+
Ahora que ya podemos cargar la lista de productos podemos actualizar nuestro evento clic del submitButton agregando la carga al final.
+
submitButton.addEventListener('click', function(event) { event.preventDefault(); var result = generateProduct(); addProduct(result.product); loadProducts() });
+
El resultado será que al agregar un nuevo producto cargaremos la lista justo debajo.
+
Éxito, pero no tanto, ya que aunque vamos actualizando la lista, esta solo responde a nuestros eventos, que pasaría si alguien más se conecta al servidor y crea un producto, no lo podríamos ver hasta que agreguemos uno.
+
Por tanto vamos a usar el botón que tenemos para actualizar productos. Para ello vamos a definir un nuevo evento clic, como este se encuentra fuera del formulario no es necesario aplicar el preventDefault.
+
Debajo del evento listener del submitButton, agrega lo siguiente:
Si bien nuestro caso de uso nos permite visualizar los cambios en el servidor, es probable que no queramos tener que esperar a hacer clic en el botón, o en su defecto agregar un nuevo producto para ver la carga de la tabla. Por lo que otra funcionalidad donde podemos aplicar el AJAX es en un cierto tiempo.
+
Vamos a agregar lo siguiente debajo de nuestro último listener.
+
setInterval(() =>{ loadProducts() }, 5000);
+
Aquí vamos a estar recargando la tabla cada 5 segundo, recargar y observa el resultado.
+
Con esto ya tenemos 3 fuentes para cargar la actualización de los datos:
+
+
Al agregar un nuevo producto
+
Al dar click en actualizar
+
Cada 5 segundos
+
+
Estas opciones tienen sus ventajas y desventajas, por ejemplo la carga cada 5 segundos podemos reducirla a 1 segundo pero en un conjunto grande de datos esto quizás no sea la mejor opción puesto que tan solo el render podría tomar más de ese segundo.
+
Al final del día dependerá el caso y el contexto de lo que estés haciendo para ver cual es la mejor forma de carga de información, considera siempre el peor caso para evitar que el crecimiento de tu aplicación límite tus proyectos.
La última función que creamos fue la de getProducts, que como dijimos en vez de pintar directamente los datos genera una lista de datos de resultado, esto es por que algunos componentes existentes en internet te permiten pegar la información tal cual como por ejemplo: gráficas, tablas, etc.
+
Una ventaja de trabajar con estos componentes y es que nos evitamos construir funcionalidades complejas en cosas que hoy en día los usuarios esperan.
+
Tal es el caso de los data table, estas son tablas donde representamos la información que vemos en nuestra base de datos, y podemos añadirle funcionalidad adicional que ya viene integrada como paginar, búsqueda y/o filtros.
+
La librería que usaremos es Grid.js y ya la integramos en la plantilla del proyecto por lo que de momento no necesitas realizar nada más en términos de instalación, si quieres verla consulta el archivo index.html.
+
Ahora bien, debajo del intervalo de cada 5 segundos que definimos en el paso anterior vamos a colocar lo siguiente:
Esta librería tiene los siguientes parámetros, donde definimos:
+
+
columns: Un arreglo que debe ser el mismo número de elementos que contendrá nuestra lista de datos, y serán los nombre que asignaremos a las columnas.
+
pagination: Si queremos activar la paginación de la tabla
+
search: Si queremos activar la búsqueda de la tabla.
+
sort: Si queremos activas el ordenamiento de la tabla.
+
server: Contiene los datos y la llamada AJAX al servidor
+
+
url: La url que llamaremos
+
then: Que queremos hacer con la respuesta resultado del servidor para cargar la tabla, aquí debemos devolver un arreglo con los datos, en nuestro caso ya viene directo pero debemos llamar la variable products.
+
+
+
+
El resultado será algo como lo siguiente:
+
+
Veremos que sin la necesidad de declarar un table html tenemos una tabla que además contiene la funcionalidad de paginación, búsqueda y ordenamiento.
+
Aquí dependerá de la librería que usemos que más cosas podamos añadir para poder trabajar como filtros, código html embebido y otras cosas más, revisa la documentación para ver si la librería se ajusta a tus necesidades.
+
Vamos a comentar el intervalo de 5 segundos, pues lo que haremos a continuación pondrá un gran estrés en tu computadora si no lo quitas.
+
/*setInterval(() =>{ loadProducts() }, 5000);*/
+
Ahora comentaremos TODAS las llamadas a:
+
//loadProducts()
+
Si revistaste nuestra API cuando empezamos el laboratorio habrás notado que tenemos 3 rutas que podemos llamar:
+
+
products: Obtiene la lista de productos
+
add_product: Agregar nuevos productos a la lista
+
prepare_million_products: Agrega 1 millón de productos a la lista
+
+
La última es la que nos interesa, desde tu navegador ejecuta esta última:
+
http://localhost:3000/prepare_million_products
+
El resultado no deberá mostrar nada pero intenta volver a
+
http://localhost:3000/index.html
+
Observa como la tabla empieza a trabajar y por la cantidad de datos ya no será tan simple ver el resultado.
+
De manera automática Grid.js hace la carga pero la cantidad masiva de datos hace que debamos esperar un poco.
+
Al final debemos obtener un resultado como el siguiente sin haber destruido nuestras computadoras.
+
+
Con esto espero que veas las ventajas y las implicaciones que tiene el manejo de los datos, existen muchas diferentes estrategias que podemos abordar para manejar la carga tan masiva de datos, las funcionalidades que se manejan o los casos de uso que se presenten como lo hicimos al inicio al visualizar la lista de datos.
+
Construir APIs que sean útiles es parte del desafío de desarrollo web, pero al menos ya sabes como poder llamarlas desde tu cliente y manipularlas de diferentes maneras.
Dentro del mundo de la programación, tener un manejo de control de versiones es parte del día a día.
+
Esto implica que el programador tiene un lugar donde se aloja el código de cada proyecto.
+
También permite que el usuario cambie de equipo para trabajar en diferentes lugares teniendo la misma configuración.
+
También permite que equipos de trabajo puedan compartir su código y trabajar de manera colaborativa.
+
Es importante que al trabajar con control de versiones entendamos en que momento se encuentran nuestros archivos para que en caso de problemas no perdamos información para ello debemos entender como funciona el working tree, la staging area y el repository.
El working tree es el pedazo de proyecto en cualquier momento (usualmente es el momento actual). Cuando agregas o editas código, modificas el working tree.
+
El staging area es donde se colocan los cambios del working tree antes de hacerlos permanentes.
+
Un repository es la colección de cambios permanentes (commits) realizados a través de la historia del proyecto. Típicamente, existe un repositorio remoto (Github,Gitlab,Bitbucket,etc.) y muchos repositorios locales, uno para cada desarrollador involucrado en el proyecto al menos.
+
+
Cuando haces un cambio en el staging area es permanente, se queda removido de la staging area y commiteado al repo local. Un commit es un grabado permanente de ese cambio. El repo contiene todos los commits que se han realizado.
Instala Git en tu computadora según sea la versión de tu Sistema Operativo. Git Oficial
+
+
En windows puede que necesites utilizar el Git-Shell que se instala junto con Git, para usarlo en tu terminal normal necesitas agregarlo a las variables de entorno, investiga al respecto.
Al momento de iniciar un repositorio puedes hacerlo desde línea de comando.
+
git init
+
Para saber que el repositorio se inicio correctamente se debe crear un folder .git que por default es una carpeta ocular, debes habilitar ver este tipo de carpetas para poder verlas desde el administrador de archivos de tu sistema operativo.
+
Al momento de crear un nuevo repositorio se crea un branch default, más adelante trabajaremos con los branches. Por ahora lo único que debes saber es que el default es master, sí creas el repositorio desde la nube en ocasiones puede ser llamado como main.
+
Todo lo que vamos a hacer en este laboratorio es para trabajar directamente en master o main.
+
Setear el nombre y correo para el repo (obligatorio)
+
Para que en un repositorio puedas hacer commit es necesario establecer un correo y un nombre. Esto lo hacemos con los comandos.
Los comandos anteriores sirven para establecer el correo y nombre para 1 solo repositorio, el que estoy utilizando.
+
Una forma de evitar esto cada vez que trabajemos en nuevo proyecto es usar la directiva --global, esto hace que cada comando se establezca en una configuración global de todo nuestro git para que en cada proyecto se utilice la misma configuración y evitemos tener que escribirlo.
Una vez que tenemos preparado nuestro repositorio podemos crear un nuevo archivo y agregarlo al staging area.
+
+
Nota: Para los siguiente ejemplos usaremos comandos para crear rápidamente archivos de texto, pero si se te hace más fácil puedes usar el editor de texto de tu preferencia.
+
+
echo "git is awesom" > message.txt git add message.txt
+
Una vez que creamos el archivo vamos a ver los cambios en el staging area
+
git diff --cached
+
El resultado debería ser algo como lo siguiente
+
diff --git a/message.txt b/message.txt new file mode 100644 index 0000000..0165e86 --- /dev/null +++ b/message.txt @@ -0,0 +1 @@ +git is awesom
+
Ahora vamos a preparar un commit al repositorio local
Edita el archivo previamente commiteado y agrega los cambios a la staging area.
+
echo "git is awesome" > message.txt git add message.txt
+
Crea un nuevo archivo
+
echo "git is great" > praise.txt
+
Para mostrar el estatus actual vamos a utilizar el comando
+
git status
+
El resultado se verá como
+
On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: message.txt Untracked files: (use "git add <file>..." to include in what will be committed) praise.txt
+
+
Nota que message.txt esta en el staging area, mientras que praise.txt no está siendo trackeado.
HEAD es el apuntador especial que marca donde se encuentra la línea del último commit del repositorio.
+
commit ecdeb79aad4565d8d7d725678ffadc48b3cdec52 Author: sandbox <sandbox@example.com> Date: Thu Mar 14 15:00:00 2024 +0000 edit message diff --git a/message.txt b/message.txt index 0165e86..118f108 100644 --- a/message.txt +++ b/message.txt @@ -1 +1 @@ -git is awesom +git is awesome
+
Mostrar el segundo-al-último commit
+
git show HEAD~1
+
Usar HEADn muestra el nth-antes-del-último commit o usa el has del commit específico en lugar de HEADn.
+
Cuando crear un commit siempre se crea un hash de ese commit, este es la firma directa de ese commit y justamente sirve para cuando necesitamos ver el detalle de ese commit o como en este caso ver o regresar a un commit.
+
Ten cuidado cuando trabajes con el hash, el completo es:
+
ecdeb79aad4565d8d7d725678ffadc48b3cdec52
+
Ya que algunos comandos o en algunas visualizaciones te aparece simplificado
+
ecdeb79
+
El simplificado sirve para visualización pero para los comandos utiliza el completo.
Para poder trabajar en la nube dependerá de la plataforma que usemos, pero el estándar al final del día es que git utiliza los mismo comandos, lo que va a variar es como funciona la plataforma que utilicemos.
+
Crea un repositorio en la plataforma de tu elección
+
Dentro de la plataforma que uses para trabajar (Github, Bitbucket, Gitlab...), busca como crear un nuevo repositorio.
+
Configura los datos básicos necesarios hasta que puedas clonar el repositorio.
Para poder clonar un repositorio puedes usar https o ssh, la diferencia son protocolos de seguridad en como vamos a subir y bajar la información del repositorio, la más simple es https.
+
+
En https inicialmente se usaba tu usuario y contraseña de la plataforma cada vez que realizas un commit para poder firmarlo y conectarte. La realidad es que existen nuevas funciones de seguridad que obligan al usuario a crear un token dentro de la plataforma y en vez de la contraseña utilizar dicho token, por lo que necesitas realizar un proceso adicional y guardar muy bien el token para poder hacer tus commits.
+
El modo de ssh, es un protocolo de seguridad donde desde tu computadora creas un archivo el cual debes registrar en la plataforma de tu repositorio en la nube y con ello ya no vas a necesitar autentificarse cada vez que realices un commit.
+
+
Si bien puedes usar uno u otro, la recomendación es que por seguridad generes tu llave de ssh, ya que hay repositorios que son restringidos solo a este tipo de comunicación y siempre va a ser una mejor práctica de seguridad.
+
Si utilizas Github sigue este manual para crear tu llave SSH.
+Si utilizar Bitbucket este sería el manual
+
Puede ser un proceso desafiante y un poco complicado al inicio pero a la larga te evitara muchos problemas y hoy en día es la forma de trabajar como todo un profesional.
Ya que tienes un repositorio en la nube y que tienes un método de conexión puedes obtener la url de https o de ssh y ejecuta la siguiente línea de comando en una carpeta vacía local donde vayas a tener tu repositorio.
+
git clone {{url https o ssh del repositorio en la nube}}
Una vez que clonemos el repositorio siempre es una buena práctica utilizar el comando
+
git pull origin
+
Lo que hace git pull es bajar los últimos cambios que existen desde el repositorio en la nube.
+
+
La mejor práctica es que constantemente realices git pull para siempre tener la última versión del repositorio.
+
+
Como equipo de trabajo quien siempre va a tener la mejor versión es la nube a menos que subas un nuevo cambio, esto evitará que tú y tu equipo eliminen información valiosa del repositorio.
+
Ahora ya que hiciste pull puedes hacer tu trabajo dentro de tu repositorio. Al finalizar debes
+
git add -A git commit -m "Mensaje del commit"
+
Y por último deberás subir los archivos al repositorio en la nube, esto lo hacemos con el comando
+
git push origin
+
+
Nuevamente antes de realizar y subir cambios al repositorio en la nube no olvides hacer un pull, y evita hacer commits de varios días de trabajo pues esto solo genera muchos cambios que corre el riesgo de afectar a todo el equipo y poner el riesgo el avance al momento.
CSS son las siglas en inglés para (Cascading Style Sheet), en español significa Hojas de Estilo en casada. De manera general es la forma de poder manejar el diseño y presentación de las páginas web, es decir el como se ven cuando un usuario las visita. Si bien no es un lenguaje de programación como tal, el CSS es un conjunto de instrucciones y atributos que permiten asignárselos al código HTML.
+
Se les denomina hojas de estilo en cascada porque puedes tener varias, pero al momento de aplicarlas en una etiqueta toman precedencia, es decir se aplican de acuerdo a su aplicación, terminando en la última encontrada. Además si por alguna razón se perdiera el último estilo se toma el anterior, así hasta quitar todas las propiedades de estilo y dejar la etiqueta en un estado base.
+
Un ejemplo muy común, es que hace muchos años para que una página se viera con estilo, la tendencia era programarla mediante el uso de tablas HTML, esto para alinear elementos de contenido como imágenes y texto. Sin embargo esta era una tarea muy tediosa y el código quedaba muy largo y al final el potencial que se tenía en cuanto a diseño era muy limitado.
+
Por lo mismo al salir el primer estándar de CSS en el mercado cambió la forma en la que las páginas web son construidas para siempre.
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/docs/backend/node/tutorials/intro_web/Lab4JS/index.html b/deprecated/old-docs-build/docs/docs/backend/node/tutorials/intro_web/Lab4JS/index.html
new file mode 100644
index 0000000..4965c82
--- /dev/null
+++ b/deprecated/old-docs-build/docs/docs/backend/node/tutorials/intro_web/Lab4JS/index.html
@@ -0,0 +1,538 @@
+
+
+
+
+
+Intro a Javascript | TC2005B
+
+
+
+
+
+
+
Para este laboratorio tendrás aparte los archivos HTML y JS correspondientes para poder verlos desde tu navegador y podrás ver cada concepto dentro de este documento.
+
Javascript es el lenguaje de programación por excelencia para usarse dentro del navegador, cuando hablamos de desarrollo web, tenemos que el código HTML, le da estructura a nuestra página, el CSS le da el estilo y JS nos permite dotar de funcionalidad a nuestro sitio.
+
Visto de otra forma, JS nos va a ayudar a que en la arquitectura cliente-servidor, sea el cliente el que tenga mayor capacidad de solicitar al servidor de información ya sea a través de la petición de la misma o el guardado.
+
Comenzaremos desde la base de como declarar el javascript para poder trabajar con él, hasta los conceptos generales que tiene el lenguaje de programación.
+
+
Nota: este curso no está orientado a ser una clase de programación de javascript, solamente se toman las nociones básicas para poder empezar a construir sitios web, se te pide que poco a poco entiendas el lenguaje con práctica e investigando por tu cuenta las peculiaridades y especializaciones del lenguaje.
Cuando hablamos de empezar a trabajar con Javascript vamos a tener varias formas de hacerlo.
+
La primera de ellas es a través del inline scripting o dentro del mismo archivo HTML donde estemos trabajando. Esta forma nos permite visualizar el código en cualquier parte del archivo.
+
Por lo general aunque se puede realizar la buena práctica nos dice que debemos separar en archivos diferentes la funcionalidad, pero eso lo veremos poco a poco.
+
Los programas de Javascript pueden ser insertados en cualquier parte del HTML usando la etiqueta
+
<script></script>
+
Antes era necesarios añadir el atributo type pero en las nuevas versiones esto ya no es necesario pues el navegador lo hace de manera automática.
+
<script type="text/javascript"> ... </script>
+
Para tener una base de donde comenzar podemos tener el siguiente código:
+
<!DOCTYPE HTML> <html> <body> <p>Before the script...</p> <script> alert( 'Hello, world!' ); </script> <p>...After the script.</p> </body> </html>
+
En el ejemplo anterior vemos como con el uso de la etiqueta script podemos añadir en su interior el código de javascript que queramos ejecutar.
+
La declaración que usamos es una llamada a la función alert(), la cual recibe como parámetro un string, y al momento de ejecutarse en el navegador nos muestra un modal de alerta simple con el texto que hayamos pasado como parámetro.
+
<!-- Los programas de Javascript pueden ser insertados en cualquier parte del HTML usando la etiqueta <script></script> Antes era necesarios añadir el atributo type pero en las nuevas versiones esto ya no es necesario. <script type="text/javascript"> ... </script> --> <!DOCTYPE HTML> <html> <body> <p>Before the script...</p> <script> alert( 'Hello, world!' ); </script> <p>...After the script.</p> </body> </html>
También podemos añadir el código de javascript en archivos separados, en general siempre esta es la mejor práctica que debemos seguir.
+
Una buena práctica es colocarlos en la parte de abajo afuera del body por cuestiones de carga y velocidad de la página.
+
En el siguiente archivo HTML vamos a hacer uso de un archivo javascript externo, para ello el nombre del archivo será script y la extensión del mismo será .js el cual justamente delimita los archivos de javascript.
+
<!DOCTYPE HTML> <html> <body> <p>Before the script...</p> <p>...After the script.</p> </body> <script src="./1_separate_file_script.js"></script> </html>
+
Observa que colocamos la etiqueta del script fuera de la etiqueta body, esto es una buena práctica recomendada por lo navegadores, y esto se debe a la carga de la página.
+
Cuando trabajamos con Javascript es muy común que este realice procesamiento, si la página contiene estilos e imágenes su tiempo de carga puede empezar a aumentar entre más cosas tenga, si a eso le añadimos el tiempo de procesamiento de Javascript hay páginas que tardan hasta 1 minuto en cargar. Si hablamos de páginas web optimizadas son segundos los que tarda un usuario en salirse sin haber visto nuestro contenido. Por ello al colocar el script fuera del body hacemos que esta carga se realice de manera paralela a cuando el usuario ya está navegando esto ayuda a recuperar valiosos segundos de navegación.
+
Debe existir un equilibrio entre cuando cargar de manera paralela y la carga inicial de la página, pero todo esto se refiere a un tema de optimización para posicionamiento de la página en buscadores y es todo un tema de estudio.
+
Ahora bien dentro del nuevo archivo script.js podemos empezar directamente a escribir el código de javascript sin necesidad de escribir alguna directiva o etiqueta adicional como se muestra a continuación.
+
alert( 'Hello, world!' );
+
Observa que estamos usando nuevamente la misma declaración que cuando hicimos el inline scripting, solo que ahora lo hacemos desde el archivo separado, el resultado final será el mismo que el anterior pero habremos realizado una buena práctica separando nuestros códigos.
Podemos cargar tantos archivos de javascript como necesitemos y pueden o no estar en la carpeta o utilizar algún CDN.
+
+
Nota: Un CDN es un tipo de servidor especial que esta orientado a servir archivos públicos como HTML, CSS, JS, imágenes, etc. Estos servidores tienen la peculiaridad que al no procesar información como un servidor normal son más baratos y tienen un mayor alcance, la idea es poder generar múltiples al rededor del mundo para dar mayor acceso a las aplicaciones. Un ejemplo es que se recomienda que las aplicaciones en React utilicen estos servidores para servir rápidamente la interfaz de usuario independientemente de como se procese la información tal y como lo hace Facebook.
+
+
<!DOCTYPE HTML> <html> <body> <p>Before the script...</p> <p>...After the script.</p> </body> <script src="./script2.js"></script> <script src="./script3.js"></script> </html>
+
Aquí el contenido de script2 es el siguiente
+
alert( 'Hello' );
+
Y para script3 el contenido es el siguiente
+
alert( 'world!' );
+
El resultado final es que la ejecución de las instrucciones viene dada por el orden en que colocamos los archivos, entonces para el navegador la fila de instrucciones quedaría de la siguiente forma:
+
alert( 'Hello' ); alert( 'world!' );
+
Si cambiamos de orden los archivos, entonces la fila de instrucciones se invertiría dejando un resultado como:
+
alert( 'world!' ); alert( 'Hello' );
+
+
Este último punto es importante ya que si en algún momento necesitamos cargar una librería y ejecutar un código en un archivo separado dependiente de esa librería, debemos asegurarnos que el código se ejecuta después de haberla cargado. Es un error muy común al inicio perder esto de vista y por lo mismo podemos perder tiempo validando código funcional cuando que el verdadero problema es que la librería ni siquiera se ha cargado correctamente al momento de ejecutar el código.
Como adicional observa lo que pasa cuando intentamos cargar un archivo externo de javascript y adentro de esa misma etiqueta ejecutar código también.
+
<!DOCTYPE HTML> <html> <body> <p>Before the script...</p> <p>...After the script.</p> </body> <script src="./script.js"> alert(1); </script> </html>
+
Esta forma nos va a generar conflictos, pues si bien no vamos a ver un error en consola, el código que está dentro de la etiqueta no se va a ejecutar, pues una vez declarada la propiedad src de la etiqueta se da prioridad al archivo externo en este caso script.js.
+
Así bien la forma correcta de hacer lo anterior sería la siguiente
+
<!DOCTYPE HTML> <html> <body> <p>Before the script...</p> <p>...After the script.</p> </body> <script src="./script.js"></script> <script> alert(1); </script> </html>
+
Donde de manera continua separamos el código del archivo y en otra etiqueta el inline scripting que queremos ejecutar.
El punto y coma puede ser omitido en la mayoría de los casos cuando un salto de línea exista.
+
alert('Hello') alert('World')
+
Aquí Javascript interpreta el salto de línea como un punto y coma "implícito", a esto se le llama inserción automática de punto y coma.
+
+
Nota: La mayoría de los casos una nueva línea implica un punto y coma. Pero en la mayoría de los casos no significa siempre.
+
+
Existen casos que una nueva línea no significa un punto y coma. Ejemplo:
+
alert(3 + 1 + 2);
+
El código tiene una salida de 6 por que javascript no inserta el punto y coma aquí. Es obvio en este caso que si la línea termina con un +
+entonces se tiene una expresión incompleta, por lo que un punto y coma sería incorrecto.
+
Ejemplo de un error, remover el punto y coma del alert para ver el cambio:
+
alert("Hello"); [1, 2].forEach(alert);
+
alert("Hello") //Aquí se genera el error [1, 2].forEach(alert);
Conforme el tiempo pasa los programas se hacen más y más complejos, entonces se vuelve necesario agrega comentarios que describen como funciona el código y por qué.
+
Los comentarios pueden ser colocados en cualquier lugar del script. Ellos no afectan su ejecución porque el engine simplemente lo ignora.
+
//One-line comments
+
/* Multiline comments */
+
//Comentarios anidados no son soportados /* /* nested comment ?!? */ // */
+
Se recomienda el uso de estándares para la documentación del código para Javascript se utiliza JSDoc
Por un largo tiempo, Javascript evolucionó sin problemas de compatibilidad. Nuevas funcionalidades fueron añadidas al lenguaje
+mientras mientras que funcionalidad antigua no cambió.
+
Eso tuvo el beneficio de nunca romper el código. Sin embargo, un problema de ello es que el lenguaje se quedó atrapado en el tiempo.
+
Esto cambio en 2009 con la llegada de ECMAScript5 (ES5) apareció. Agregó nuevas funcionalidades al lenguaje y modificar algunas de las existentes.
+Para poder mantener el código viejo funcionando esas modificaciones fueron apagadas por default.
+
Para ello debes habilitar estas directivas usando: "use strict"
+
La directiva debe colocarse al inicio de un script, para que todo el script funcione de la forma "moderna".
+
"use strict";
+
Este código va a trabajar de la forma moderna
+
Asegúrate que "use strict" este hasta arriba, de otra forma no se habilitarán las funciones.
+
alert("some code"); // "use strict" below is ignored--it must be at the top "use strict"; // strict mode is not activated
+
No existe directiva como "no use strict" que regrese el engine a su conducta anterior.
+Una vez usado el strict mode no hay vuelta atrás.
+
¿QUÉ DEBO USAR?
+
El Javascript moderno soporta "clases" y "módulos" que veremos más adelante. Lo interesante es que no necesitamos de "use strict" para usarlos.
+
Por ahora "use strict;" es bienvenido como invitado al inicio de tus scripts.
+
Después, cuando tu código este todo en clases y módulos podrías omitirlo.
El nombre contiene solo letras, dígitos o símbolos $ y _.
+
El primer carácter no debe ser un dígito.
+
+
let userName; let test123;
+
Cuando el nombre contiene múltiples palabras se utiliza el estilo camelCase.
+
Esto significa: Las palabras van seguidas, cada palabra excepto excepto la primera inician con mayúscula: myVeryLongName.
+
Lo que es interesante es que el signo de dólar $ y el guión bajo _ también pueden usarse en nombre.
+
Estos son símbolos regulares, justo como letras, sin ningún significado especial.
+
let $ = 1; // declarar la variable con el nombre "$" let _ = 2; // y ahora la variable con el nombre "_" alert($ + _); // 3
+
Si bien el ejemplo anterior funciona, al momento de leer el código este no es descriptivo en el contenido de las variables por lo que la recomendación es siempre declarar variables que den noción al programador de que es lo que contienen para tener un código más legible.
+
Ejemplos de variables con nombre incorrectos
+
let 1a; // No puede iniciar con un dígito let my-name; // hyphens '-' no son permitidos
+
Las mayúsculas y minúsculas importan
+Las variables nombradas apple y APPLE son 2 variables diferentes.
+
let apple; let APPLE;
+
Caracteres no latinos son permitidos, pero no recomendados.
+Es posible usar cualquier lenguaje, incluyendo letras Cirílicas, logogramas Chinos
+
let имя = '...'; let 我 = '...';
+
Técnicamente no existe un error. Pero la convención internacional usa Inglés para el nombre de variables.
+
Una asignación sin usar use strict
+
// Nota: No se usa "use strict" en este ejemplo num = 5; // La variable "num" es creada si no existe. alert(num); // 5
+
Esta es una mala práctica y es causa de error con el strict mode
+
"use strict"; num = 5; // error: num is not defined
La buena práctica para el uso de constantes difíciles de recordad es usar valores conocidos antes de su ejecución.
+
Tales constantes son nombrados usando mayúsculas y guiones bajos.
+
const COLOR_RED = "#F00"; const COLOR_GREEN = "#0F0"; const COLOR_BLUE = "#00F"; const COLOR_ORANGE = "#FF7F00"; // ...when we need to pick a color let color = COLOR_ORANGE; alert(color); // #FF7F00
+
Beneficios:
+
+
COLOR_ORANGE es más fácil de recordar que #FF7F00
+
Es más fácil equivocarse escribiendo "#FF7F00" que COLOR_ORANGE
+
Cuando leemos el código, COLOR_ORANGE es mas significativo que #FF7F00
Algunos programadores tienden a re usar las variables en lugar re declarar nuevas.
+
Como resultado, sus variables son como cajas donde avientan cosas sin cambiar sus nombres. Como resultado, ¿qué hay dentro de la caja?¿quién sabe?
+Necesitamos ir a detalle y verificar, esto consume mucho tiempo.
+
Una variable extra es buena, no mala. Pregúntate lo siguiente: ¿Cuánto te cobran por una nueva variable? NADA, SON GRATIS.
+
Los sistemas modernos de Javascript optimizan muy bien el código, entonces no habrá problemas de rendimiento.
+Usar diferentes nombre de variables ayuda incluso al motor de Javascript a optimizar más tu código.
Un valor en Javascript siempre es de cierto tipo. Por ejemplo, un string o un número
+
Existen 8 TIPOS BÁSICOS de datos en Javascript.
+
Podemos colocar cualquier tipo en una variable. Por ejemplo. una variable puede ser un string y después almacenar un número.
+
let message = "hello"; message = 123456;
+
Los lenguajes de programación que permiten esto tales como Javascript son conocidos como dinámicamente tipados, esto significa que existen tipos de datos, pero las variables
+no están ligados a ninguno de ellos.
NaN: representa un error computacional. Es el resultado de una operación matemática incorrecta o no definida.
+
+
alert( "not a number" / 2 ); // NaN, tal división es errónea //NaN es continua. Cualquier operación matemática en NaN regresa NaN alert( NaN + 1 ); // NaN alert( 3 * NaN ); // NaN alert( "not a number" / 2 - 1 ); // NaN
+
Así que si una operación matemática devuelve NaN, se propaga el resultado.
+
Realizar operaciones matemáticas en Javascript es "seguro", podemos hacer lo que sea: dividir por 0, tratar valores no numéricos como números, etc.
+
El script no fallará con un error fatal. Lo más que puede suceder es obtener un NaN como resultado.
En Javascript los números se representan por valores enteros (253-1) (eso es 9007199254740991), o menores que -(253-1) para negativos.
+
Para ser precisos los números puede soportar hasta 1.7976931348623157 ^ 10308, pero fuera de ese rango existirá un error de precisión, porque no todos los dígitos
+caben en 64-bits. Por lo que un valor aproximado será guardado.
let str = "Hello"; let str2 = 'Single quotes are ok too'; let phrase = `can embed another ${str}`;
+
En Javascript existen 3 tipos de comillas:
+
+
Dobles: "Hello".
+
Simples: 'Hello'.
+
Backticks: `Hello`.
+
+
Dobles y simples son comillas "sencillas". Prácticamente no existe diferencia en Javascript.
+
Backticks son comillas de "funcionalidad extendida". Estas nos permiten meter variables y expresiones dentro de un string usando
+la simbología ${...}, por ejemplo
+
let name = "John"; // embed a variable alert( `Hello, ${name}!` ); // Hello, John! // embed an expression alert( `the result is ${1 + 2}` ); // the result is 3
El tipo boolean tiene los valores true y false
+let nameFieldChecked = true; // yes, name field is checked
+let ageFieldChecked = false; // no, age field is not checked
+
Los valores booleanos son resultado de comparaciones
El valor especial undefined también es un tipo aparte. Es un tipo en sí mismo, como null.
+
El significado de undefined es "valor no asignado"
+
Si una variable es declarada, pero no asignada, entonces su valor es undefined
+
let age; alert(age); // shows "undefined"
+
Técnicamente es posible asignar undefined a una variable
+
let age = 100; // change the value to undefined age = undefined; alert(age); // "undefined"
+
Pero no te recomiendo realizar esta acción. Normalmente, uno utiliza null para asignar "vacío" o "desconocido" a una variable, mientras que undefined está reservado al valor inicial default para quitar la asignación cosas.
El tipo especial Object:
+Todos los otros tipos son llamados "primitive" por que sus valores pueden contener solo una cosa (un string o un número). En contraste, los objetos son usados para almacenar colecciones de datos y entidades más complejas.
+
Por lo mismo, los objetos merecen un tratamiento especial.
+
El tipo symbol:
+Se usa para crear identificadores únicos para objetos. Tenemos que mencionarlos pero vamos a posponer sus detalles para más adelante.
Como vamos a estar utilizando el navegador para visualizar nuestro ambiente de demostración, vamos a ver algunas funciones para interactuar con el usuario.
let year = prompt('In which year was the ECMAScript-2015 specification published?', ''); if (year == 2015) { console.log( 'You guessed it right!' ); } else { console.log( 'How can you be so wrong?' ); // any value except 2015 }
let accessAllowed; let age = prompt('How old are you?', ''); if (age > 18) { accessAllowed = true; } else { accessAllowed = false; } console.log(accessAllowed);
+
let result = condition ? value1 : value2; let accessAllowed = (age > 18) ? true : false;
let a = 3; switch (a) { case 4: console.log('Right!'); break; case 3: // (*) grouped two cases case 5: console.log('Wrong!'); console.log("Why don't you take a math class?"); break; default: console.log('The result is strange. Really.'); }
function showMessage() { let message = "Hello, I'm JavaScript!"; // local variable console.log( message ); } showMessage(); // Hello, I'm JavaScript! console.log( message ); // <-- Error! The variable is local to the function
function showMessage(from, text = "no text given") { console.log( from + ": " + text ); } showMessage("Ann"); // Ann: no text given showMessage("Ann", undefined);
Las funciones son acciones. Su nombre por lo general es un verbo. Este debe ser breve, tan preciso como sea posible y describir lo que la función hace, para que quien lea el código entienda que hace.
+
Por ejemplo, las funciones que empiezan con "show" por lo general muestran algo.
+
Otros ejemplos serían:
+
+
"get..." - regresa un valor
+
"calc..." - calcula algo
+
"create..." - crea algo
+
"check..." - checa algo y regresa un boolean, etc.
+
+
El resultado final sería algo como:
+
+
showMessage(..) // shows a message
+
getAge(..) // returns the age (gets it somehow)
+
calcSum(..) // calculates a sum and returns the - result
+
createForm(..) // creates a form (and usually returns it)
+
checkPermission(..) // checks a permission, returns true/false
En Javascript, una función no es una "estructura mágica del lenguaje", más bien es un tipo especial de valores.
+
La sintaxis que usamos anteriormente se le conoce como Function Declaration.
+
function sayHi() { console.log( "Hello" ); }
+
Existe otra sintaxis que es llamada Function Expression. Esta forma nos permite crear una función en medio de cualquier expresión. Esto es algo muy importante pues es lo que hace diferente a Javascript de muchos lenguajes de programación, ya que podemos hacer muchas operaciones como almacenar la función en una variable.
+
let sayHi = function() { console.log( "Hello" ); };
+
Aquí podemos ver que la variable sayHi obtiene el valor, la nueva función, creada como function() { alert("Hello"); }.
+
Como la creación de la función sucede en el contexto de la expresión de asignación (a la derecha del operador =), esta es una Function Expression.
+
Por favor observa que no hay un nombre después de la palabra reservada function. Omitiendo el nombre es permitido por las Function Expressions.
+
Aquí inmediatamente se asigna el valor a la variable, por lo que el significado de este código es "crear una función y ponerla en la variable sayHi.
+
Cuando trabajamos con Function Expressions el formato de función sin nombre es conocido también como función anónima.
Para reiterar, no importa como una función es creada, esta es un valor.
+
function sayHi() { console.log( "Hello" ); } console.log( sayHi ); // shows the function code
+
Nota que en el ejemplo anterior la última línea no ejecuta la función, por que no hay paréntesis después de sayHi. Existen lenguajes de programación donde cualquier mención al nombre de una función causa su ejecución, pero en Javascript esto no sucede.
+
Ahora bien, como en Javascript una función es un valor, podemos lidiar con ella como un valor. Por ello podemos hacer acciones como la siguiente:
+
function sayHi() { // (1) crear alert( "Hello" ); } let func = sayHi; // (2) copiar func(); // Hello // (3) ejecutar la copia (funciona)! sayHi(); // Hello // esto aún funciona
Veamos más ejemplos de pasar funciones como valores y usar function expressions.
+
Vamos a escribir la función ask(question, yes, no) con 3 parámetros.
+
+
question - Texto de la pregunta
+
yes - Función que regresa en caso de contestar sí
+
no - Función que regresa en caso de contestar no
+
+
La función debe realizar la pregunta y dependiendo de la respuesta llamar a yes() o no()
+
function ask(question, yes, no) { if (confirm(question)) yes() else no(); } function showOk() { console.log( "You agreed." ); } function showCancel() { console.log( "You canceled the execution." ); } // usage: functions showOk, showCancel are passed as arguments to ask ask("Do you agree?", showOk, showCancel);
+
En la práctica estas funciones son muy útiles. La mayor diferencia entre una pregunta del mundo real y el ejemplo anterior es que una pregunta real tiene formas más complejas de interactuar con un usuario que un simple confirm. En el navegador tales funciones por lo general dibujan una bonita venta de pregunta , pero eso es otro tema.
+
Los argumentos showOk y showCancel de ask son llamadas funciones callback o solamente callbacks.
+
La idea es que pasemos una función y esperemos a que sea llamada después si es necesario. En nuestro caso, showOk se convierte en el callback para yes y showCancel se convierte en el callback para no.
+
Podemos usar Function Expressions para escribir una equivalente función más corta.
+
function ask(question, yes, no) { if (confirm(question)) yes() else no(); } ask( "Do you agree?", function() { console.log("You agreed."); }, function() { console.log("You canceled the execution."); } );
Existe otra simple forma de crear funciones, que a menudo es mejor que las Function Expressions y se llama Arrow Functions, por que se ven de la siguiente manera.
+
let func = (arg1, arg2, ..., argN) => expression;
+
Esto crea una función que agrega n argumentos, luego evalúa la expression a la derecha y devuelve su resultado.
let sum = (a, b) => a + b; /* This arrow function is a shorter form of: let sum = function(a, b) { return a + b; }; */ alert( sum(1, 2) ); // 3
+
Como puedes ver (a, b) => a + b, significa una función que acepta 2 parámetros a y b. Al ejecutarse, se evalúa la expresión a + b y regresa el resultado.
+
+
Si solo tenemos un argumento, el paréntesis al rededor de los parámetros puede ser omitido, haciendo la versión todavía más corta.
+
+
let double = n => n * 2; // roughly the same as: let double = function(n) { return n * 2 } alert( double(3) ); // 6
+
+
Si no hay argumentos, los paréntesis quedan vacíos, pero ellos deben estar presentes:
+
+
let sayHi = () => alert("Hello!"); sayHi();
+
Las Arrow Functions pueden ser usadas de la misma manera como Function Expressions
+
Para poder crear dinámicamente una función.
+
let age = prompt("What is your age?", 18); let welcome = (age < 18) ? () => console.log('Hello!') : () => console.log("Greetings!"); welcome();
+
Arrow Functions pueden parecer muy diferentes y difíciles de leer al inicio, pero esto cambia rápidamente una vez que la estructura se adapta a los ojos.
+
Son muy convenientes para acciones simple de una sola línea, donde solo somos flojos para leer muchas palabras.
Dentro del código que hemos estado usando en los ejemplos anteriores puedes darte cuenta que utiliza el
+
console.log("Hello World");
+
El console.log sirve para mostrar algo en la consola de nuestro navegador, es muy común imprimir diferentes tipos de mensajes y la función log también lo es. Pero existen algunas funciones adicionales que también pueden ayudarnos como son info, warn y error.
+
Las funciones log e info son muy similares en que solo proveen información básica, y depende del navegador pueden distinguirse entre ellas al momento de ver el resultado.
+
Las que sí son diferentes son warn que además de marcar la advertencia en la consola también muestra un contador de las advertencias que se tienen para facilidad. En programación un warning es algo que requiere atender más no es urgente o necesario de modificar en el momento.
+
Por su parte error sirve para marcar en rojo y en un contador aparte la cantidad de errores reportados por esta salida. En programación un error es algo que requiere atención inmediata pues puede hacer que la funcionalidad no funcione como se espera o en un peor caso no funcione del todo
+
console.log("Hello World"); console.info("Clash of clans"); console.warn("This is a warning"); console.error("This is an error");
+
Por último y no menos importante está el assert, esta salida de consola simple, puede ayudarnos a realizar pruebas en nuestro código cuando esperamos un valor en particular. Esto nos permite tener una primera aproximación a automatizar las pruebas del navegador ya que lo que se recibe como valor es el resultado de una expresión, en caso de que la expresión verdadera no sucede nada, sin embargo en caso de ser false la consola lanza un error para que el desarrollador pueda ver el error.
Las dos impresiones en consola anteriores, no van a desplegar nada puesto que el resultado de las expresiones es verdadero en ambos casos.
+
Si queremos ver el error necesitamos realizar algo como lo siguiente, en donde la expresión sea falsa.
+
console.assert(2 == "1");
+
Ahora que conocemos las impresiones básicas, es probable que estar escribiéndolas a cada momento sea algo tedioso, pero podemos aplicar los conocimientos que ya tenemos y simplificar esto de la siguiente manera.
+
let cLog = console.log; let cInfo = console.info; let cWarn = console.warn; let cError = console.error; let cAssert = console.assert; cLog("Hello World);
+
Al usar la asignación de funciones por valor, podemos asignar cada tipo de salida a la consola en una variable más corta para escribir menos y poder depurar nuestro código de una manera más rápida.
+
No olvides utilizar los conceptos vistos como en el último ejemplo para hacer que tu código sea más limpio, legible y sobre todo funcional.
Los arreglos en javascript son iguales a otros lenguajes de programación, con la diferencia que aquí al momento de declarar un arreglo podemos también considerarlo como una lista. A diferencia de lenguajes de programación más estrictos esto significa que una vez definido el arreglo su tamaño podrá no ser fijo y podremos seguir agregando elementos. Esto es una función muy poderosa de los arrays en Javascript ya que nos permite trabajar directamente con estructuras de datos que al final hoy en día es lo que se necesita para manipular información.
Igualmente como en otros lenguajes de programación podemos iterar sobre los arreglos que hayamos declarado usando un ciclo for.
+
for (let i = 0; i < arreglo.length; i++) { console.log(arreglo[i]); }
+
Otra forma de iterar sobre los arreglos es con un tipo especial de ciclo for, pero este puede ser de 2 tipos, utilizando las palabras reservadas of e in, para el primero nos devuelve el elemento tal cual del arreglo que queremos revisar, y para el segundo nos devuelve el índice del elemento del arreglo.
+
for(let valor of arreglo) { console.log(valor); } for(let indice in arreglo) { console.log(indice); }
+
Aquí va a depender el caso que se quiera manejar pero en la industria ambos son ampliamente utilizados.
Los objetos parecerían un tema completamente aparte, pero si recuerdas en el lab1 de introducción al HTML, te hable sobre los medios actuales de información y como se utilizan el HTML, el XML, y el JSON.
+
Si has puesto atención hasta el momento te tengo una buena noticia, los objetos en Javascript son objetos JSON por default, por lo que para declararlos basta con realizar lo siguiente:
La forma estándar de JSON nos dice que la llave debe ser un string, pero observa que en el caso del ejemplo atributo1 esta más formado como una especie de variable, es importante destacar esto ya que si intentas copiar un objeto de javascript directamente sobre un JSON puedes llevarte una sorpresa en que no son compatibles y esto se debe al formato. Más adelante veremos como lidiar con esta situación, pero de momento observa como el objeto nos permite guardar información que queramos.
Ahora que en el curso empezamos a trabajar con el lenguaje JAVASCRIPT, es momento de hablar de un punto fundamental que puede que hayas o no escuchado algún momento y es el DOM.
+
El DOM es la razón fundamental de por que utilizamos Javascript dentro del desarrollo web y si queremos visualizarlo, es la razón fundamental de como conectamos el HTML, con CSS y con JS.
+
Antes de hablar del DOM, debemos mencionar las estructuras de datos que debiste haber visto o conocido en cursos anteriores. Las estructuras de datos nos permiten modelar información de un modo que no solo estandariza, sino también nos permite aplicar ciertas estrategias o algoritmos particulares para un funcionamiento más eficiente.
+
Dentro de las estructuras de datos tenemos los árboles, que modelan datos a través de un nodo y hojas o hijos que se van derivando de su misma estructura.
+
A esto es donde nos lleva el DOM, si analizamos un poco observaremos que nuestro código HTML en realidad lo es una estructura de datos en forma de árbol, pues cada etiqueta puede contener información de otras etiquetas y cada una de estas pueden tener un número ilimitado de hijos. De la misma manera las propiedades se amarran como hojas a las etiquetas para dar valor particular según sea el caso.
+
Por lo tanto el DOM viene como significado de Document Object Model, que en español podríamos resolver como Modelo de Objetos del Documento. Este es una interfaz que permite crear, editar o eliminar elementos del documento. También se pueden agregar eventos para hacer dinámico el documento, pero esto lo veremos un poco más adelante.
+
La manera más simple visualiza al DOM como un árbol de tres nodos. Un nodo representa un documento HTML.
+
Echemos un vistazo a este código de HTML para entender mejor la estructura del DOM.
Antes de empezar a ver métodos a diestra y siniestra, vamos a conocer la variable document, que desde el inicio está disponible para nosotros, y es esta variable de donde podemos partir para obtener todos los elementos del HTML.
+
Quizás un poco largo pero si hacemos un console.log() de document veremos que como resultados obtendremos literalmente todo nuestro HTML.
+
console.log(document)
+
Dentro de Javascript, vamos a encontrar varios métodos que nos permiten seleccionar un elemento del HTML dentro del document.
En HTML, los id se utilizan como identificadores únicos para los elementos HTML. Esto significa que no podemos tener el mismo nombre id para dos elementos diferentes. En ese sentido es lo mismo que en cualquier lenguaje de programación donde declaramos una variable y no podemos repetir el nombre de la misma.
+
Para ejemplificar el siguiente código sería incorrecto.
+
<p id="para">Soy un párrafo.</p> <p id="para">Soy otro párrafo.</p>
+
Para que el id funcione, debemos segmentar o nombrar individualmente cada párrafo.
+
<p id="para1">Soy un párrafo.</p> <p id="para2">Soy otro párrafo.</p>
+
Ya que hemos segmentado nuestros id ahora viene la forma de llamarlo desde Javascript.
+
document.getElementById("nombre de id va aquí")
+
Entonces el código final con el que podríamos llamar nuestro ejemplo sería el siguiente.
Ahora bien, observa que en la lista hemos declarado una propiedad class con nombre list, con esto podemos obtener toda la etiqueta usando el querySelector().
+
const list = document.querySelector(".list"); console.log(list);
+
El resultado en la consola será la etiqueta con todos sus elementos hijos.
+
<ul class="list"> <li>Titanic</li> <li>Jurassic Park</li> <li>El señor de los Anillos</li> <li>Star Wars</li> </ul>
+
Observa que al querySelector(), le estamos añadiendo un . y esto es por hacer referencia al nombre de la clase por tanto cuando llamemos a la clase debemos enviar como parámetro .list ó .{{nombre}}.
+
querySelector() es un método genérico el cual permite obtener tanto etiquetas generales, clases o igual que con getElementById identificadores. Para poder llamar un id deberás utilizar en vez del . el símbolo de #.
El query selector all encuentra todos los métodos que coinciden con el selector de css y devuelve una lista de todos esos nodos. Si volvemos con nuestro ejemplo anterior de:
+
<ul class="list"> <li>Titanic</li> <li>Jurassic Park</li> <li>El señor de los Anillos</li> <li>Star Wars</li> </ul>
+
Si quisiéramos encontrar todos los elementos li de nuestro ejemplo, podríamos utilizar el combinado de hijos > para encontrar todos los elementos hijos de ul.
Aquí veremos un resultado un poco diferente a los anteriores, si exploras tu navegador incluso verás más detalles. En este caso estas viendo todas las propiedades de los nodos hijos li .
+
{ "0": {}, "1": {}, "2": {}, "3": {} }
+
Como query selector obtiene todo, veremos que cuando hay varios hijos se modifica la forma de visualizar los datos. Si queremos ir elemento hijo por elemento hijo vamos a necesitar iterar el resultado. Para ello podemos hacer lo siguiente.
También podemos utilizar createElement() para agregar nuevos elementos al DOM.
+
Veamos el siguiente ejemplo añadiendo la etiqueta:
+
<h2>Elementos que se utilizan en desarrollo Web:</h2>
+
Con esta nueva etiqueta h2 queremos agregar una lista de elementos en la parte inferior.
+
Primero vamos a crear un elemento ul usando document.createElement(). Asignaremos eso a una variable llamada listaSinOrden.
+
let listaSinOrden = document.createElement("ul");
+
Después necesitaremos añadir el elemento usando el método appendChild().
+
document.body.appendChild(listaSinOrden);
+
Antes de avanzar observa como estamos haciendo para anexar directamente el ul después de de nuestra etiqueta h2, y esto lo hacemos sin la necesidad de hacer referencia al h2 como tal.
+
Para hacerlo accedemos al document y desde aquí podemos acceder a la etiqueta body, al utilizar appendChild() estamos anexando el elemento al final del elemento seleccionado, en este caso al final del body y por tanto adelante de nuestra etiqueta h2.
+
Ahora debemos agregar varios elementos li dentro del elemento ul usando nuevamente createElement()
+
let elemento1Lista = document.createElement("li"); let elemento2Lista = document.createElement("li"); let elemento3Lista = document.createElement("li");
+
Ojo: Aquí solo estamos creando las etiquetas
+
Después podemos utilizar la propiedad textContent para agregar texto para nuestros 3 elementos de la lista.
+
let elemento1Lista = document.createElement("li"); elemento1Lista.textContent = "HTML"; let elemento2Lista = document.createElement("li"); elemento2Lista.textContent = "CSS"; let elemento3Lista = document.createElement("li"); elemento3Lista.textContent = "JS";
+
El último paso es agregar el método appendChild() para que los elementos de la lista sean agregados al ul.
+
let elemento1Lista = document.createElement("li"); elemento1Lista.textContent = "HTML"; listaSinOrden.appendChild(elemento1Lista); let elemento2Lista = document.createElement("li"); elemento2Lista.textContent = "CSS"; listaSinOrden.appendChild(elemento2Lista); let elemento3Lista = document.createElement("li"); elemento3Lista.textContent = "JS"; listaSinOrden.appendChild(elemento3Lista);
Ya hemos modificado el HTML, pero la ventaja de utilizar el DOM, es que como ya mencioné nos da acceso a como se encuentra en ese momento el HTML incluyendo sus propiedades. Por lo mismo nos da acceso al estado del CSS de las etiquetas.
+
+
Nota: Recuerda el poder modificar CSS, no nos da acceso directo a crear estilos dentro de los archivos externos CSS. Sin embargo en usos más avanzados podemos crear etiquetas de style y agregarlas al DOM, aunque esto no es una buena práctica no hay una limitante sintáctica que nos impida hacerlo.
+
+
En este ejemplo vamos a cambiar el color de texto de un h3 de negro a azul usando la propiedad style. En vez de agregar directamente el html vamos a añadirlo de manera dinámica como vimos en el paso anterior.
+
let newH3 = document.createElement("h3"); newH3.textContent = "Hola soy un texto en color negro" document.body.appendChild(newH3);
+
Después usamos el querySelector() para obtener el h3.
+
const h3 = document.querySelector("h3");
+
Ahora vamos a utilizar ** h3.style.color** para modificar el color, nota que al acceder a la etiqueta podemos acceder a su propiedad style, y desde aquí podemos aplicar el css que necesitemos.
+
h3.style.color = "blue";
+
Por último quizás el texto del h3 ya no sea tan representativo, que tal si lo modificamos como ya hemos visto anteriormente.
+
h3.textContent = "Ohh no me cambiaron a azul."
+
Si observas el resultado en el navegador verás que el cambio se realizó correctamente. Pero si somos curiosos que pasarías si imprimimos en consola el valor de newH3.
+
console.log(newH3)
+
Verás que de la misma manera newH3 se actualiza con el nuevo valor, y esto es por que los valores no se guardan de manera estática sino por referencia unos de otros. En el día a día no vamos a trabajar de esta manera, pero es un buen ejemplo para entender como se modifican las cosas.
+
Al final verás que en cuanto al style podrás modificar prácticamente cualquier propiedad de css que necesites, desde background-color, border-style, font-size, etc.
Los eventos son cosas que pasan en el documento que estás programando, el cual se encarga de avisarte para que tu código pueda hacer algo al respecto.
+
Un ejemplo simple sería, si el usuario hace un clic en un botón de la página lo normal es reaccionar a esa acción y realizar algo a partir de ello.
+
Existen diversos tipos de eventos, y depende más del desarrollador identificar que eventos quiere obtener del usuario. Algunas ideas que puedes obtener para ello son las siguientes:
+
+
El usuario selecciona, hace clic o pasar el ratón por encima de cierto elemento.
+
El usuario presiona una tecla del teclado.
+
El usuario redimensiona o cierra la ventana del navegador.
+
Una página web terminó de cargarse.
+
Un formulario fue enviado.
+
Un vídeo se reproduce, se pausa o termina.
+
Ocurrió un error.
+
+
Para este ejemplo agregaremos el siguiente botón, nuevamente de manera dinámica.
+
let newButton = document.createElement("button"); newButton.textContent = "Mostrar Alerta" newButton.setAttribute("id","btn"); document.body.appendChild(newButton);
+
Si bien estamos utilizando el código de los ejemplos anteriores para agregar un button observa que utilizamos el método setAttribute para agregar el id a nuestro botón, esto sería el equivalente a que en el HTML declaráramos la etiqueta de la siguiente manera:
+
<button id="btn">Mostrar Alerta</button>
+
Ahora solo para practicar vamos a obtener el botón utilizando su ID.
+
const button = document.getElementById("btn");
+
Para poder agregar manejo de eventos a cualquier etiqueta del HTML o dicho de otra forma, para poder agregar eventos a cualquier elemento del DOM, utilizaremos el método addEventListener(). Este método asociará un evento a la lista de eventos estandarizados que se tienen para los navegadores, si tienes duda en que tipo de eventos se pueden utilizar checa la siguiente página, aquí verás el listado completo.
+
Como nosotros queremos asociar un click a nuestro botón, deberemos hacer lo siguiente.
+
button.addEventListener("click", () => { alert("Gracias por el click"); });
+
El resultado es que al hacer clic en nuestro botón se despliega una alerta mostrando el mensaje. Como puedes concluir, realizar acciones desde Javascript lo es todo para hacer páginas como las que existen hoy en día. Siguiendo los principios de diseño, manteniendo los estándares y siendo simple al momento de hacer las cosas podemos tener desarrollo complejos y elegantes que sean dignos de premios.
+
Si quieres ahondar más en el tema te recomiendo el siguiente artículo el cual crea varios proyectos de Javascript paso a paso para ir entendiendo el potencial que tiene, no olvides que la práctica es la única manera de entender como funciona y sin ello te estarás quedando atrás.
En el laboratorio 2 estuvimos trabajando con la introducción al control de versiones, desde aquí vimos el diagrama y los elementos principales que componen la herramienta de Git. A manera de repaso los veremos nuevamente pues serán fundamentales en el siguiente paso.
El working tree es el pedazo de proyecto en cualquier momento (usualmente es el momento actual). Cuando agregas o editas código, modificas el working tree.
+
El staging area es donde se colocan los cambios del working tree antes de hacerlos permanentes.
+
Un repository es la colección de cambios permanentes (commits) realizados a través de la historia del proyecto. Típicamente, existe un repositorio remoto (Github,Gitlab,Bitbucket,etc.) y muchos repositorios locales, uno para cada desarrollador involucrado en el proyecto al menos.
+
+
Ahora bien, si esta es la forma de cliente-servidor que tenemos para visualizar en donde se encuentran nuestros archivos, es importante mencionar que el siguiente paso para trabajar con control de versiones es a través del manejo de branches o ramas.
+
Desde este punto toma la siguiente analogía, eres dueño del tiempo y el espacio en tu proyecto, hasta ahora hemos trabajado con el espacio, es hora de incorporar el tiempo. Pero recuerda el manejo de estas 2 dimensiones debe realizarse con responsabilidad pues en caso contrario terribles cosas podrán pasar.
+
Antes de entrar de lleno recuerda los 2 valores fundamentales que te transmití para el manejo de versiones que son:
+
+
Disciplina - Aquí viene la responsabilidad de la persona en asegurarse que se encuentra con los cambios más recientes y no forzar a agregar ningún cambio con comandos extraños que puedan llegar a afectar la línea del tiempo del proyecto o los archivos del mismo repositorio de manera remota.
+
Comunicación - Cuando algo extraño suceda siempre es mejor invocar al equipo para que entre todos apoyen a resolver los conflictos, dejarlo en manos de una sola persona puede llevar a perder archivos importantes.
+
+
+
Nota: Recuerda que puedes recuperar un archivo que haya sido commiteado al repositorio remoto siempre y cuando no borren los apuntadores de commits que vienen, como regla específica si ya se subió mal, mejor arreglarlo en commits posteriores, si el error compromete seguridad por regla mejor crear un nuevo repositorio y un nuevo historial de commits.
+
+
Ahora bien, las ramas te permiten desarrollar características, funcionalidades o features, corregir errores, o experimentar con seguridad las ideas nuevas en un área contenida de tu repositorio.
+
Siempre puedes crear una rama a partir de una rama existente. Habitualmente, puedes crear una rama nueva desde la predeterminada de tu repositorio. Podrás entonces trabajar en esta rama nueva aislado de los cambios que otras personas hacen al repositorio. Ala rama que crear para construir una característica se le conoce como rama de característica o rama de tema.
+
Dependiendo de tu equipo puede o no ser común trabajar con los comandos de creación de ramas. Aquí veremos la forma más tradicional tomando como base que tenemos un repositorio vacío.
+
+
Para ir ubicándonos, vamos a ver que el branch default para GitHub es main pero si nos acercamos al menú que aparece podremos desplegar todos los branches que tenga nuestro repositorio. Nuevamente como apenas vamos comenzando solo existe main.
+
+
Ahora vamos a clonar nuestro repositorio en nuestra computadora local, elige una carpeta donde estará almacenado tu repositorio dentro de tu máquina y ejecuta el comando git clone seguido de la url del repositorio, recuerda que puedes usar https o ssh, la recomendación es que utilices ssh por facilidad y seguridad.
+
+
Dentro de la consola entonces tendrías el siguiente comando
+
git clone {{url https o ssh del repositorio}}
+
El resultado de tu terminal será algo como lo siguiente:
Nota: Si utilizas la interfaz gráfica de git, también es válido, y podrás realizar el mismo procedimiento que veremos a nivel de comandos, solo necesitas encontrar los botones que realicen las acciones que estamos realizando.
+
+
Como hemos clonado la carpeta del repositorio debemos entrar a ella y nos encontraremos de lleno en nuestro repositorio.
+
+
En mi caso se creo un archivo README.md al momento de crear el repositorio, si no lo tienes no te preocupes te invito a que lo empieces a crear en este momento.
+
El contenido de mi archivo es el siguiente
+
# git-learning-branches Este repositorios es para demostrar el uso de los branches y el gitflow simplificado
+
Que para efectos prácticos es el nombre y la descripción de mi repositorio.
+
Hasta este punto ya podemos empezar a trabajar y algo que vamos a notar con la descripción que tengo es que me equivoque al escribir repositorio y escribí repositorios, aprovechemos este momento para empezar a trabajar con los branches.
+
Idealmente veremos que existen 2 tipos de branches, los de control y los de trabajo.
+
+
Control - Este tipo de branches no deben tener commits directos de los miembros del equipo, quizás cuando se está configurando se pueden tener algunos commits justo a medio de configuración, pero la tendencia es evitar escribir en ellos. Esto ayuda a mantener la calidad de los productos de trabajo, ejemplos de branches de este tipo serían main, develop, release.
+
Trabajo - Este tipo de branches contienen el trabajo y por tanto los commits de los miembros del equipo, es muy común que aquí se genere un historial completo del trabajo de la persona, se recomienda ampliamente realizar commits de manera continua para evitar perder cambios y también se recomienda siempre estar sincronizándolos con las últimas versiones de main o develop.
+
+
Regresando a nuestro repositorio vamos a modificar el archivo entonces con lo siguiente:
+
# git-learning-branches Este repositorio es para demostrar el uso de los branches y el gitflow simplificado.
+
+
No olvides al final agregar el salto del línea.
+
+
Ahora, si guardamos el archivo y revisamos en nuestra terminal podemos escribir lo siguiente:
+
git status
+
El resultado debería aparecer como lo siguiente:
+
Por un lado VSCode nos detecta que ya existen cambios dentro del archivo.
+
+
On branch main Your branch is up to date with 'origin/main'. Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: README.md no changes added to commit (use "git add" and/or "git commit -a")
+
Recuerda que de momento nuestros cambios están en el working tree, primero necesitamos agregarlos al staging area y después debemos firmarlo con un commit.
+
Pero alerta veremos que al hacer el git status, nos regresa en que branch nos encontramos actualmente.
+
On branch main Your branch is up to date with 'origin/main'.
+
Esto es justo lo que no queremos que suceda pues main debería ser intocables, por lo tanto debemos crear un nuevo branch que será develop, si recuerdas te dije que develop tampoco debería de incluir commits, pero como es la configuración de la rama para este primer caso será permitido.
+
Entonces para crear un nuevo bran realizaremos lo siguiente
+
git checkout -b "develop"
+
De manera automática nuestro repositorio local crea el nuevo branch y nos da el siguiente resultado.
+
Switched to a new branch 'develop'
+
Éxito, si volvemos a ejecutar
+
git status
+
El resultado entonces será:
+
On branch develop Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: README.md no changes added to commit (use "git add" and/or "git commit -a")
+
Observa que nuestro archivo README.md sigue en el working tree, pero ahora el resultado nos dice que estamos en el branch develop On branch develop.
+
Entonces ahora sí podemos agregar el archivo y hacer el commit de la siguiente manera:
+
git add -A git commit -m "fix: Se corrige el typo de la descripción del README.md"
+
Algo que también es importante mencionar, es que observa que mi commit no tiene una gran cantidad de trabajo, un buen programador hace iteraciones cortas en su trabajo para evitar perder sus cambios, esta es la diferencia de pasar horas sentado tratando de resolver algo y poder levantarte cada x tiempo de la silla para obtener aire.
+
El resultado de nuestro comandos será entonces:
+
[develop 5a06f6d] fix: Se corrige el typo de la descripción del README.md 1 file changed, 1 insertion(+), 1 deletion(-)
+
Y si volvemos a ejecutar un
+
git status
+
El resultado será entonces
+
On branch develop nothing to commit, working tree clean
+
Ahora recuerda el diagrama del funcionamiento de git y pregúntate. ¿En qué parte se encuentra nuestro archivo?
+
Ya pasamos del working tree al staging area usando el git add, de ahí firmamos el cambio del staging area con un commit hacia nuestro repositorio local.
+
Por tanto la respuesta es que nuestro cambio se encuentra en el repositorio local, hasta este punto debemos seguir teniendo cuidado por que mientras no lo subamos al repositorio remoto, corremos el riesgo de perder nuestro cambios. Ejemplo: si pierdes tu computadora, si tu disco duro falla, etc. Son razones por las cuales podrías perder tus cambios aunque ya hayan sido firmados.
+
Por tanto vamos a subir el archivo a la nueva rama, normalmente cuando la rama existe debemos realizar un:
+
git push origin
+
Pero si lo hacemos veremos el siguiente resultado:
+
fatal: The current branch develop has no upstream branch. To push the current branch and set the remote as upstream, use git push --set-upstream origin develop To have this happen automatically for branches without a tracking upstream, see 'push.autoSetupRemote' in 'git help config'.
+
Esto sucede por que al haber creado el branch de develop lo tenemos creado solamente en nuestro repositorio local, es más si nuevamente nos vamos a github, y desplegamos la lista de branches, veremos que este aún no existe.
+
+
Esto es normal y justo lo que queremos es crear de manera remota el nuevo branch, por lo que si leemos el resultado de la terminal verás que nos da el comando que necesitamos para subir nuestros cambios y crear el nuevo branch, en este caso sería:
+
git push --set-upstream origin develop
+
Este comando se ejecuta solo 1 vez al crear un nuevo branch ya que de aquí en delante puedes usar git push origin sin problema. El resultado entonces será:
+
Enumerating objects: 5, done. Counting objects: 100% (5/5), done. Delta compression using up to 16 threads Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 323 bytes | 323.00 KiB/s, done. Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 remote: Resolving deltas: 100% (1/1), completed with 1 local object. remote: remote: Create a pull request for 'develop' on GitHub by visiting: remote: https://github.com/black4ninja/git-learning-branches/pull/new/develop remote: To github.com:black4ninja/git-learning-branches.git * [new branch] develop -> develop branch 'develop' set up to track 'origin/develop'.
+
Y sí actualizamos la página de GitHub veremos que no solo ya nos aparece develop, sino que además GitHub nos da una alerta que un nuevo branch fue creado.
+
+
Aquí podemos podemos examinar varias cosas.
+
Si cargo el branch de main, veré que los cambios siguen como eran inicialmente.
+
+
Pero si selecciono el branch de develop veremos que tiene el contenido actualizado de nuestro archivo README.md.
+
+
Aquí esta todo el poder del control de versiones, pues pensemos un minuto lo que esto significa, main no se ha modificado, eso implica que si tuviera un sistema está en su versión más estable, quizás develop, necesite ser probado, pero eso no va a impedir que el equipo pueda bajar los cambios necesarios en sus máquinas o automatizar un set de pruebas automáticas sin comprometer la version final a los usuarios finales.
+
Con esto ya tienes el conocimiento de trabajar con ramas en tus repositorios, ahora lo que veremos en los siguientes pasos es como estandarizar nuestra forma de trabajo utilizando todo lo que hemos aprendido hasta ahora.
Como ya mencionamos es importante que al trabajar con ramas el equipo se asegure que al cambiar entre una y otra se baje la última versión remota, pues el repositorio remoto es la última fuente de la verdad, si tratamos de forzar cambios, entonces seguramente romperemos la versión para todo el equipo. En términos de daños cuantificables, siempre es mejor que solo 1 persona tenga conflictos a todo el equipo y más si eso compromete el repositorio remoto.
+
Vamos a regresar a nuestra terminal y vamos a empezar a cambiar entre branches.
+
En ocasiones podemos olvidar el branch en el que nos encontramos y esto es perfectamente normal, previamente vimos como con git status podemos ver el branch en el que nos encontramos.
+
Pero git tiene un comando que nos permite ver no solo en que branch estamos sino también que branches tenemos actualmente. Antes de hacer cualquier cosa es recomendado hacer un git pull origin para obtener los últimos cambios incluidos nuevos branches remotos que hayan creado nuestros compañeros de trabajo.
+
Por tanto los comandos a ejecutar serían:
+
git pull origin git branch
+
En este caso particular git pull origin no va a realizar ningún cambio pues nuestro repositorio local es igual al repositorio remoto. Pero, git branch nos mostrará la lista de ramas disponibles en nuestro repositorio local que en este caso coinciden con el remoto.
+
* develop main
+
También veremos que se nos marca en un asterisco, el branch donde nos encontramos actualmente, por lo que si necesitamos pasar de develop a main basta con ejecutar el siguiente comando:
+
git checkout main
+
El resultado:
+
Switched to branch 'main' Your branch is up to date with 'origin/main'.
+
Si recuerdas, cuando ejecutamos el comando que creaba la rama de develop ejecutamos git checkout -b "develop", la bandera -b sirve para crear una nueva rama seguido del nombre de la misma, cuando utilizamos git checkout directamente es por que ya tenemos la rama que vamos a utilizar.
+
Aunque nos dice que tenemos la última versión no está por demás tener la buena práctica:
+
git pull origin git branch
+
El resultado final:
+
develop * main
+
Y ahora veremos que nos marca el asterisco que estamos en main y nos aseguramos con el pull de tener los últimos cambios siempre.
Dentro del mundo del control de versiones existen varias estrategias recomendadas para trabajar con branches en un repositorio. La que veremos en este laboratorio se conoce como GitFlow y de está existen a su vez varias versiones, con tal de seguir un estándar pero tratando de no complicarnos demasiado estaremos aplicando el GitFlow simplificado.
+
Este GitFlow simplificado es un mapa de como debemos trabajar en un repositorio. Esto nos da un estándar de como manejar los branches, ejecutar commits, etc.
+
+
Nuevamente y haciendo hincapié, recuerda que las ramas de master o main y develop no se tocan para trabajo continuo, solo para configuraciones o en su defecto no se tocan.
+
Las configuraciones de GitHub te permiten bloquear estas ramas de recibir commits, sin embargo pueden limitarse al plan que tienes, por lo que más que la automatización establece con tu equipo que no deben realizarse commits a estas ramas.
+
Por lo mismo vamos a realizar algo similar a lo que hicimos previamente en develop pero vamos a simular el trabajo continuo de un día a día.
+
Vamos a pensar que nos toca trabajar en el README.md y que además nos solicitan un archivo CONTRIBUTING.md en donde especifiquemos la estrategia para desarrollar commits y branches dentro del equipo. Además nos solicitan que todo el desarrollo se realice en Inglés.
+
Antes que nada vamos a crear un nuevo branch de Trabajo.
+
git checkout -b "feature/contributing-standard"
+
Switched to a new branch 'feature/contributing-standard'
+
Y ahora sin haber realizado ningún cambio vamos a crear el nuevo bran en el repositorio remoto. Si no recuerdas específicamente el comando recuerda que puedes hacer un git push origin y te marcará error que el branch actual no existe en remoto.
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 remote: remote: Create a pull request for 'feature/contributing-standard' on GitHub by visiting: remote: https://github.com/black4ninja/git-learning-branches/pull/new/feature/contributing-standard remote: To github.com:black4ninja/git-learning-branches.git * [new branch] feature/contributing-standard -> feature/contributing-standard branch 'feature/contributing-standard' set up to track 'origin/feature/contributing-standard'.
+
Con esto hemos visto que no se necesitan tener cambios para crear ramas en el proyecto, procura como buena práctica crear tus ramas de trabajo antes de empezar a trabajar de manera normal.
+
+
Pero, ohh no hemos cometido un error fatal, si te diste cuenta al momento de crear el branch estábamos en el branch de main y no de develop, por lo que al crear nuestra nueva rama el contenido de README.md es el original y no contiene el cambio que realizamos.
+
Caos, muerte y destrucción, pero es justo por este tipo de detalles que se recomienda hacer iteraciones cortas, ya que el error lo voy a tener en 2 líneas, pero imagina que es 1 mes de trabajo entre versiones, entonces ahí si puedo tener un problema.
+
Aquí tengo 2 opciones, si el cambio entre versiones es muy amplio, lo mejor sería borrar mi branch remoto y volver a empezar, pero hay algo que podemos hacer para arreglarlo.
+
Asegurándonos que estamos en nuestro nuevo branch,dentro de nuestra consola vamos a realizar un:
+
git pull origin develop
+
El resultado es que se integrarán todos los commits de develop que no existan en main:
Y por ahora hemos evitado la crisis. Pero de mi parte si los cambios hubieran sido mayores y al momento de ejecutar la versión local algo falla, lo más probable y más confiable es volver a crear otro branch asegurándome que ahora si empiece por develop.
+
Algo que es vital entender es que sí debemos tener cuidado al ejecutar el git checkout -b, pues va a depender el branch en el que estemos es desde donde se creará la rama.
+
Por eso es importante que antes de crear una nueva rama nos coloquemos desde donde queremos crear la nueva versión.
+
Ya que tenemos nuestra rama creada, podemos ahora sí trabajar en ella como lo haríamos en el día a día.
+
Primero vamos a modificar el título del README.md por algo más legible. Cambia la línea 1 por lo siguiente:
+
# My Git Learning of branches
+
Vamos a considerar que este cambio representa un espacio de horas de trabajo, ahora realizamos nuestro commit como normalmente lo haríamos:
+
git add -A git commit -m "feature: Changed title README.md and started migration to English language." git push origin
+
+
Nuestra versión fue actualizada correctamente.
+
Sigamos ahora traduciendo el texto de la descripción para nuestro README.md pensando que es otro espacio de horas de trabajo.
+
This repository is to demonstrate the use of branches and simplified gitflow.
+
Nuevamente finalizamos haciendo commit de nuestros cambios.
Perfecto, ahora nos hicieron la solicitud de agregar un archivo CONTRIBUTING.md al repositorio donde vamos a establecer el estándar para este repositorio para la generación de commits y branches.
+
Este archivo es un poco extenso pero te lo dejo aquí, utilízalo como base para el repositorio de tu equipo y futuros proyectos.
+
+
Ya que tengo mi archivo CONTRIBUTING.md, nuevamente vamos a llamarlo un día, y hacemos el commit correspondiente.
+
git add -A git commit -m "feature: Added CONTRIBUTING.md with standard specification on how team must work with branches, commits and additional stuff." git push origin
+
+
Finalmente nos hace falta agregar una acceso rápido al archivo CONTRIBUTING.md desde nuestro archivo README.md. Por lo que actualizaremos el archivo a lo siguiente:
+
# My Git Learning of branches This repository is to demonstrate the use of branches and simplified gitflow. --- ## Contributing Please see the [Contributing Guide](CONTRIBUTING.md)
+
No olvides el salto de línea al final.
+
Por último vamos a hacer commits de los últimos cambios a nuestra actualización.
+
git add -A git commit -m "feature: Linked CONTRIBUTING.md file with README.md." git push origin
+
Excelente, hemos finalizado nuestra iteración de trabajo.
+
+
Dentro de GitHub también podremos ver que hemos realizado 6 commits desde este branch, si seleccionamos esta parte nos llevará a la siguiente vista.
+
+
Aquí veremos literalmente todos los commits que hemos realizado durante el tiempo de vida a este branch.
+
Si nos cambiamos a otro branch, por ejemplo develop observa que no existen estos cambios aún.
+
+
Con esto ya tenemos todo listo para empezar con el último paso para poder trabajar con nuestro repositorios y equipos y es unificar el trabajo.
Hasta el momento hemos visto comandos y estrategias diferentes para trabajar con nuestros archivos, realizar nuestros commits y crear ramas, pero ahora viene la parte más engorrosa de todo el proceso, unificar el tiempo y el espacio.
+
Si bien existen los comandos para que puedas hacer merge entre las ramas directamente, hoy en día se considera una mala práctica hacerlo. Si bien entendemos que vamos empezando en este mundo de control de versiones, es mejor que empieces usando los estándares de la comunidad de desarrollo para que puedas ser parte de dicha comunidad.
+
Otro punto a favor de por que no vemos los commits de merge branches desde la línea de comando es que existe un riesgo real de que unifiquemos cosas que no son necesarias. Ya lo viste en el caso donde me equivoque en el branch, pero lo peor que puede pasar es que mi versión local se vuelva corrupta y tenga que clonar mi repositorio local nuevamente, pero en el caso de hacer un merge con comandos entre ramas puede llevar a que corromper el repositorio remoto y por tanto dar un gran dolor de cabeza al equipo.
+
Por ello vamos a utilizar una técnica que va a evitar que realicemos errores o al menos nos va a hacer más difícil poder hacerlos, y es una estrategia de Pull Request.
+
Dentro del archivo CONTRIBUTING.md viene el link oficial de GitHub, de como crear un pull request, pero esto no es exclusivo de GitHub, todas las plataformas que usan control de versiones tienen incorporada la herramienta de Pull Request para unificar cambios.
+
Un Pull Request no es nada más que realizar un aviso al repositorio de que se intentan realizar unión de ramas, esto permite al equipo revisarle a la persona que es lo que se intenta realizar y en cuestión de código revisar si se siguen las buenas prácticas de programación del equipo.
+
Algo importante es que un Pull Request engloba una rama en particular que queremos combinar con otra específica, y por tanto una rama es un conjunto de commits que incluyen varios cambios realizados.
+
Para nuestro ejemplo vamos a querer realizar un pull request desde nuestro branch feature/contributing-standard hacia develop.
+
Para evitar rollos entre comandos de GitHub empezaremos a ver los Pull Request directamente desde la página, pero es posible crearlos directamente desde terminal, esto no lo abarcaremos por ahora, con entender el concepto es más que suficiente para que puedas trabajar.
+
Empecemos observando que cada vez que hacemos un commit en la rama, GitHub nos da una notificación de que un cambio se realizó y desde aquí podemos crear el Pull Request.
+
+
La otra forma, es en la parte superior vienen las opciones del repositorio y una de ells es el menú de Pull Request. Si la notificación no apareciera desde aquí podemos crear un Pull Request.
+
+
Como ya dijimos un Pull Request es el intento de hacer un merge entre una rama y otra. Por lo que al crear uno nuevo veremos una ventana como lo siguiente.
+
+
Esto me aparece si seleccione crear el Pull Request desde el menú, si seleccionaste la notificación, puede que te pre cargue algunos datos, pero vamos desde el inicio.
+
La parte más importante del Pull Request es seleccionar a donde quiero hacer el merge y desde que branch, esto lo haremos en el siguiente espacio.
+
+
Aquí es muy importante que seleccionemos lo necesario ya que si nos equivocamos, GitHub pudiera encontrar conflictos entre archivos y sería forzar el cambio. Si bien 1 persona es quien escribe el Pull Request, es responsabilidad del equipo revisar que la información sea correcta.
+
+
Vamos a seleccionar que vamos a mergear en develop nuestro branch feature/contributing-standard. Y observa que al seleccionarlos, nos aparece un Able to merge. Si se encontrarán conflictos entre los archivos puede darse a 2 razones:
+
+
La versión de feature/contributing-standard no esta actualizada a la última versión de develop.
+
Seleccionaste un branch para mergear diferente al esperado.
+
+
En caso de existir diferencias entre la última versión de develop y tu rama actual que quieres mergear no lo olvides.
+
Realiza un pull desde tu rama actual a la rama que quieres hacer merge, esto descargará todos los nuevos cambios. Por ejemplo:
+
git pull origin develop
+
+
Nota: Hacer esto es probable que te genere conflictos entre archivos si otros miembros del equipo tocaron archivos que tú también utilizaste. Aquí es cuando entra la comunicación y avisa a las personas correspondientes para resolver los conflictos, no se vale solo quedarse con tus cambios.
+
+
De momento no tenemos conflictos entonces daremos clic en el botón Crear Pull Request
+
Esto nos creará una vista como la siguiente:
+
+
+
En la primera parte tenemos un espacio para describir lo que hemos realizado.
+
En la segunda parte veremos que se hace la lista de todos los commits que contiene ese branch.
+
Lo siguiente que debemos hacer es crear la descripción de nuestro Pull Request.
+
Una muy mala práctica es solo poner una frase corta, los equipos de desarrollo más experimentados tienen plantillas donde se incorpora una explicación de los cambios, checklist para verificar que su proceso de desarrollo y calidad se realiza correctamente, e incluso algunos agregan fotos o videos para que la o las personas que revisen el Pull Request entiendan más fácilmente de que se está hablando.
+
Dar información es más fácil a que las personas adivinen que estabas haciendo.
+
Una ventaja es que el contenido de este espacio recibe lenguaje markdown por lo que puedes hacer muchas combinaciones cuando empiezas a conocer el lenguaje.
+
En nuestro caso vamos a agregar lo siguiente a manera de estándar.
+
<!--- Provide a general summary of your changes in the Title above --> ## Description This feature adds a CONTRIBUTING.md file that gives a standard for the team on how to generate commits and branches with git, and also some additional information that can be used to contribute to this repository. ## Motivation and Context This feature follows the requirements [001]() that can be found on the Excel sheet of the management log of the team. ## Where can I try this functionality? This feature is not intended for development but to give the team a guide on how to contribute to the project. ## Screenshots (if appropriate): Not available ## Types of changes <!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [X] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) ## Checklist: <!--- Go over all the following points, and put an `x` in all the boxes that apply. --> - [X] I have included links to all Excel Sheet or Traceability matrices (if applicable) - [X] I have added a link to this PR to the Excel sheet - [ ] I have updated `CHANGELOG.md` accordingly. (if applicable) - [ ] Another developer has performed a code review on this PR and approved it - [ ] Quality Assurance has taken place by a team member not directly involved in developing this PR. - [ ] I have updated the related Excel sheet and will move this to "done" after merge & deploy.
+
Al finalizar actualizamos el Pull Request y el resultado se debería ver como el siguiente:
+
+
Date el tiempo para escribir pull requests, de lo contrario a tu equipo le costará trabajo entender lo que realizaste.
+
Hasta este punto todo lo hemos realizado como nosotros mismos, el último paso necesario para la persona que realizó el Pull Request es avisar que hay uno nuevo y que se necesita revisar.
+
+
Nota: Es responsabilidad de la persona que crea el Pull Request insistir en que el equipo revise el trabajo, pero no siempre estamos todos disponibles todo el tiempo, por lo que busca la mejor estrategia con tu equipo para que no pase mucho tiempo.
+
+
+
Nota 2: Algo muy común que puede pasar es que varios miembros del equipo realicen Pull Request al mismo tiempo, uno de los conflictos que tienen al hacer esto es que todos quieren mergear su trabajo a la vez. Pero los Pull Request son una fila cuando se atiende uno y se hace merge, el siguiente debe bajar la última versión, resolver conflictos y atenderse. Como ya dije, lo más común es hacerme responsable de mi trabajo pero olvidar a los demás, esto es trabajo en equipo, comuníquense efectivamente para llevar un buen control de actualizaciones para evitar algún problema entre archivos.
+
+
Los siguientes pasos los realizaremos en el supuesto de que somos otro miembro del equipo que va a revisar el Pull Request que acabamos de subir.
+
Si entramos al repositorio el miembro del equipo nos debió compartir el link al Pull Request, de lo contrario podemos encontrarlo desde el menú de Pull Request, cuando hay pocos no pasa nada pero cuando hay varios es mejor que nos indiquen exactamente donde está.
+
+
Al entrar veremos el detalle que ya vimos previamente,el trabajo de alguien que va a revisar el Pull Request, es ver que la información del mismo tenga sentido, se entienda la funcionalidad que se está realizando y revisar el código.
+
La primera parte vamos a asumir que es correcta, por lo que vamos a revisar el "código".
+
Aquí vamos a seleccionar de las opciones en la parte superior la que dice Files changed.
+
+
Aquí nos va a aparecer una vista mostrándonos los cambios y los archivos nuevos, y en particular veremos que aparece primero el CONTRIBUTING.md, la posición dependerá de la estructura de tu proyecto.
+
Lo ideal al revisar el código es ver cada cambio, entenderlo y en caso de que algo no quede claro crear un comentario y una revisión para que la persona responsable haga el chequeo pertinente. Ahora bien, estos cambios son sugerencias de mejora, si aplican o no depende del responsable del Pull Request o justificar su acción o realizar el cambio pertinente.
+
En mi caso voy a ir a la línea 25 y al seleccionar veremos que puedo escribir un comentario para la persona y daremos clic en Start Review.
+
+
Lo que va a suceder es que se agregará el comentario y se notificará al responsable que existe una revisión pendiente.
+
+
La persona que revisa podrá decidir si revisa lo que resta del código o esperar a que se realice el cambio para hacer una nueva revisión.
+
En mi caso seguir revisando el código. Cuando finalice el archivo CONTRIBUTING.md puedo marcarlo como revisado.
+
+
Termino la revisión y veo que no hay nada más por lo que marco ambos archivos como revisados.
+
+
Ahora lo que debo hacer es terminar la revisión, en la parte superior tengo un botón que dice Finish your review.
+
+
Aquí voy a poder agregar un comentario global a toda la revisión y voy a poder seleccionar que sigue:
+
+
Comment - Solo agregará los comentarios pero le permitirá a la persona hacer merge de sus cambios.
+
Approve - Aprobar los cambios para hacer merge.
+
Request changes - Solicitar cambios al responsable y todavía no se puede hacer merge.
+
+
Por default, los branches a mergear no vienen bloqueados, por lo que es posible hacer merges sin revisión, ten mucho cuidado con esto siempre realiza un proceso de revisión y estandariza con tu equipo cuales son los pasos a seguir al momento de hacer un Pull Request.
+
Como en mi ejemplo soy yo mismo no me deja ni aprobar ni solicitar cambios, así que de momento solo vamos a comentar.
+
+
Damos clic en Submit Review y ahora volvemos cmo responsables del Pull Request.
+
Al entrar nuevamente al Pull Request veremos que se va agregando una conversación, con todos los comentarios echos por el equipo, si necesitara realizar cambios igualmente GitHub me avisaría.
+
+
Aquí nuevamente, es responsabilidad de la persona atender o no los cambios, pero la realidad es que siempre es bueno al menos contestar si no se ejecuta algún cambio y justificar el por que. Simulando la conversación entre el equipo pasaría lo siguiente.
+
+
Al final terminamos la conversación con RESOLVE CONVERSATION y pasamos al siguiente comentario.
+
En este Pull Request ya hemos realizado todo lo necesario, entonces como responsables del mismo podemos ir al resumen y prepararnos para el último paso hacer el merge.
+
+
Estrategias de Merge (merge, rebase, squash+merge)
+
Antes de terminar con el PR, vamos a resolver uno de los mitos en el control de versiones, las estrategias de merge.
+
Dentro de Git, tenemos varias opciones para fusionar ramas, y cada una hace algo diferente, es importante conocerlas.
+
A nivel general las estrategias de merge es la forma en la que Git tratará los commits de la rama que estamos fusionando. No hay una sola forma válida, cada estrategia tiene sus pro y sus contras, algunas empresas optan por ciertas estrategias que otras por conveniencia.
+
Vamos a ver cada estrategia en particular. Veremos que tenemos como estrategias el merge, el rebase y el squash+merge.
+
+
+
Merge - Es la más amigable de todas las estrategias, esta genera una visualización de como se unifica la rama a través de los commits sin alterar el historial de commits de la rama a fusionar.
+
Rebase - Es la más agresiva de las estrategias, aquí se empuja el historial de commits al final de la fila en la rama a fusionar, aquí se debe tener cuidado pues si no se está en la última versión se generarán conflictos importantes.
+
Squash+merge - Esta estrategia es sencilla, parecida al rebase, empuja los cambios al final del historial de la rama fusionada, peros con la diferencia al rebase que combina todo el historial de commits de la rama a fusionar en uno solo, por lo que tendremos commits que en su detalle incluirán commits de viejos de la rama a fusionar.
+
+
Como puedes ver lo único importante de la estrategia de merge es como queremos que se visualice el historial de commits al momento de hacer la fusión.
+
Para mi pull request yo voy a seleccionar la estrategia de merge pues prefiero mantener la visualización de todo el historial y por donde va pasando. Pero esto no significa que se la única estrategia que utilizó, con mis clientes en ocasiones utilizo la estrategia de rebase por la simplicidad del historial de commits.
+
Habla con tu equipo y definan que estrategia utilizarán para su proyecto.
+
+
Nos aseguramos que seleccionamos la estrategia adecuada y damos clic en Merge Pull Request.
+
+
Se nos pedirá una confirmación del merge. Y finalmente habremos terminado.
+
+
Aquí recuerda lo que te dije, este branch que finalizamos es de Trabajo por lo que deberíamos borrar el branch.
+
+
Si algo malo sucediera, nota que puedes restaurar el branch o incluso revertir el merge, ten cuidad que sea solo al momento, ya que si lo haces más adelante nuevamente puedes poner en riesgo el repositorio remoto.
+
Si regresamos a la vista principal del repositorio y seleccionamos el branch de develop, veremos que tenemos la última versión actualizada en el repositorio remoto.
+
+
Si nos vamos a la lista de branches, veremos que nuestro branch ya no existe.
+
+
Si nos vamos al menú de Pull Request y modificamos el filtro podremos ver todos los PR que han sido cerrados.
+
+
Y si nos vamos al historial de commits de develop veremos lo siguiente.
+
+
Ve como la estrategia de merge me agrega todos los commits a develop, esto es lo que buscamos ya que al final no tocamos develop y tenemos nuestra última funcionalidad añadida.
Ya que hemos finalizado ahora puedes estarte preguntando, que pasa en mi repositorio local.
+
Lo primero que debemos hacer es actualizar nuestros cambios, por lo que si nuestro PR, ya finalizó, lo correcto sería cambiarnos al branch de develop y actualizar la últma versión.
+
Si somos otro miembro del equipo lo ideal es actualizar los cambios de develop en la rama que estamos trabajando.
+
Si es a o b, el comando es el mismo, no lo olvides
+
git checkout develop git pull origin
+
Como resultado actualizaremos a la última versión disponible sea el miembro del equipo que sea, por eso es importante que al hacer un merge avises a tu equipo para que actualicen lo que sea necesario.
Otra pregunta que te puedes estar haciendo es ¿qué pasó con mi branch local de feature/contributing-standard pues antes de cambiarnos a develop estábamos en él.
+
Si ejecutamos:
+
git branch
+
Obtendremos que sigue ahí
+
* develop feature/contributing-standard main
+
Nuevamente debemos entender que aunque realizamos la actualización a la última versión, y que el branch ya no existe en el repositorio local, un branch no se eliminará hasta que no le digamos a git, en este caso podríamos dejarlo hasta ahí y realmente no nos afectaría en nada tenerlo, pero después de trabajar un rato puede que tengas una lista enorme de branches que ya no utilices.
+
Para sincronizar mis branches locales con los del repositorio remoto vamos a ejecutar la siguiente secuencia de comandos.
Donde vamos a sustituir main por master en caso de que así se llamara el principal.
+
+
git fetch - Es un pull pero para los branches del repositorio, esto actualiza cualquier branch que no tengamos y que este en el repositorio remoto.
+
git remote prune origin - Purga los branches no utilizados para marcar cuales existen en remoto y cuales en local
+
La última línea es una combinación para borrar todos los branches locales que no estén sincronizados con remoto o que no sean el principal main o master.
Y el resultado del último comando será el siguiente:
+
error: branch '*' not found error: cannot delete branch 'develop' used by worktree at 'D:/ITESM/TC2005B/Practicas/Lab7Branches/git-learning-branches' Deleted branch feature/contributing-standard (was 9edef4d).
+
Si ejecutamos un:
+
git branch
+
Obtendremos:
+
* develop main
+
Con esto nuestro repositorio queda limpio y podemos seguir trabajando en nuevas funcionalidades.
+
Solo por curiosidad si hacemos en la consola un:
+
git log --oneline --graph
+
Veremos un resultado interesante:
+
* a78b66b (HEAD -> develop, origin/develop) Merge pull request #1 from black4ninja/feature/contributing-standard |\ | * 9edef4d feature: Linked CONTRIBUTING.md file with README.md. | * 38278cf feature: Added CONTRIBUTING.md with standard specification on how team must work with branches, commits and additional stuff. | * 6dc4490 feature: Changed description README.md | * 5117385 feature: Changed title README.md and started migration to English language. |/ * 5a06f6d fix: Se corrige el typo de la descripción del README.md * 808115e (origin/main, origin/HEAD, main) Initial commit
+
Aquí veremos más clara la estrategia del merge pues nos dice como se hizo la bifurcación, los commits de nuestra rama y como se pego al final.
+
Además veremos que nuestro HEAD apunta a develop indicando donde estamos y también que main se ha quedado atrás en su última versión, pero que al final tenemos la versión más actualizada del mismo.
+
Por lo mismo ya podemos planear hacer un merge a main.
Hasta ahora, ya tenemos todas las herramientas para un proceso de control de versiones completo, pero nos falta subir nuestros cambios a main.
+
De aquí en adelante y no será nada nuevo lo que haremos pues los pasos a seguir son:
+
+
Bajar la última versión de develop y asegurarnos que no hay cambios adicionales a realizar.
+
Crear un pull request para hacer merge en main desde develop.
+
Hacer la revisión correspondiente.
+
Corregir cualquier cambio adicional.
+
Hacer el merge.
+
+
Iremos un poco más rápido pero iremos paso a paso, si consideras que ya entendiste toda la lección te invito a que lo intentes, incluso si fallas aprenderás a que no hacer y como resolver problemas, mejor ahorita que en tu proyecto.
+
Bajar la última versión de develop y asegurarnos que no hay cambios adicionales a realizar.
+
Hacemos un:
+
git pull origin git pull origin main
+
Primero bajamos los últimos cambios de develop y luego nos aseguramos que la posición de develop corresponda al final de main
+
En este caso particular no necesitamos hacer nada más y sabemos que el repositorio remoto tiene todos los cambios necesarios para hacer el Pull Request.
+
Crear un pull request para hacer merge en main desde develop
+
Para este PR, es diferente a los PR que vas a trabajar con tu equipo, pues este es el condensado de todo el trabajo que hayan realizado hasta el momento.
+
Idealmente sería suficiente llenar la información con la lista de PR que incluye para que en caso de querer ver el detalle, la persona que revise entienda que incluye. No es necesario repetir información de PR de trabajo ya cerrados.
+
+
También observa que el título del PR lo modifique del default que me da como nombre del branch develop a Release 0.0.1 Aquí dependerá la convención que utilizes con tu equipo pero sean descriptivos para entender mejor en el historial de commits.
La persona que revisa el PR, podrá irse a Files Changed y verán que son todos los archivos que modificamos en el PR anterior, aquí es más cuestión del equipo si deciden hacer una última revisión o al considerar que el PR anterior ya cubrió con todo los estándares pueden evitar la revisión.
Lo más común es que para este momento se ejecute un set de pruebas automáticas indicando al equipo si todas las pruebas se pasaron correctamente, en caso de que sí se puede pasar a hacer el merge.
Nuevamente seleccionamos la estrategia de merge que queremos aplicar, en mi caso merge solamente, y aceptamos los cambios.
+
+
En este caso no queremos eliminar develop pues seguiremos trabajando como está.
+
Pero si regresamos a la página principal seleccionando main veremos:
+
+
Que efectivamente main contiene la última versión.
+
Listo, has sobrevivido al GitFlow. Ahora es que empieces a practicar y hagas el compendio de los comandos necesarios para que recuerdes lo que debes trabajar.
+
Pero hey, podemos hacer un último detalle para trabajar como los profesionales.
Un paso que realizan muchas empresas al finalizar una versión, es marcarla como una versión estable, ya que si algo malo sucede puede entrar en acción rápidamente sin depender del repositorio, esto permite automatizar a que en el momento de crear estas versiones especiales se suban de manera automática a los servidores y ahorran mucho tiempo y esfuerzo por parte del equipo de desarrollo.
+
Marcar las versiones estables tiene muchas formas de hacerse, algunos extienden el GitFlow creando ramas especiales donde solo se hacen merge de dichas versiones, en otros casos al finalizar un merge a main se ejecuta una serie de comandos para generar esta versión especial.
+
En nuestro caso vamos a crear una etiqueta y que eso quede en el historial aparte para que de ser necesario podamos bajar la última versión estable.
+
Para hacerlo no olvidemos actualizar nuestr última version desde terminal:
+
git checkout main git pull origin
+
Solo como formalidad el resultado de la actualización debería verse como el siguiente:
Veremos como ha evolucionado nuestro historial de commits con todo lo que hemos echo actualizando hasta main.
+
* b3ff2e1 (HEAD -> main, origin/main, origin/HEAD) Merge pull request #2 from black4ninja/develop |\ | * a78b66b (origin/develop, develop) Merge pull request #1 from black4ninja/feature/contributing-standard | |\ | | * 9edef4d feature: Linked CONTRIBUTING.md file with README.md. | | * 38278cf feature: Added CONTRIBUTING.md with standard specification on how team must work with branches, commits and additional stuff. | | * 6dc4490 feature: Changed description README.md | | * 5117385 feature: Changed title README.md and started migration to English language. | |/ | * 5a06f6d fix: Se corrige el typo de la descripción del README.md |/ * 808115e Initial commit
+
Ahora bien, para crear una etiqueta con la nueva versión debemos asegurarnos que estamos en main.
+
Desde aquí vamos a ejecutar lo siguiente git tag -a v[version], donde version es el número de versión que asignaremos a nuestro trabajo:
+
git tag -a v0.0.1
+
Aquí en nuestra terminal se nos va a abrir una especie de editor de texto, si es la primera vez que lo haces seguramente te preguntará cuál. Selecciona el de tu preferencia.
+
En este editor agrega una pequeña descripción de que es lo que contiene la versión nueva.
+
+
Por último ejecutamos el siguiente comando git push origin v[version] donde la versión debe coincidir con la que creamos anteriormente.
Pero lo más práctico es ir a GitHub y ver que sucedió.
+
+
+
+
+
El contador de releases ha aumentado en nuestro repositorio.
+
Si entramos a esta sección veremos lo siguiente.
+
Primero tendremos una lista de versiones que en nuestro caso coincide con la versión creada.
+
+
Si entramos al detalle de la versión veremos entonces:
+
+
Y aquí observa como todo lo que contiene nuestro repositorio fue añadido a un archivo .zip y .tar.gz, este es nuestro código integro, para que en caso de que algo malo pase en el repositorio ya tenemos una versión aislada para recuperar y subir a nuestro servidor.
+
Eso es todo, acabamos de realizar un proceso de desarrollo completo desde el punto de vista de control de versiones.
+
Nuevamente de inicio puede parecer desafiante, pero este es el día a día de todos los desarrolladores en el mundo que siguen este estándar, revisa otros repositorios de GitHub y observa las diferencias entre los commits, los pull request, los branches y adquiere tu propio estilo de trabajo para tus equipos.
+
Al final del día lo que se busca es hacer más fácil la vida de los programadores, no olvides que si tienes alguna duda acercarte con tus profesores para no quedarte atrás.
Dentro de los laboratorios anteriores hemos mostrado como el uso de HTML, CSS y Javascript, nos permite manipular el DOM dentro del navegador web y crear lo que hoy en día conocemos como páginas web.
+
Hasta este punto debe quedarnos claro que hemos utilizado archivos estáticos, es decir que no se modifican y que solo generan un procesamiento con Javascript a través de lo que conocemos como Programación Orientada a Eventos.
+
Si bien lo hemos mencionado poco hemos llegado a un punto de infección en el curso donde si bien nos podemos volver expertos en el desarrollo web estático, construyendo páginas web increíbles con los 3 exponentes, ha llegado el momento de empezar a pensar en el verdadero procesamiento de datos e información que nos permitirá crear soluciones integrales a nuestros proyectos y clientes.
+
Para ello necesitamos de distinguir entre el concepto de front-end y back-end. Muy escuchado por los programadores, pero ¿qué abarca cada uno?
+
Si recuerdas, hemos hablado que la arquitectura que utilizamos para la comunicación entre archivos la hacemos mediante un Cliente-Servidor, el tipo de arquitectura más simple que existe. De manera simple podemos decir que el front-end es lo que está del lado del cliente y el back-end lo que está del lado del servidor.
El front-end es la parte de una aplicación que interactúa con los usuarios, es conocida como el lados del cliente. Básicamente es todo lo que vemos en pantalla cuando accedemos a un sitio web o aplicación: tipos de letra, colores, forma responsiva, eventos y otros elementos que permiten navegar dentro de una página web. Este conjunto crea lo que conocemos como la experiencia de usuario.
+
Como hemos dichos el desarrollador de front-end se encarga de la experiencia de usuario, es decir, en el momento en el que se entra a una página web, se debe ser capaz de navegar en ella, por lo que el usuario verá una interface sencilla de usar, atractiva y funcional.
+
Ahora bien, el front-end hoy en día no abarca solamente desarrollo web, pues hoy en día existen muchas formas de interfaces de usuario como aplicaciones de escritorio, aplicaciones móviles, entre otras. Cada una va a tener sus propias reglas, lenguajes y limitaciones.
Cuando hablamos de back-end nos referimos al interior de las aplicaciones que viven en el servidor y que a menudo se les denomina coloquialmente como el lado del servidor.
+
El back end de un sitio consiste en un servidor, una aplicación y una base de datos. Se toman los datos, se procesa la información y se envía al usuario. Hoy en día, es un poco más complejo que eso pues según el tamaño y cantidad de usuarios se requiere más capacidad de procesamiento con lo que un solo servidor puede ser suficiente. Pero para comenzar y para un sistema básico, un servidor podrá ser suficiente.
+
Cuando hablamos de un solo servidor para servir a toda la aplicación, es decir donde metemos aplicación, base de datos y archivos, estamos hablando de una arquitectura de monolito, pues toda la capacidad va a recaer en el poder de computo de 1 sola computadora en este caso el servidor.
+
Conforme vayas avanzando en tu carrera aprenderá que un monolito limita mucho las capacidades de procesamiento, crecimiento y seguridad de los sistemas, por lo que será tu labor al graduarte aplicar estrategias de arquitectura para aplicaciones grandes con demanda de usuarios en tiempo real o con una capacidad de usuarios o de procesamiento de datos que sobrepase los terabytes de información. Y podrás hacerlo.
+
Un desarrollador back-end debe tener conocimiento amplio en lenguajes de programación, manejo de servidores, sistemas operativos, seguridad y bases de datos. Si bien no es necesario conocer todos los lenguajes, es importante conocer que ventajas y desventajas trae cada uno pues ningún lenguaje es infalible. Además de que cada lenguaje cuenta con sus propios frameworks que no son más que librerías que ayudan a hacer el trabajo del desarrollador back-end más simple incorporando buenas prácticas, métodos de conexión, seguridad, entre otros.
Algo que pudieras haber escuchado es sobre el término full-stack, y este no es más que el tipo de desarrollador que tiene conocimiento integral de front-end y back-end, así como de manejo de diversos sistemas operativos y lenguajes de programación.
Para el curso vamos a estar trabajando con NodeJS el lenguaje de programación para servidores que no es otro más que Javascript. NodeJS surgió a partir de que el desarrollo de front-end es potenciado por el lenguaje Javascript, por lo que un grupo de desarrolladores decidieron facilitar la curva de desarrollo web reutilizando el lenguaje pero dándole todas las capacidades que se necesitan para hacer el trabajo que se necesita en back-end que por ejemplo algunas funciones son: manejo de peticiones tcp/ip, capacidad de ejecución de código de javascript sin la necesidad de un navegador, ente muchas muchas otras.
+
Para esta práctica ha llegado el momento de descargar NodeJS, lo puedes hacer desde la página oficial. NodeJS está disponible para todos los sistemas operativos, te recomiendo lo instales tal cual para evitarte problemas de configuración, sobre todo en Windows ya que la instalación incluye un paquete adicional de instalación que incluye librerías de .NET y el lenguaje Python que se necesitan para que el entorno simulado de NodeJS funcione.
+
Dentro de NodeJS vas a tener 2 versiones a instalar la mayoría de las veces la LTS (Long Term Support) y la (Current) ó actual. En general siempre intenta tener la LTS, pues es la versión más estable mientras que la Current puede llegar a tener problemas al ser un poco más experimental o menos soporte a las librerías.
+
Una vez que hayamos instalado NodeJS, podemos crear una carpeta en nuestra computadora y abrir nuestra terminal para poder trabajar con NodeJS.
Abriendo nuestro editor de confianza vamos a tener esta carpeta de inicio y vamos a crear un archivo app.js.
+
Anteriormente, la única forma de ver el resultado de nuestro archivo de Javascript, era mediante la liga a través de un archivo HTML en donde al abrir el navegador, teníamos que navegar a la consola y finalmente ver nuestro resultado.
+
Ahora vamos a simplificar todo el proceso ejecutando directamente app.js.
+
Dentro de app.js escribe lo siguiente:
+
console.log("Hola Mundo");
+
Antes de continuar te voy a decir que para manejar todo lo concerniente a NodeJS, es a través de línea de comandos y terminal. Aquí no hay escape y es el motivo de huida de muchos desarrolladores pues el manejo de servidores a través de terminal se les hace un proceso tedioso y complicado. Si bien es un terreno desafiante te pido que no te límites todavía, pues la capacidad que vas a adquirir desde este momento es mucha.
+
Desde nuestra terminal y en la carpeta del proyecto vamos a ejecutar el siguiente comando:
+
node -v
+
Esta instrucción nos devolverá la versión actual de NodeJS que tenemos instalada, en mi caso es la:
+
v20.11.0
+
Si tienes una versión diferente no te preocupes, NodeJS en ese aspecto no suele tener tantos conflictos de retrocompatibilidad como otros lenguajes, a lo mucho lo que puede suceder es que una librería no exista o se hayan realizado parches de seguridad que al menos para el aprendizaje no nos afectarán.
+
Ahora bien, es momento de ejecutar nuestro código en app.js, para ello escribe la siguiente instrucción:
+
node app.js
+
El resultado en terminal será como puedes intuir:
+
Hola Mundo
+
Éxito, acaba de ejecutar tu primer programa en NodeJS. Si has trabajado con otros lenguajes de programación notarás que el proceso es similar, en el sentido que hace falta tener el entorno de ejecución del lenguaje o el compilador en su defecto y a partir de ello se ejecuta el programa.
+
Lo anterior suena muy bien, pero no hemos visto nada diferente a lo que ya vimos en la introducción a Javascript.
Una de las funciones importantes de un servidor y de cualquier lenguaje de programación es el poder hacer manejo de archivos a través de lo que se conoce como Filesystem.
+
Esto nos llevará a los siguientes 2 puntos:
+
+
Importar una librería default de NodeJS
+
Manejar el filesystem para leer un archivo.
+
+
Para importar librerías en Javascript, que es algo que no hemos realizado hasta este momento vamos a hacer uso de la siguiente instrucción en nuestro archivo app.js
+
//fs es el módulo que contiene las funciones para //manipular el sistema de archivos const filesystem = require('fs');
+
Esto le dirá a NodeJS que cargue la librería para el manejo del filesystem en nuestro programa mientras se esté ejecutando.
+
Como ya tenemos acceso al sistema de archivos de la computadora, podemos crear, editar o eliminar archivos. Para nuestro caso vamos a crear un nuevo archivo con la siguiente instrucción.
+
//Se escribe el segundo parámetro en el archivo del primer parámetro filesystem.writeFileSync('hola.txt', 'Hola mundo desde node');
+
Si volvemos a ejecutar nuestro archivo con la instrucción node app.js en la terminal veremos el mismo resultado del Hola Mundo, pero, si ves en tu carpeta del proyecto o en tu editor, debería aparecer un nuevo archivo con nombre hola.txt y si examinas su contenido verás que dice:
+
Hola mundo desde node
+
¿Por qué podemos manipular archivos? Recuerda que cuando hablamos de un servidor es la computadora que almacena nuestra aplicación, por tanto nosotros somos responsables de como se maneja dicha computadora. Cuando hablamos de Javascript del lado del cliente en teoría no deberíamos modificar ningún archivo dentro de la computadora del cliente, pues no es algo que nos pertenezca, viéndolo desde el punto de ciberseguridad sería una muy mala práctica y nuestro sitio podría ser marcado como malicioso. Ahora nota que mi indicación es que no se debería hacer, pues el echo desde el punto ético y de seguridad es ese más desde el punto de vista funcional es que puede hacerse.
Ya tenemos la capacidad de manipular los archivos de nuestra máquina lo que ya es un gran avance, pero que pasa con NodeJS y Javascript del lado del navegador, además del punto ético que te mencioné en el último párrafo, ¿existe alguna diferencia desde el punto de vista de la sintaxis del lenguaje? La respuesta es no.
+
NodeJS y Javascript siguen las mismas reglas de escritura de instrucciones, variables, funciones, métodos, etc. Quizás la diferencia radica es que en Javascript tenemos acceso al objeto DOM y podemos manipularlo, pero incluso el DOM desde el punto de vista del lenguaje es solo un objeto con datos, variables y funciones.
+
Para el caso de NodeJS quitando el objeto document, el windows y la manipulación del DOM, podemos hacer el uso de la misma lógica del lenguaje.
Trata de descubrir por tí mismo que hace el código anterior.
+
El código anterior lo vamos a conocer como el async sort y nos permitirá ver uno de los usos más frecuentes de NodeJS que es la espera asíncrona de peticiones.
+
Dentro del código tendremos un arreglo con números enteros no ordenados. Lo que hace el algoritmos es recorrer dicha lista y utilizar la función de Javascript setTimeout(), la cual recibe 2 parámetros:
+
+
Función - Una función que ejecute un código que deseemos, en este caso imprimir el número que estamos leyendo.
+
Tiempo (milisegundos) - El tiempo que debe pasar en milisegundos para que se ejecute la función que se recibe como parámetro.
+
+
Entonces lo que hace el código es imprimir en pantalla el elemento de la lista según el tiempo en milisegundos que representa.
+
Haciendo una corrida lo que sucede es lo siguiente:
+
+
Se ejecuta el paso por 5000 (5 segs), pasarán 5 segundos antes de imprimir 5000.
+
Se ejecuta el paso por 60 (60 millis), pasarán 60 milisegundos antes de imprimir 60.
+
Se ejecuta el paso por 90 (90 millis), pasarán 90 milisegundos antes de imprimir 90.
+
Se ejecuta el paso por 100 (100 millis), pasarán 100 milisegundos antes de imprimir 100.
+
Se ejecuta el paso por 10 (10 millis), pasarán 10 milisegundos antes de imprimir 10.
+
Se ejecuta el paso por 20 (20 millis), pasarán 20 milisegundos antes de imprimir 20.
+
Se ejecuta el paso por 10000 (10 segs), pasarán 10 segundos antes de imprimir 10000.
+
Se ejecuta el paso por 0 (0 millis), pasarán 0 milisegundos antes de imprimir 0.
+
Se ejecuta el paso por 120 (120 millis), pasarán 120 milisegundos antes de imprimir 120.
+
Se ejecuta el paso por 2000 (2 segs), pasarán 2 segundos antes de imprimir 2000.
+
Se ejecuta el paso por 340 (340 millis), pasarán 340 milisegundos antes de imprimir 340.
+
Se ejecuta el paso por 1000 (1 segs), pasarán 1 segundo antes de imprimir 1000.
+
Se ejecuta el paso por 50 (50 millis), pasarán 50 milisegundos antes de imprimir 50.
+
+
Con la corrida, resulta más evidente que los números no se imprimen como vienen en el arreglo, sino que se imprimen en orden pues empiezan a generarse un delay entre cada uno pues el orden es directamente proporcional al tiempo en milisegundos que esperan a imprimirse.
Con lo visto anteriormente, entonces vemos que desde Javascript podemos tener código que va llegando en diferentes momentos aunque la ejecución continua sucediendo línea por línea en el orden esperado.
+
Para la función setTimeOut() el parámetro de función que se recibe espera el tiempo necesario hasta poder ejecutarse, entonces aunque el código de manera lineal va instrucción por instrucción, si se cumple el tiempo de espera, se ejecuta la función de parámetro sin importar que más este sucediendo en ese momento. Más que decir que sea bueno o malo, dependerá de lo que necesitemos hacer y para el caso de NodeJS y el manejo de nuestro servidor será el pan de cada día.
+
Con lo anterior entonces ya deberías de poder resolver la siguiente incógnita. ¿Qué línea de código se ejecuta primero?
+
console.log("jojo te hackié"); console.log("¿En dónde se ejecuta esta línea?");
+
Del siguiente extracto de código:
+
const te_hackie = () => { console.log("jojo te hackié"); } //setTimeout ejecuta la función recibida como primer parámetro //cuando hayan transcurrido los milisegundos del segundo parámetro setTimeout(te_hackie, 7000); console.log("¿En dónde se ejecuta esta línea?");
+
En el código anterior veremos que si no hemos comentado el Async Sort, la función te_hackie se ejecuta después de la impresión del 5000 pero antes que el 10000. Nuevamente a Javascript no lo importa el orden de las funciones, solo el tiempo en el que les toca ejecutarse, a esto es lo que conocemos como código asíncrono, y más adelante veremos que será la manera en la que funciona un servidor.
La razón de cambiar de archivo es que en la práctica hay proyectos cuyo nombre principal de archivos de NodeJS lo llaman app.js ó index.js, otro no tan común sería main.js.
+
En nuestro código haremos uso de otra librería estándar de NodeJS llamada http, esta librería es la que nos permite definir un servidor y poder manipular las entradas y salidas del mismo.
+
Antes de ejecutar el archivo vamos a hacer algo, ve a tu navegador y escribe la dirección:
Si observas el mensaje de error dice ERR_CONNECTION_REFUSED indicándonos que no hay nada corriendo en la dirección local dentro del puerto 3000.
+
Ahora, vamos a ejecutar el archivo con:
+
node index.js
+
Dos cosas van a suceder, en la terminal no finalizará el programa se quedará en un modo espera. Y si nuevamente vamos al navegador y escribimos la dirección pasará lo siguiente:
+
Al inicio no pasará nada, pero deberá aparecer un loader de que algo esta sucediendo.
+
+
Nota: Si usaste la ventana donde nos apareció ERR_CONNECTION_REFUSED puedes confundirte, te recomiendo hagas la prueba en una nueva pestaña.
+
+
Lo interesante, es que ahora no nos da la página de error, sino que simplemente se queda ahí, esto es por que el servidor recibe la petición, pero no tenemos nada actualmente que realice.
+
Ahora vamos a quitar el comentario del código que está dentro de la declaración de la función del servidor.
Ahora, guarda el archivo e intenta abrirlo en el navegador con localhost:3000, ¿qué sucedió?
+
Nada, y esto es por que aunque guardamos nuestro archivo index.js no reiniciamos el servidor, esto es importante cuando estás desarrollando pues necesitas detener el servidor y volverlo a correr para que carguen los nuevos cambios.
+
Para detener el servidor, desde la terminal ejecuta la secuencia de comandos ctrl+C.
+
Nuevamente ejecuta node index.js, ve al navegador y recarga ahora sí la página.
+
El resultado será una página en blanco:
+
+
Vamos a revisar instrucción por instrucción lo que acabamos de hacer:
+
console.log(request.url);
+
Esta línea utiliza el objeto request que contendrá toda la información que pasemos desde el cliente al servidor, si mandamos un formularios por ejemplo, o información a través de una url request debería tener esa información.
+
En el caso de request.url lo que hace es que imprime la url que se está llamando en ese momento.
+
El resultado aparecerá en la terminal y nos tiene una sorpresa.
+
/ /favicon.ico
+
La primera ruta es la que hace referencia a nuestro navegador cuando escribimos localhost:3000, escribir esta url solo le da la dirección a donde conectarse, pero cuando la encuentra adelante escribirá la url que le es solicitada, en este caso como no pasamos nada, utiliza la url default /.
+
La sorpresa vendrá con la segunda url /favicon.ico, aquí lo que sucede es que al momento de llamar una cualquier url, los navegadores tienen la instrucción estándar de jalar esta url y colocar un icono, que seguramente has visto al lado del título de la pestaña del navegador.
+
+
Como no estamos manejando esta url el navegador utiliza una imagen default.
+
Para la instrucción:
+
response.setHeader('Content-Type', 'text/html');
+
Lo que estamos diciendo aquí es que el contenido de la respuesta sea tratado por el navegador como código HTML. Aquí se utiliza el objeto response, que nuevamente es esencial en el manejo del servidor, a diferencia de request como podrás imaginar, contendrá toda la información que mandaremos de regreso al cliente al completar la solicitud. Lo cual nos lleva a la siguiente instrucción:
+
response.write("");
+
La instrucción escribe el código HTML que queramos mandar de regreso, de momento lo dejamos vacío y por eso es que la página se ve en blanco.
+
La función write solo escribe la respuesta en el request pero no lo manda, para ello es necesario hacer uso de la última instrucción.
+
response.end();
+
La función end(), es la encargada de mandar la respuesta al cliente y a partir de ello es que podemos ver la página en blanco en el navegador.
+
Intenta a cambiar la url de localhost:3000 con variantes de url y observa lo que pasa:
Todas las respuestas en el navegador serán páginas en blanco, pues no estamos haciendo una distinción para las url. Pero en la terminal verás como cambia la primera url que se llama y que además van seguidas del /favicon.ico, también nota que el servidor nunca se detiene y se combina la primera ejecución con las siguientes.
Hoy en día, los servidores rastrean todas las peticiones que les hacen los clientes para mantener un mejor entendimiento de lo que se está haciendo o solicitando. De la misma manera los navegadores han evolucionado tanto que es posible ver que acciones ejecuta el servidor, y si bien no podemos ver lo que sucede internamente podemos ver nuestros request y todas sus responses.
+
En un ciclo ideal, dentro de la arquitectura cliente-servidor siempre para todo request deberá existir un response, puede haber excepciones y para ello se manejan códigos especiales de error que veremos más adelante.
+
Para ver este historial de solicitudes basta que hagamos clic derecho > seleccionemos inspeccionar elemento.
+
Dentro del menú hasta ahora hemos explorado, la pestaña de elements para ver el contenido de la página y console para ver la salida del código de javascript. Ahora nos vamos a la pestaña network y procederemos a recargar la página.
+
+
Esta vista nos ayudará mucho en el futuro, pues hará lo siguiente:
+
+
Nos muestra una lista de los request y sus respuestas, si seleccionamos cada uno podemos ver el contenido completo, además nos dice el tiempo de carga de cada cosa.
+
Por otro lado de manera global nos indica el tiempo de carga del DOM, si estamos optimizando el tiempo de carga de la página es un buen sitio donde comenzar.
+
+
Con más experiencia y conocimiento podrás obtener más información de lo que esta sucediendo entre el cliente y el servidor, de momento nos quedaremos así.
Permite enviar texto de vuelta,y se permite el uso de HTML solo por que previamente usamos la instrucción:
+
response.setHeader('Content-Type', 'text/html');
+
Vamos a enviar algo al servidor como:
+
response.write("hola mundo desde node");
+
No olvides guardar el archivo y reiniciar el servidor.
+
Al volver a cargar la url, puede ser la primera, localhost:3000, veremos el siguiente resultado:
+
+
Dentro de la página no hay problema, pero si exploramos el contenido html, veremos que se hace un código difícil de entender, a diferencia que hicimos lo mismo la primera vez con index.html, esto a se debe que el navegador entiende que se procesa una petición y su respuesta no es un html estandarizado, por lo que aunque intenta hacerlo añade más código del necesario.
+
Vamos a modificar la respuesta con un html en regla, pero para ello usaremos el tercer tipo de comillas de Javascript para evitar conflictos con otro tipo de comillas.
+
response.write(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>HTML</title> </head> <body> <h1>hola mundo desde node</h1> </body> </html> `);
+
Nuevamente guardamos y reiniciamos el servidor. El resultado:
+
+
El resultado es el h1 en negritas y que el contenido html tiene el formato esperado.
+
+
Nota: Verás que a mí me aparecen 2 div extraños, estas son extensiones que tengo instaladas en chrome, y se añaden de manera automática a todo el código HTML de mi navegador. Si tu tienes extensiones instaladas el código puede variar.
+
+
Éxito, con esto ya podrías incorporar páginas HTML de tus laboratorios anteriores, pero si tratas de agregar archivos externos de CSS o Javascript estos no funcionarán, la única manera de que esto sirva es que agregue el código directamente en una etiqueta:
+
<style> //Mi código CSS </style>
+
ó
+
<javascript> //Mi código Javascript </javascript>
+
Si bien ya podríamos trabajar creando páginas web completas, esta no es la forma más funcional de hacerlo. En próximos laboratorios veremos como optimizar esto separando en archivos las peticiones y para usar nuevamente nuestros archivos HTML, CSS y JS como en laboratorios anteriores por separado.
+
Experimenta con el servidor, la única forma de que entiendas todos sus secretos es llevarlo al límite y ejecutar ver si es el resultado que realmente esperas.
Tutoriales - te toman de la mano a través de una serie de pasos para crear un proyecto o entender un tema completo. Inicia aquí si eres nuevo al tema que estás buscando.
+
Guías por Tema - discuten temas clave o particulares y conceptos a un alto nivel y proveen información de trasfondo y explicación.
+
Guías de Referencia - contienen referencias técnicas para el tema revisado. Describen como funciona y como usarlo pero asumen que tienes un conocimiento básico de entendimiento para los conceptos clave.
+
Guías How-To son recetas - Te guían a través de pasos para resolver problemas concretos y casos de uso. Son más avanzados que los tutoriales y asumen que tienes conocimiento del tema buscado.
Sigue la convención de ramas que vayas estableciendo en tus equipos de trabajo, una vez que la tengas te recomiendo la siguiente convención de comandos para un día de trabajo.
+
+<%- include('./../scripts.ejs') %>
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/LAB14Sesiones/test-project.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/LAB14Sesiones/test-project.zip
new file mode 100644
index 0000000..f684643
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/LAB14Sesiones/test-project.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/ejemplo.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/ejemplo.zip
new file mode 100644
index 0000000..bae4951
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/ejemplo.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/form.html b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/form.html
new file mode 100644
index 0000000..3e05d5e
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/form.html
@@ -0,0 +1,16 @@
+
+
+
+
+ Formularios HTML
+
+
+
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/index.js b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/index.js
new file mode 100644
index 0000000..89f4566
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/index.js
@@ -0,0 +1,80 @@
+const http = require('http');
+const path = require('path');
+const fs = require('fs');
+
+const server = http.createServer( (request, response) => {
+ console.log(request.url);
+
+ switch(request.url){
+ case "/":
+ response.setHeader('Content-Type', 'text/plain');
+ response.write("URL index /");
+ response.end();
+ break;
+ case "/test_json":
+ if(request.method == "GET"){
+ response.setHeader('Content-Type', 'application/json');
+ response.write('{code:200, msg:"Ok GET"}');
+ response.end();
+ }else if(request.method == "POST"){
+ response.setHeader('Content-Type', 'application/json');
+ response.write('{code:200, msg:"Ok POST"}');
+ response.end();
+ }
+ break;
+ case "/test_html":
+ response.setHeader('Content-Type', 'text/html');
+ response.write(`
+
+
+
+
+ Código en HTML
+
+
+
hola mundo desde node
+
+
+ `);
+ response.end();
+ break;
+ case "/form_method":
+ if(request.method == "GET"){
+ response.setHeader('Content-Type', 'text/html');
+ const html = fs.readFileSync(path.resolve(__dirname, './form.html'), 'utf8')
+ response.write(html);
+ response.end();
+ }else if(request.method == "POST"){
+ let body = [];
+ request
+ .on('data', chunk => {
+ body.push(chunk);
+ })
+ .on('end', () => {
+ body = Buffer.concat(body).toString();
+ console.log(body)
+
+ const indice = Number(body.split('&')[0].split('=')[1]);
+ console.log(indice);
+ const imprimir = body.split('&')[1].split('=')[1];
+ console.log(imprimir);
+
+ for(var i = 1; i <= indice; i++){
+ console.log(imprimir)
+ }
+
+ response.setHeader('Content-Type', 'application/json');
+ response.statusCode = 200;
+ response.write('{code:200, msg:"Ok POST"}');
+ response.end();
+ });
+ }
+ break;
+ default:
+ response.statusCode = 404;
+ response.end();
+ break;
+ }
+
+});
+server.listen(3000);
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/inputs.html b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/inputs.html
new file mode 100644
index 0000000..10d3f53
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab10RutasYFormas/inputs.html
@@ -0,0 +1,40 @@
+
+
+
+
+ Código en HTML
+
+
+
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project.zip
new file mode 100644
index 0000000..3935963
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/.gitignore b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/.gitignore
new file mode 100644
index 0000000..6a7d6d8
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/.gitignore
@@ -0,0 +1,130 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/form.html b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/form.html
new file mode 100644
index 0000000..9b7ea09
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/form.html
@@ -0,0 +1,16 @@
+
+
+
+
+ Formularios HTML
+
+
+
+
+
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/index.js b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/index.js
new file mode 100644
index 0000000..79547f2
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/index.js
@@ -0,0 +1,63 @@
+const http = require('http');
+const express = require('express');
+const path = require('path');
+const fs = require('fs');
+const app = express();
+
+const bodyParser = require('body-parser');
+app.use(bodyParser.urlencoded({extended: false}));
+
+//Middleware
+app.use((request, response, next) => {
+ console.log('Middleware!');
+ next(); //Le permite a la petición avanzar hacia el siguiente middleware
+});
+
+app.get('/', (request, response, next) => {
+ response.setHeader('Content-Type', 'text/plain');
+ response.send("URL index /");
+ response.end();
+});
+
+app.get('/test_json', (request, response, next) => {
+ response.setHeader('Content-Type', 'application/json');
+ response.json({code:200, msg:"Ok GET"});
+ response.end();
+});
+
+app.post('/test_json', (request, response, next) => {
+ response.setHeader('Content-Type', 'application/json');
+ response.json({code:200, msg:"Ok POST"});
+ response.end();
+});
+
+app.get('/test_html', (request, response, next) => {
+ response.setHeader('Content-Type', 'text/html');
+ response.write(`
+
+
+
+
+ Código en HTML
+
+
+
hola mundo desde express
+
+
+ `);
+ response.end();
+});
+
+const rutasFormulario = require('./routes/formulario.routes');
+app.use('/formulario', rutasFormulario);
+
+app.use((request, response, next) => {
+ console.log('Otro middleware!');
+ response.status(404);
+ response.send('¡Page Not Found!'); //Manda la respuesta
+});
+
+const server = http.createServer( (request, response) => {
+ console.log(request.url);
+});
+app.listen(3000);
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/package-lock.json b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/package-lock.json
new file mode 100644
index 0000000..6b0fddf
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/package-lock.json
@@ -0,0 +1,695 @@
+{
+ "name": "test-project",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "test-project",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.20.2",
+ "express": "^4.19.2"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
+ "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.11.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.19.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+ "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.2",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.6.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.2.0",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.11.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.18.0",
+ "serve-static": "1.15.0",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
+ "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.11.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
+ "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
+ "dependencies": {
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/send": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
+ "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/serve-static": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
+ "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "dependencies": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.18.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ }
+ }
+}
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/package.json b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/package.json
new file mode 100644
index 0000000..d794a1b
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "test-project",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.20.2",
+ "express": "^4.19.2"
+ }
+}
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/routes/formulario.routes.js b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/routes/formulario.routes.js
new file mode 100644
index 0000000..fdf7877
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab11Express/test-project/routes/formulario.routes.js
@@ -0,0 +1,29 @@
+const express = require('express');
+const path = require('path');
+const fs = require('fs');
+const router = express.Router();
+
+router.get('/form_method', (request, response, next) => {
+ response.setHeader('Content-Type', 'text/html');
+ const html = fs.readFileSync(path.resolve(__dirname, './../form.html'), 'utf8')
+ response.write(html);
+ response.end();
+});
+
+router.post('/form_method', (request, response, next) => {
+ const indice = Number(request.body.indice);
+ console.log(indice);
+ const imprimir = request.body.imprimir
+ console.log(imprimir);
+
+ for(var i = 1; i <= indice; i++){
+ console.log(imprimir)
+ }
+
+ response.setHeader('Content-Type', 'application/json');
+ response.statusCode = 200;
+ response.write('{code:200, msg:"Ok POST"}');
+ response.end();
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab12EJS/test-project.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab12EJS/test-project.zip
new file mode 100644
index 0000000..097a1ce
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab12EJS/test-project.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab17BD/test-project.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab17BD/test-project.zip
new file mode 100644
index 0000000..a4f9a66
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab17BD/test-project.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab18Autenticacion/MVC.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab18Autenticacion/MVC.zip
new file mode 100644
index 0000000..c90fd4b
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab18Autenticacion/MVC.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab18Autenticacion/test-project.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab18Autenticacion/test-project.zip
new file mode 100644
index 0000000..7fbc16c
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab18Autenticacion/test-project.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab19RBAC/MVC.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab19RBAC/MVC.zip
new file mode 100644
index 0000000..d9bbed7
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab19RBAC/MVC.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab19RBAC/test_project.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab19RBAC/test_project.zip
new file mode 100644
index 0000000..2acf5fd
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab19RBAC/test_project.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab22Archivos/test-project.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab22Archivos/test-project.zip
new file mode 100644
index 0000000..1497ac6
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab22Archivos/test-project.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab24AJAX/template.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab24AJAX/template.zip
new file mode 100644
index 0000000..d6eed40
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab24AJAX/template.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab24AJAX/test-project.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab24AJAX/test-project.zip
new file mode 100644
index 0000000..5ed923d
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab24AJAX/test-project.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/10Conditionals.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/10Conditionals.zip
new file mode 100644
index 0000000..0e0afa8
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/10Conditionals.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/11Loops.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/11Loops.zip
new file mode 100644
index 0000000..494292e
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/11Loops.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/12Functions.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/12Functions.zip
new file mode 100644
index 0000000..eaa4dbe
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/12Functions.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/13Arrays&Objects.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/13Arrays&Objects.zip
new file mode 100644
index 0000000..b5b4bfb
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/13Arrays&Objects.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/1SeparateFiles.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/1SeparateFiles.zip
new file mode 100644
index 0000000..1fe1a23
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/1SeparateFiles.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/2MultipleScripts.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/2MultipleScripts.zip
new file mode 100644
index 0000000..96c9384
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/2MultipleScripts.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/3CombinedScripting.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/3CombinedScripting.zip
new file mode 100644
index 0000000..f00cd80
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/3CombinedScripting.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/4CodeStructure.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/4CodeStructure.zip
new file mode 100644
index 0000000..d432c7d
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/2CodeStructure/4CodeStructure.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/3UseStrict.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/3UseStrict.zip
new file mode 100644
index 0000000..9215d43
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/3UseStrict.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/4Variables.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/4Variables.zip
new file mode 100644
index 0000000..7428e44
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/4Variables.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/5DataTypes.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/5DataTypes.zip
new file mode 100644
index 0000000..7af84c4
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/5DataTypes.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/6Interaction.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/6Interaction.zip
new file mode 100644
index 0000000..fc37090
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/6Interaction.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/7TypeConversions.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/7TypeConversions.zip
new file mode 100644
index 0000000..de933ac
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/7TypeConversions.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/8BasicOperators.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/8BasicOperators.zip
new file mode 100644
index 0000000..64d7839
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/8BasicOperators.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/9Comparisons.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/9Comparisons.zip
new file mode 100644
index 0000000..40c1dc0
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab4JS/9Comparisons.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab6POE/poe.zip b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab6POE/poe.zip
new file mode 100644
index 0000000..2e9efdf
Binary files /dev/null and b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab6POE/poe.zip differ
diff --git a/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab7Branches/CONTRIBUTING.md b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab7Branches/CONTRIBUTING.md
new file mode 100644
index 0000000..3b75c6c
--- /dev/null
+++ b/deprecated/old-docs-build/docs/node/tutorials/intro_web/Lab7Branches/CONTRIBUTING.md
@@ -0,0 +1,153 @@
+## Contributing
+
+Before you start to code, please create traceability for a use case in [Tool used by the team to manage project](), open a [new issue]() to describe a bug found, or search for and continue the discussion in an [existing issue]().
+
+Please completely fill out any templates to provide essential information about your new feature or the bug you discovered or reported.
+
+When you are ready to code, you can find more information about opening a pull request in the [GitHub docs](https://help.github.com/articles/creating-a-pull-request/).
+
+Whether this is your first contribution or you are already an experienced contributor, your team has your back – don't hesitate to ask for help!
+
+### Issue vs. Pull Request
+
+An issue will never substitute a former card in the tech system fo management, an issue is only to address bugs recognized and not solved within the app.
+
+An issue is required to be linked in every pull request. We understand that no-one likes to create an issue for something that appears to be a simple pull request, but here is why this is beneficial for everyone:
+
+- An issue get more visibility than a pull request as issues can be pinned and it is primarily the issue list that people browse through rather than the more technical pull request list. Visibility is a key aspect so others can weigh in on issues and contribute their opinion.
+- The discussion in the issue is different from the discussion in the pull request. The issue discussion is focused on the issue and how to address it, whereas the discussion in the pull request is focused on a specific implementation. An issue may even have multiple pull requests because either the issue requires multiple implementations or multiple pull requests are opened to compare and test different approaches to later decide for one.
+- High-level conceptual discussions about the issue should be still available, even if a pull request is closed because its approach was discarded. If these discussions are in the pull request instead, they can easily become fragmented over multiple pull requests and issues, which can make it very hard to make sense of all aspects of an issue.
+
+## Environment Setup
+
+### Recommended Tools
+
+- [Tool used by the team to develop]().
+
+### Setting up your local machine
+
+
+```sh
+$ git clone {{your url repository}}
+$ cd my-repository-folder # go into the clone directory
+```
+
+Open Development tool, navigate to clone directory **my-repository-folder** and select it.
+
+### Good to Know
+
+- Things that are good to know or commands needed in order to run the project.
+
+## Breaking Changes
+
+### Deprecation Policy
+
+If you change or remove an existing feature that would lead to a breaking change, use the following deprecation pattern:
+
+- Make the new feature or change optional, if necessary with a new Scheduling option parameter.
+- Use a default value that falls back to existing behavior.
+- Add the @deprecated tag for the comment of the function, for example:
+ > DeprecationWarning: The System App option 'example' will be removed in a future release.
+
+Deprecations become breaking changes after notifying developers through deprecation warnings for at least one entire previous major release. For example:
+
+- `4.5.0` is the current version
+- `4.6.0` adds a new optional feature and a deprecation warning for the existing feature
+- `5.0.0` marks the beginning of logging the deprecation warning for one entire major release
+- `6.0.0` makes the breaking change by removing the deprecation warning and making the new feature replace the existing feature
+
+See the [Deprecation Plan](DEPRECATIONS.md) for an overview of deprecations and planned breaking changes.
+
+## Naming Branches
+
+### Create a new branch
+
+The title of a branch needs to be written in a defined syntax. We loosely follow the [Git Naming Convention](https://tilburgsciencehub.com/building-blocks/collaborate-and-share-your-work/use-github/naming-git-branches/)
+
+1. Use separators: When writing a branch name, using slash (/) separators to increase readability of the name
+2. Start name with category word: It is recommended to begin the name of a branch with a category word, which indicates the type of task that is being solved with that branch. Some of the most used category words are:
+ - `hotfix` - for quickly fixing critical issues, usually with a temporary solution
+ - `bugfix` - for fixing a bug
+ - `feature` - for adding, removing or modifying a feature
+ - `test` - for experimenting something which is not an issue
+ - `wip` - for a work in progress
+3. Use the {Release version - ID} of the Card or Issue addressing: Using the ID in the branch name makes it easy to identify the task and keep track of its progress. `X.Y.Z-alpha.ID`
+4. Avoid Using Numbers Only: It’s not a good practice to name a branch by only using numbers, because it creates confusion and increases chances of making mistakes. Instead, combine ID of issues with key words for the respective task.
+5. Avoid Long Branch Names: As much as the branch name needs to be informative, it also needs to be precise and short. Detailed and long names can affect readability and efficiency.
+
+```
+//
+```
+
+## Pull Request
+
+### Commit Message
+
+The title of pull requests needs to be written in a defined syntax. We loosely follow the [Conventional Commits](https://www.conventionalcommits.org) specification, which defines this syntax:
+
+```
+:
+```
+
+The _type_ is the category of change that is made, possible types are:
+
+- `feat` - add a new feature or improve an existing feature
+- `fix` - fix a bug
+- `refactor` - refactor code without impact on features or performance
+- `docs` - add or edit code comments, documentation, GitHub pages
+- `style` - edit code style
+- `build` - retry failing build and anything build process related
+- `perf` - performance optimization
+- `ci` - continuous integration
+- `test` - tests
+
+The _summary_ is a short change description in present tense, not capitalized, without period at the end. This summary will also be used as the changelog entry.
+
+- It must be short and self-explanatory for a reader who does not see the details of the full pull request description
+- It must not contain abbreviations, e.g. instead of `LQ` write `LiveQuery`
+- It must use the correct product and feature names as referenced in the documentation, e.g. instead of `Cloud Validator` use `Cloud Function validation`
+
+For example:
+
+```
+feat: add handle to door for easy opening
+```
+
+Github actions:
+
+In addition, commit messages can be used to trigger specific github actions. We currently support the following:
+
+- `#apk` - builds a signed APK that can be distributed for testing
+
+Currently, we are not making use of the commit _scope_, which would be written as `(): `, that attributes a change to a specific part of the product.
+
+### Breaking Change
+
+If a pull request contains a braking change, the description of the pull request must contain a dedicated chapter at the bottom to indicate this. This is to assist the committer of the pull request to avoid merging a breaking change as non-breaking.
+
+## Merging
+
+The following guide is for anyone who merges a contributor pull request into the working branch, the working branch into a release branch, a release branch into another release branch, or any other direct commits such as hotfixes into release branches or the working branch.
+
+- A contributor pull request must be merged into the working branch using `Squash and Merge`, to create a single commit message that describes the change.
+- A release branch or the default branch must be merged into another release branch using `Merge Commit`, to preserve each individual commit message that describes its respective change.
+
+## Versioning
+
+System follows [semantic versioning](https://semver.org) with a flavor of [calendric versioning](https://calver.org). Semantic versioning makes System easy to upgrade because breaking changes only occur in major releases. Calendric versioning gives an additional sense of how old a System release is and allows for Long-Term Support of previous major releases.
+
+Example version: `5.0.0-alpha.1`
+
+Syntax: `[major]`**.**`[minor]`**.**`[patch]`**-**`[pre-release-label]`**.**`[pre-release-increment]`
+
+- The `major` version increments with the first release of every year and may include changes that are _not backwards compatible_.
+- The `minor` version increments during the year and may include new features or improvements of existing features that are backwards compatible.
+- The `patch` version increments during the year and may include bug fixes that are backwards compatible.
+- The `pre-release-label` is optional for pre-release versions such as:
+ - `-alpha` (likely to contain bugs, likely to change in features until release)
+ - `-beta` (likely to contain bugs, no change in features until release)
+- The `[pre-release-increment]` is an ID of the current Card or Issue.
+
+Exceptions:
+
+- The `major` version may increment during the year in the unlikely event that a breaking change is so urgent that it cannot wait for the next yearly release. An example would be a vulnerability fix that leads to an unavoidable breaking change. However, security requirements depend on the application and not every vulnerability may affect every deployment, depending on the features used. Therefore we usually prefer to deprecate insecure functionality and introduce the breaking change following our [deprecation policy](#deprecation-policy).
\ No newline at end of file
diff --git a/deprecated/old-docs-build/docs/sitemap.xml b/deprecated/old-docs-build/docs/sitemap.xml
new file mode 100644
index 0000000..0568099
--- /dev/null
+++ b/deprecated/old-docs-build/docs/sitemap.xml
@@ -0,0 +1 @@
+https://tc2005b.github.io/docs/blogweekly0.5https://tc2005b.github.io/docs/blog/archiveweekly0.5https://tc2005b.github.io/docs/blog/authorsweekly0.5https://tc2005b.github.io/docs/blog/authors/all-sebastien-lorber-articlesweekly0.5https://tc2005b.github.io/docs/blog/authors/yangshunweekly0.5https://tc2005b.github.io/docs/blog/first-blog-postweekly0.5https://tc2005b.github.io/docs/blog/long-blog-postweekly0.5https://tc2005b.github.io/docs/blog/mdx-blog-postweekly0.5https://tc2005b.github.io/docs/blog/tagsweekly0.5https://tc2005b.github.io/docs/blog/tags/docusaurusweekly0.5https://tc2005b.github.io/docs/blog/tags/facebookweekly0.5https://tc2005b.github.io/docs/blog/tags/helloweekly0.5https://tc2005b.github.io/docs/blog/tags/holaweekly0.5https://tc2005b.github.io/docs/blog/welcomeweekly0.5https://tc2005b.github.io/docs/markdown-pageweekly0.5https://tc2005b.github.io/docs/docs/backend/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab10RutasYFormas/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab11Express/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab12EJS/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/LAB13MVC/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/LAB14Sesiones/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab17BD/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab18Autenticacion/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab19RBAC/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab1HTML/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab22Archivos/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab24AJAX/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab2Git/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab3CSS/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab4JS/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab5StyleFramework/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab6POE/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab7Branches/weekly0.5https://tc2005b.github.io/docs/docs/backend/node/tutorials/intro_web/Lab8IntroBackend/weekly0.5https://tc2005b.github.io/docs/docs/category/introducci%C3%B3n-a-webweekly0.5https://tc2005b.github.io/docs/docs/introweekly0.5https://tc2005b.github.io/docs/weekly0.5
\ No newline at end of file
diff --git a/deprecated/old-docusaurus/docusaurus b/deprecated/old-docusaurus/docusaurus
new file mode 160000
index 0000000..4dfb789
--- /dev/null
+++ b/deprecated/old-docusaurus/docusaurus
@@ -0,0 +1 @@
+Subproject commit 4dfb78901f22c2618b3c14bd8c32356182a57e1a
diff --git a/ej4_mr_deprecated/Dibujo1.jpg b/ej4_mr_deprecated/Dibujo1.jpg
deleted file mode 100644
index 7baf4e0..0000000
Binary files a/ej4_mr_deprecated/Dibujo1.jpg and /dev/null differ
diff --git a/ej4_mr_deprecated/ej4.html b/ej4_mr_deprecated/ej4.html
deleted file mode 100644
index fe885ff..0000000
--- a/ej4_mr_deprecated/ej4.html
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Ejercicio 4: Identificación de llaves en un Modelo ER
-
-
-
-
-
-
-
-
-
-
-
Ejercicio 4: Identificación de llaves en un Modelo ER
-
-
-
-
-
-
-
-
- Modalidad
-
-
- Trabajo por equipos.
-
-
-
-
-
-
-
-
- Objetivos de aprendizaje
-
-
Aplicando las reglas de traslado de MER a MR, define el Modelo Relacional para el siguiente Modelo Entidad Relación, posteriormente determinar las llaves primarias, foráneas y alternas que existen en cada relación, basándose en la lectura de "Conceptos básicos del modelo relacional".
-
-
-
-
-
-
-
-
- Instrucciones
-
-
-
La convención utilizada para identificar la llaves será la siguiente:
-
Pk Llave Primaria
-
Fk Llave Foránea
-
Ak Llave Alterna
-
-
-
-
-
-
Una vez identificadas las llaves, deben de dar una definición para los siguientes términos, si consideran necesario ejemplificar para lograr una mejor explicación, pueden hacerlo.
-
Llave Primaria
-
Llave Foránea
-
Llave Alterna
-
-
-
-
-
-
- Especificaciones de entrega
-
-
- Un integrante del equipo deberá subir la solución vía Bitbucket o Github del equipo al ejercicio, indicando las matrículas y nombres de los miembros del equipo.
-
Desarrollar las expresiones en Álgebra relacional que representan las descripciones en lenguaje coloquial que a continuación se describen, en relación a los esquemas indicados.
-
-
-
-
-
-
-
-
- Instrucciones
-
-
-
- Convenio: para evitar las letras griegas originales del Álgebra relacional y simplificar la escritura en computadora utiliza la siguiente notación:
-
-
-
- SL {condición}: selección con el criterio condición.
-
-
- PR {lista de campos}: proyección de lista de campos.
-
-
- JN: reunión natural (natural join).
-
-
- JN {condición}: reunión con el criterio condición (teta join).
-
- Plantea expresiones en Álgebra relacional para las siguientes consultas:
-
-
-
-
- Títulos de películas en las que ha actuado Sharon Stone.
-
-
- Nombre e importe de ventas de los productores que han producido películas en las que ha actuado Tom Cruise.
-
-
- Dirección de los estudios en los que se han filmado películas con más de tres horas de duración
- en las que han actuado Salma Hayek o Antonio Banderas.
-
-
- Nombre de todo el elenco que participo en la película "Los enamorados" que fue producida por el estudio
- "Warner" de sexo femenino.
-
-
-
-
-
- El director de la compañía te pide un reporte con la Dirección, teléfono y sexo del actor que colaboró con los estudios
- con dirección "Epigmenio" y "La gran manzana" cuyo dicho estudio realizó películas tanto en el año 1999 y 2010.
-
-
-
-
-
-
-
-
-
-
-
- Especificaciones de entrega
-
-
- Un integrante del equipo deberá subir la solución vía Bitbucket o Github del equipo al ejercicio, indicando las matrículas y nombres de los miembros del equipo.
-
A modern JavaScript charting library that allows you to build interactive data visualizations with simple API and 100+ ready-to-use samples. Packed with the features that you expect, ApexCharts includes over a dozen chart types that deliver beautiful, responsive visualizations in your apps and dashboards. ApexCharts is an MIT-licensed open-source project that can be used in commercial and non-commercial projects.
+
+
+
+
+
+## Download and Installation
+
+##### Installing via npm
+
+```bash
+npm install apexcharts --save
+```
+
+##### Direct <script> include
+
+```html
+
+```
+
+## Wrappers for Vue/React/Angular/Stencil
+
+Integrate easily with 3rd party frameworks
+
+- [vue-apexcharts](https://github.com/apexcharts/vue-apexcharts)
+- [react-apexcharts](https://github.com/apexcharts/react-apexcharts)
+- [ng-apexcharts](https://github.com/apexcharts/ng-apexcharts) - Plugin by [Morris Janatzek](https://morrisj.net/)
+- [stencil-apexcharts](https://github.com/apexcharts/stencil-apexcharts)
+
+### Unofficial Wrappers
+
+Useful links to wrappers other than the popular frameworks mentioned above
+
+- [apexcharter](https://github.com/dreamRs/apexcharter) - Htmlwidget for ApexCharts
+- [apexcharts.rb](https://github.com/styd/apexcharts.rb) - Ruby wrapper for ApexCharts
+- [larapex-charts](https://github.com/ArielMejiaDev/larapex-charts) - Laravel wrapper for ApexCharts
+- [blazor-apexcharts](https://github.com/apexcharts/Blazor-ApexCharts) - Blazor wrapper for ApexCharts [demo](https://apexcharts.github.io/Blazor-ApexCharts/)
+- [svelte-apexcharts](https://github.com/galkatz373/svelte-apexcharts) - Svelte wrapper for ApexCharts
+
+
+## Usage
+
+```js
+import ApexCharts from 'apexcharts'
+```
+
+To create a basic bar chart with minimal configuration, write as follows:
+
+```js
+var options = {
+ chart: {
+ type: 'bar'
+ },
+ series: [
+ {
+ name: 'sales',
+ data: [30, 40, 35, 50, 49, 60, 70, 91, 125]
+ }
+ ],
+ xaxis: {
+ categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999]
+ }
+}
+
+var chart = new ApexCharts(document.querySelector('#chart'), options)
+chart.render()
+```
+
+This will render the following chart
+
+
+
+### A little more than the basic
+
+You can create a combination of different charts, sync them and give your desired look with unlimited possibilities.
+Below is an example of synchronized charts with github style.
+
+
+
+## Interactivity
+
+Zoom, Pan, and Scroll through data. Make selections and load other charts using those selections.
+An example showing some interactivity
+
+
+
+## Dynamic Series Update
+
+Another approach is to Drill down charts where one selection updates the data of other charts.
+An example of loading dynamic series into charts is shown below
+
+
+
+## Annotations
+
+Annotations allow you to write custom text on specific values or on axes values. Valuable to expand the visual appeal of your chart and make it more informative.
+
+
+
+## Mixed Charts
+
+You can combine more than one chart type to create a combo/mixed chart. Possible combinations can be line/area/column together in a single chart. Each chart type can have its own y-axis.
+
+
+
+## Candlestick
+
+Use a candlestick chart (a common financial chart) to describe price changes of a security, derivative, or currency. The below image shows how you can use another chart as a brush/preview pane which acts as a handle to browse the main candlestick chart.
+
+
+
+## Heatmaps
+
+Use Heatmaps to represent data through colors and shades. Frequently used with bigger data collections, they are valuable for recognizing patterns and areas of focus.
+
+
+
+## Gauges
+
+The tiny gauges are an important part of a dashboard and are useful in displaying single-series data. A demo of these gauges:
+
+
+
+## Sparklines
+
+Utilize sparklines to indicate trends in data, for example, occasional increments or declines, monetary cycles, or to feature the most extreme and least values:
+
+
+
+
+## Need Advanced Data Grid for your next project?
+We partnered with Infragistics, creators of the fastest data grids on the planet! Ignite UI Grids can handle unlimited rows and columns of data while providing access to custom templates and real-time data updates.
+
+
+
+Featuring an intuitive API for easy theming and branding, you can quickly bind to data with minimal hand-on coding. The grid is available in most of your favorite frameworks:
+
+Angular Data Grid | React Data Grid | Blazor Data Grid | Web Components DataGrid | jQuery Data Grid
+
+## What's included
+
+The download bundle includes the following files and directories providing a minified single file in the dist folder. Every asset including icon/css is bundled in the js itself to avoid loading multiple files.
+
+```
+apexcharts/
+├── dist/
+│ └── apexcharts.min.js
+├── src/
+│ ├── assets/
+│ ├── charts/
+│ ├── modules/
+│ ├── utils/
+│ └── apexcharts.js
+└── samples/
+```
+
+## Development
+
+#### Install dependencies and run the project
+
+```bash
+npm install
+npm run dev
+```
+
+This will start the webpack watch and any changes you make to `src` folder will auto-compile and output will be produced in the `dist` folder.
+
+More details in [Contributing Guidelines](CONTRIBUTING.md).
+
+#### Minifying the src
+
+```bash
+npm run build
+```
+
+## Where do I go next?
+
+Head over to the documentation section to read more about how to use different kinds of charts and explore all options.
+
+## Contacts
+
+Email: info@apexcharts.com
+
+Twitter: @apexcharts
+
+Facebook: fb.com/apexcharts
+
+## Dependency
+
+ApexCharts uses SVG.js for drawing shapes, animations, applying svg filters, and a lot more under the hood. The library is bundled in the final build file, so you don't need to include it.
+
+## License
+
+ApexCharts is released under MIT license. You are free to use, modify and distribute this software, as long as the copyright header is left intact.
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/package.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/package.json
new file mode 100644
index 0000000..0032266
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/package.json
@@ -0,0 +1,112 @@
+{
+ "name": "apexcharts",
+ "version": "3.54.1",
+ "description": "A JavaScript Chart Library",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/apexcharts/apexcharts.js.git"
+ },
+ "main": "dist/apexcharts.common.js",
+ "unpkg": "dist/apexcharts.js",
+ "jsdelivr": "dist/apexcharts.js",
+ "typings": "types/apexcharts.d.ts",
+ "files": [
+ "src",
+ "dist/*.js",
+ "dist/*.css",
+ "dist/locales/*.json",
+ "types/*.d.ts"
+ ],
+ "scripts": {
+ "dev": "rollup -w -c build/config.js --environment TARGET:web-umd-dev",
+ "dev:cjs": "rollup -w -c build/config.js --environment TARGET:web-cjs",
+ "bundle": "node build/build.js",
+ "build": "npm run bundle && webpack",
+ "build:umd": "rollup -w -c build/config.js --environment TARGET:web-umd-dev",
+ "build:amd": "webpack",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "test": "npm run e2e && npm run unit",
+ "test:ci": "npm run e2e:ci && npm run unit",
+ "unit": "jest tests/unit/",
+ "e2e": "node tests/e2e/samples.js test",
+ "e2e:update": "node tests/e2e/samples.js update",
+ "e2e:ci": "node tests/e2e/samples.js test:ci",
+ "build:samples": "node samples/source/index.js generate"
+ },
+ "dependencies": {
+ "@yr/monotone-cubic-spline": "^1.0.3",
+ "svg.draggable.js": "^2.2.2",
+ "svg.easing.js": "^2.0.0",
+ "svg.filter.js": "^2.0.2",
+ "svg.pathmorphing.js": "^0.1.3",
+ "svg.resize.js": "^1.4.3",
+ "svg.select.js": "^3.0.1"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.8.7",
+ "@babel/plugin-proposal-class-properties": "^7.8.3",
+ "@babel/preset-env": "^7.8.7",
+ "@rollup/plugin-babel": "^5.2.1",
+ "@rollup/plugin-json": "4.0.1",
+ "@rollup/plugin-node-resolve": "6.0.0",
+ "@rollup/plugin-replace": "2.3.0",
+ "@rollup/plugin-strip": "1.3.1",
+ "@rollup/plugin-terser": "0.4.4",
+ "babel-eslint": "10.0.3",
+ "babel-jest": "27.3.1",
+ "babel-loader": "8.0.6",
+ "babel-plugin-istanbul": "6.0.0",
+ "chalk": "3.0.0",
+ "css-loader": "6.10.0",
+ "eslint": "8.36.0",
+ "eslint-config-prettier": "8.8.0",
+ "eslint-loader": "3.0.3",
+ "eslint-plugin-import": "^2.27.5",
+ "eslint-plugin-prettier": "3.1.2",
+ "eslint-plugin-promise": "4.2.1",
+ "eslint-webpack-plugin": "4.0.0",
+ "fs-extra": "8.1.0",
+ "jest": "29.7.0",
+ "jest-environment-jsdom": "29.7.0",
+ "jest-puppeteer": "^10.0.1",
+ "nunjucks": "3.2.4",
+ "nyc": "15.0.0",
+ "pixelmatch": "5.1.0",
+ "pngjs": "3.4.0",
+ "postcss": "^8.4.21",
+ "prettier": "2.8.5",
+ "puppeteer": "22.12.1",
+ "puppeteer-cluster": "0.24.0",
+ "rollup": "3.29.5",
+ "rollup-plugin-babel": "4.4.0",
+ "rollup-plugin-copy-glob": "0.3.2",
+ "rollup-plugin-postcss": "4.0.2",
+ "rollup-plugin-svgo": "2.0.0",
+ "style-loader": "1.1.2",
+ "svg-inline-loader": "0.8.2",
+ "terser": "5.16.6",
+ "tslint": "6.1.3",
+ "typescript": "5.0.2",
+ "webpack": "5.94.0",
+ "webpack-bundle-analyzer": "4.8.0",
+ "webpack-cli": "5.0.1"
+ },
+ "bugs": {
+ "url": "https://github.com/apexcharts/apexcharts.js/issues"
+ },
+ "license": "MIT",
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://www.opensource.org/licenses/mit-license.php"
+ }
+ ],
+ "homepage": "https://apexcharts.com",
+ "keywords": [
+ "charts",
+ "graphs",
+ "visualizations",
+ "data"
+ ]
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/apexcharts.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/apexcharts.js
new file mode 100644
index 0000000..ddd53d1
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/apexcharts.js
@@ -0,0 +1,794 @@
+import Annotations from './modules/annotations/Annotations'
+import Base from './modules/Base'
+import CoreUtils from './modules/CoreUtils'
+import DataLabels from './modules/DataLabels'
+import Defaults from './modules/settings/Defaults'
+import Exports from './modules/Exports'
+import Grid from './modules/axes/Grid'
+import Markers from './modules/Markers'
+import Range from './modules/Range'
+import Utils from './utils/Utils'
+import XAxis from './modules/axes/XAxis'
+import YAxis from './modules/axes/YAxis'
+import InitCtxVariables from './modules/helpers/InitCtxVariables'
+import Destroy from './modules/helpers/Destroy'
+import { addResizeListener, removeResizeListener } from './utils/Resize'
+import apexCSS from './assets/apexcharts.css'
+
+/**
+ *
+ * @module ApexCharts
+ **/
+
+export default class ApexCharts {
+ constructor(el, opts) {
+ this.opts = opts
+ this.ctx = this
+
+ // Pass the user supplied options to the Base Class where these options will be extended with defaults. The returned object from Base Class will become the config object in the entire codebase.
+ this.w = new Base(opts).init()
+
+ this.el = el
+
+ this.w.globals.cuid = Utils.randomId()
+ this.w.globals.chartID = this.w.config.chart.id
+ ? Utils.escapeString(this.w.config.chart.id)
+ : this.w.globals.cuid
+
+ const initCtx = new InitCtxVariables(this)
+ initCtx.initModules()
+
+ this.create = Utils.bind(this.create, this)
+ this.windowResizeHandler = this._windowResizeHandler.bind(this)
+ this.parentResizeHandler = this._parentResizeCallback.bind(this)
+ }
+
+ /**
+ * The primary method user will call to render the chart.
+ */
+ render() {
+ // main method
+ return new Promise((resolve, reject) => {
+ // only draw chart, if element found
+ if (this.el !== null) {
+ if (typeof Apex._chartInstances === 'undefined') {
+ Apex._chartInstances = []
+ }
+ if (this.w.config.chart.id) {
+ Apex._chartInstances.push({
+ id: this.w.globals.chartID,
+ group: this.w.config.chart.group,
+ chart: this,
+ })
+ }
+
+ // set the locale here
+ this.setLocale(this.w.config.chart.defaultLocale)
+ const beforeMount = this.w.config.chart.events.beforeMount
+ if (typeof beforeMount === 'function') {
+ beforeMount(this, this.w)
+ }
+
+ this.events.fireEvent('beforeMount', [this, this.w])
+ window.addEventListener('resize', this.windowResizeHandler)
+ addResizeListener(this.el.parentNode, this.parentResizeHandler)
+
+ let rootNode = this.el.getRootNode && this.el.getRootNode()
+ let inShadowRoot = Utils.is('ShadowRoot', rootNode)
+ let doc = this.el.ownerDocument
+ let css = inShadowRoot
+ ? rootNode.getElementById('apexcharts-css')
+ : doc.getElementById('apexcharts-css')
+
+ if (!css) {
+ css = document.createElement('style')
+ css.id = 'apexcharts-css'
+ css.textContent = apexCSS
+ const nonce = this.opts.chart?.nonce || this.w.config.chart.nonce
+ if (nonce) {
+ css.setAttribute('nonce', nonce)
+ }
+
+ if (inShadowRoot) {
+ // We are in Shadow DOM, add to shadow root
+ rootNode.prepend(css)
+ } else {
+ // Add to of element's document
+ doc.head.appendChild(css)
+ }
+ }
+
+ let graphData = this.create(this.w.config.series, {})
+ if (!graphData) return resolve(this)
+ this.mount(graphData)
+ .then(() => {
+ if (typeof this.w.config.chart.events.mounted === 'function') {
+ this.w.config.chart.events.mounted(this, this.w)
+ }
+
+ this.events.fireEvent('mounted', [this, this.w])
+ resolve(graphData)
+ })
+ .catch((e) => {
+ reject(e)
+ // handle error in case no data or element not found
+ })
+ } else {
+ reject(new Error('Element not found'))
+ }
+ })
+ }
+
+ create(ser, opts) {
+ let w = this.w
+
+ const initCtx = new InitCtxVariables(this)
+ initCtx.initModules()
+ let gl = this.w.globals
+
+ gl.noData = false
+ gl.animationEnded = false
+
+ this.responsive.checkResponsiveConfig(opts)
+
+ if (w.config.xaxis.convertedCatToNumeric) {
+ const defaults = new Defaults(w.config)
+ defaults.convertCatToNumericXaxis(w.config, this.ctx)
+ }
+
+ if (this.el === null) {
+ gl.animationEnded = true
+ return null
+ }
+
+ this.core.setupElements()
+
+ if (w.config.chart.type === 'treemap') {
+ w.config.grid.show = false
+ w.config.yaxis[0].show = false
+ }
+
+ if (gl.svgWidth === 0) {
+ // if the element is hidden, skip drawing
+ gl.animationEnded = true
+ return null
+ }
+
+ let series = ser
+ ser.forEach((s, realIndex) => {
+ if (s.hidden) {
+ series = this.legend.legendHelpers.getSeriesAfterCollapsing({
+ realIndex,
+ })
+ }
+ })
+
+ const combo = CoreUtils.checkComboSeries(series, w.config.chart.type)
+ gl.comboCharts = combo.comboCharts
+ gl.comboBarCount = combo.comboBarCount
+
+ const allSeriesAreEmpty = series.every((s) => s.data && s.data.length === 0)
+
+ if (
+ series.length === 0 ||
+ (allSeriesAreEmpty && gl.collapsedSeries.length < 1)
+ ) {
+ this.series.handleNoData()
+ }
+
+ this.events.setupEventHandlers()
+
+ // Handle the data inputted by user and set some of the global variables (for eg, if data is datetime / numeric / category). Don't calculate the range / min / max at this time
+ this.data.parseData(series)
+
+ // this is a good time to set theme colors first
+ this.theme.init()
+
+ // as markers accepts array, we need to setup global markers for easier access
+ const markers = new Markers(this)
+ markers.setGlobalMarkerSize()
+
+ // labelFormatters should be called before dimensions as in dimensions we need text labels width
+ this.formatters.setLabelFormatters()
+ this.titleSubtitle.draw()
+
+ // legend is calculated here before coreCalculations because it affects the plottable area
+ // if there is some data to show or user collapsed all series, then proceed drawing legend
+ if (
+ !gl.noData ||
+ gl.collapsedSeries.length === gl.series.length ||
+ w.config.legend.showForSingleSeries
+ ) {
+ this.legend.init()
+ }
+
+ // check whether in multiple series, all series share the same X
+ this.series.hasAllSeriesEqualX()
+
+ // coreCalculations will give the min/max range and yaxis/axis values. It should be called here to set series variable from config to globals
+ if (gl.axisCharts) {
+ this.core.coreCalculations()
+ if (w.config.xaxis.type !== 'category') {
+ // as we have minX and maxX values, determine the default DateTimeFormat for time series
+ this.formatters.setLabelFormatters()
+ }
+ this.ctx.toolbar.minX = w.globals.minX
+ this.ctx.toolbar.maxX = w.globals.maxX
+ }
+
+ // we need to generate yaxis for heatmap separately as we are not showing numerics there, but seriesNames. There are some tweaks which are required for heatmap to align labels correctly which are done in below function
+ // Also we need to do this before calculating Dimensions plotCoords() method of Dimensions
+ this.formatters.heatmapLabelFormatters()
+
+ // get the largest marker size which will be needed in dimensions calc
+ const coreUtils = new CoreUtils(this)
+ coreUtils.getLargestMarkerSize()
+
+ // We got plottable area here, next task would be to calculate axis areas
+ this.dimensions.plotCoords()
+
+ const xyRatios = this.core.xySettings()
+
+ this.grid.createGridMask()
+
+ const elGraph = this.core.plotChartType(series, xyRatios)
+
+ const dataLabels = new DataLabels(this)
+ dataLabels.bringForward()
+ if (w.config.dataLabels.background.enabled) {
+ dataLabels.dataLabelsBackground()
+ }
+
+ // after all the drawing calculations, shift the graphical area (actual charts/bars) excluding legends
+ this.core.shiftGraphPosition()
+
+ const dim = {
+ plot: {
+ left: w.globals.translateX,
+ top: w.globals.translateY,
+ width: w.globals.gridWidth,
+ height: w.globals.gridHeight,
+ },
+ }
+
+ return {
+ elGraph,
+ xyRatios,
+ dimensions: dim,
+ }
+ }
+
+ mount(graphData = null) {
+ let me = this
+ let w = me.w
+
+ return new Promise((resolve, reject) => {
+ // no data to display
+ if (me.el === null) {
+ return reject(
+ new Error('Not enough data to display or target element not found')
+ )
+ } else if (graphData === null || w.globals.allSeriesCollapsed) {
+ me.series.handleNoData()
+ }
+
+ me.grid = new Grid(me)
+ let elgrid = me.grid.drawGrid()
+
+ me.annotations = new Annotations(me)
+ me.annotations.drawImageAnnos()
+ me.annotations.drawTextAnnos()
+
+ if (w.config.grid.position === 'back') {
+ if (elgrid) {
+ w.globals.dom.elGraphical.add(elgrid.el)
+ }
+ if (elgrid?.elGridBorders?.node) {
+ w.globals.dom.elGraphical.add(elgrid.elGridBorders)
+ }
+ }
+
+ if (Array.isArray(graphData.elGraph)) {
+ for (let g = 0; g < graphData.elGraph.length; g++) {
+ w.globals.dom.elGraphical.add(graphData.elGraph[g])
+ }
+ } else {
+ w.globals.dom.elGraphical.add(graphData.elGraph)
+ }
+
+ if (w.config.grid.position === 'front') {
+ if (elgrid) {
+ w.globals.dom.elGraphical.add(elgrid.el)
+ }
+ if (elgrid?.elGridBorders?.node) {
+ w.globals.dom.elGraphical.add(elgrid.elGridBorders)
+ }
+ }
+
+ if (w.config.xaxis.crosshairs.position === 'front') {
+ me.crosshairs.drawXCrosshairs()
+ }
+
+ if (w.config.yaxis[0].crosshairs.position === 'front') {
+ me.crosshairs.drawYCrosshairs()
+ }
+
+ if (w.config.chart.type !== 'treemap') {
+ me.axes.drawAxis(w.config.chart.type, elgrid)
+ }
+
+ let xAxis = new XAxis(this.ctx, elgrid)
+ let yaxis = new YAxis(this.ctx, elgrid)
+ if (elgrid !== null) {
+ xAxis.xAxisLabelCorrections(elgrid.xAxisTickWidth)
+ yaxis.setYAxisTextAlignments()
+
+ w.config.yaxis.map((yaxe, index) => {
+ if (w.globals.ignoreYAxisIndexes.indexOf(index) === -1) {
+ yaxis.yAxisTitleRotate(index, yaxe.opposite)
+ }
+ })
+ }
+
+ me.annotations.drawAxesAnnotations()
+
+ if (!w.globals.noData) {
+ // draw tooltips at the end
+ if (w.config.tooltip.enabled && !w.globals.noData) {
+ me.w.globals.tooltip.drawTooltip(graphData.xyRatios)
+ }
+
+ if (
+ w.globals.axisCharts &&
+ (w.globals.isXNumeric ||
+ w.config.xaxis.convertedCatToNumeric ||
+ w.globals.isRangeBar)
+ ) {
+ if (
+ w.config.chart.zoom.enabled ||
+ (w.config.chart.selection && w.config.chart.selection.enabled) ||
+ (w.config.chart.pan && w.config.chart.pan.enabled)
+ ) {
+ me.zoomPanSelection.init({
+ xyRatios: graphData.xyRatios,
+ })
+ }
+ } else {
+ const tools = w.config.chart.toolbar.tools
+ let toolsArr = [
+ 'zoom',
+ 'zoomin',
+ 'zoomout',
+ 'selection',
+ 'pan',
+ 'reset',
+ ]
+ toolsArr.forEach((t) => {
+ tools[t] = false
+ })
+ }
+
+ if (w.config.chart.toolbar.show && !w.globals.allSeriesCollapsed) {
+ me.toolbar.createToolbar()
+ }
+ }
+
+ if (w.globals.memory.methodsToExec.length > 0) {
+ w.globals.memory.methodsToExec.forEach((fn) => {
+ fn.method(fn.params, false, fn.context)
+ })
+ }
+
+ if (!w.globals.axisCharts && !w.globals.noData) {
+ me.core.resizeNonAxisCharts()
+ }
+ resolve(me)
+ })
+ }
+
+ /**
+ * Destroy the chart instance by removing all elements which also clean up event listeners on those elements.
+ */
+ destroy() {
+ window.removeEventListener('resize', this.windowResizeHandler)
+
+ removeResizeListener(this.el.parentNode, this.parentResizeHandler)
+ // remove the chart's instance from the global Apex._chartInstances
+ const chartID = this.w.config.chart.id
+ if (chartID) {
+ Apex._chartInstances.forEach((c, i) => {
+ if (c.id === Utils.escapeString(chartID)) {
+ Apex._chartInstances.splice(i, 1)
+ }
+ })
+ }
+ new Destroy(this.ctx).clear({ isUpdating: false })
+ }
+
+ /**
+ * Allows users to update Options after the chart has rendered.
+ *
+ * @param {object} options - A new config object can be passed which will be merged with the existing config object
+ * @param {boolean} redraw - should redraw from beginning or should use existing paths and redraw from there
+ * @param {boolean} animate - should animate or not on updating Options
+ */
+ updateOptions(
+ options,
+ redraw = false,
+ animate = true,
+ updateSyncedCharts = true,
+ overwriteInitialConfig = true
+ ) {
+ const w = this.w
+
+ // when called externally, clear some global variables
+ // fixes apexcharts.js#1488
+ w.globals.selection = undefined
+
+ if (options.series) {
+ this.series.resetSeries(false, true, false)
+ if (options.series.length && options.series[0].data) {
+ options.series = options.series.map((s, i) => {
+ return this.updateHelpers._extendSeries(s, i)
+ })
+ }
+
+ // user updated the series via updateOptions() function.
+ // Hence, we need to reset axis min/max to avoid zooming issues
+ this.updateHelpers.revertDefaultAxisMinMax()
+ }
+ // user has set x-axis min/max externally - hence we need to forcefully set the xaxis min/max
+ if (options.xaxis) {
+ options = this.updateHelpers.forceXAxisUpdate(options)
+ }
+ if (options.yaxis) {
+ options = this.updateHelpers.forceYAxisUpdate(options)
+ }
+ if (w.globals.collapsedSeriesIndices.length > 0) {
+ this.series.clearPreviousPaths()
+ }
+ /* update theme mode#459 */
+ if (options.theme) {
+ options = this.theme.updateThemeOptions(options)
+ }
+ return this.updateHelpers._updateOptions(
+ options,
+ redraw,
+ animate,
+ updateSyncedCharts,
+ overwriteInitialConfig
+ )
+ }
+
+ /**
+ * Allows users to update Series after the chart has rendered.
+ *
+ * @param {array} series - New series which will override the existing
+ */
+ updateSeries(newSeries = [], animate = true, overwriteInitialSeries = true) {
+ this.series.resetSeries(false)
+ this.updateHelpers.revertDefaultAxisMinMax()
+ return this.updateHelpers._updateSeries(
+ newSeries,
+ animate,
+ overwriteInitialSeries
+ )
+ }
+
+ /**
+ * Allows users to append a new series after the chart has rendered.
+ *
+ * @param {array} newSerie - New serie which will be appended to the existing series
+ */
+ appendSeries(newSerie, animate = true, overwriteInitialSeries = true) {
+ const newSeries = this.w.config.series.slice()
+ newSeries.push(newSerie)
+ this.series.resetSeries(false)
+ this.updateHelpers.revertDefaultAxisMinMax()
+ return this.updateHelpers._updateSeries(
+ newSeries,
+ animate,
+ overwriteInitialSeries
+ )
+ }
+
+ /**
+ * Allows users to append Data to series.
+ *
+ * @param {array} newData - New data in the same format as series
+ */
+ appendData(newData, overwriteInitialSeries = true) {
+ let me = this
+
+ me.w.globals.dataChanged = true
+
+ me.series.getPreviousPaths()
+
+ let newSeries = me.w.config.series.slice()
+
+ for (let i = 0; i < newSeries.length; i++) {
+ if (newData[i] !== null && typeof newData[i] !== 'undefined') {
+ for (let j = 0; j < newData[i].data.length; j++) {
+ newSeries[i].data.push(newData[i].data[j])
+ }
+ }
+ }
+ me.w.config.series = newSeries
+ if (overwriteInitialSeries) {
+ me.w.globals.initialSeries = Utils.clone(me.w.config.series)
+ }
+
+ return this.update()
+ }
+
+ update(options) {
+ return new Promise((resolve, reject) => {
+ new Destroy(this.ctx).clear({ isUpdating: true })
+
+ const graphData = this.create(this.w.config.series, options)
+ if (!graphData) return resolve(this)
+ this.mount(graphData)
+ .then(() => {
+ if (typeof this.w.config.chart.events.updated === 'function') {
+ this.w.config.chart.events.updated(this, this.w)
+ }
+ this.events.fireEvent('updated', [this, this.w])
+
+ this.w.globals.isDirty = true
+
+ resolve(this)
+ })
+ .catch((e) => {
+ reject(e)
+ })
+ })
+ }
+
+ /**
+ * Get all charts in the same "group" (including the instance which is called upon) to sync them when user zooms in/out or pan.
+ */
+ getSyncedCharts() {
+ const chartGroups = this.getGroupedCharts()
+ let allCharts = [this]
+ if (chartGroups.length) {
+ allCharts = []
+ chartGroups.forEach((ch) => {
+ allCharts.push(ch)
+ })
+ }
+
+ return allCharts
+ }
+
+ /**
+ * Get charts in the same "group" (excluding the instance which is called upon) to perform operations on the other charts of the same group (eg., tooltip hovering)
+ */
+ getGroupedCharts() {
+ return Apex._chartInstances
+ .filter((ch) => {
+ if (ch.group) {
+ return true
+ }
+ })
+ .map((ch) => (this.w.config.chart.group === ch.group ? ch.chart : this))
+ }
+
+ static getChartByID(id) {
+ const chartId = Utils.escapeString(id)
+ if (!Apex._chartInstances) return undefined
+
+ const c = Apex._chartInstances.filter((ch) => ch.id === chartId)[0]
+ return c && c.chart
+ }
+
+ /**
+ * Allows the user to provide data attrs in the element and the chart will render automatically when this method is called by searching for the elements containing 'data-apexcharts' attribute
+ */
+ static initOnLoad() {
+ const els = document.querySelectorAll('[data-apexcharts]')
+
+ for (let i = 0; i < els.length; i++) {
+ const el = els[i]
+ const options = JSON.parse(els[i].getAttribute('data-options'))
+ const apexChart = new ApexCharts(el, options)
+ apexChart.render()
+ }
+ }
+
+ /**
+ * This static method allows users to call chart methods without necessarily from the
+ * instance of the chart in case user has assigned chartID to the targeted chart.
+ * The chartID is used for mapping the instance stored in Apex._chartInstances global variable
+ *
+ * This is helpful in cases when you don't have reference of the chart instance
+ * easily and need to call the method from anywhere.
+ * For eg, in React/Vue applications when you have many parent/child components,
+ * and need easy reference to other charts for performing dynamic operations
+ *
+ * @param {string} chartID - The unique identifier which will be used to call methods
+ * on that chart instance
+ * @param {function} fn - The method name to call
+ * @param {object} opts - The parameters which are accepted in the original method will be passed here in the same order.
+ */
+ static exec(chartID, fn, ...opts) {
+ const chart = this.getChartByID(chartID)
+ if (!chart) return
+
+ // turn on the global exec flag to indicate this method was called
+ chart.w.globals.isExecCalled = true
+
+ let ret = null
+ if (chart.publicMethods.indexOf(fn) !== -1) {
+ ret = chart[fn](...opts)
+ }
+ return ret
+ }
+
+ static merge(target, source) {
+ return Utils.extend(target, source)
+ }
+
+ toggleSeries(seriesName) {
+ return this.series.toggleSeries(seriesName)
+ }
+
+ highlightSeriesOnLegendHover(e, targetElement) {
+ return this.series.toggleSeriesOnHover(e, targetElement)
+ }
+
+ showSeries(seriesName) {
+ this.series.showSeries(seriesName)
+ }
+
+ hideSeries(seriesName) {
+ this.series.hideSeries(seriesName)
+ }
+
+ highlightSeries(seriesName) {
+ this.series.highlightSeries(seriesName)
+ }
+
+ isSeriesHidden(seriesName) {
+ this.series.isSeriesHidden(seriesName)
+ }
+
+ resetSeries(shouldUpdateChart = true, shouldResetZoom = true) {
+ this.series.resetSeries(shouldUpdateChart, shouldResetZoom)
+ }
+
+ // Public method to add event listener on chart context
+ addEventListener(name, handler) {
+ this.events.addEventListener(name, handler)
+ }
+
+ // Public method to remove event listener on chart context
+ removeEventListener(name, handler) {
+ this.events.removeEventListener(name, handler)
+ }
+
+ addXaxisAnnotation(opts, pushToMemory = true, context = undefined) {
+ let me = this
+ if (context) {
+ me = context
+ }
+ me.annotations.addXaxisAnnotationExternal(opts, pushToMemory, me)
+ }
+
+ addYaxisAnnotation(opts, pushToMemory = true, context = undefined) {
+ let me = this
+ if (context) {
+ me = context
+ }
+ me.annotations.addYaxisAnnotationExternal(opts, pushToMemory, me)
+ }
+
+ addPointAnnotation(opts, pushToMemory = true, context = undefined) {
+ let me = this
+ if (context) {
+ me = context
+ }
+ me.annotations.addPointAnnotationExternal(opts, pushToMemory, me)
+ }
+
+ clearAnnotations(context = undefined) {
+ let me = this
+ if (context) {
+ me = context
+ }
+ me.annotations.clearAnnotations(me)
+ }
+
+ removeAnnotation(id, context = undefined) {
+ let me = this
+ if (context) {
+ me = context
+ }
+ me.annotations.removeAnnotation(me, id)
+ }
+
+ getChartArea() {
+ const el = this.w.globals.dom.baseEl.querySelector('.apexcharts-inner')
+
+ return el
+ }
+
+ getSeriesTotalXRange(minX, maxX) {
+ return this.coreUtils.getSeriesTotalsXRange(minX, maxX)
+ }
+
+ getHighestValueInSeries(seriesIndex = 0) {
+ const range = new Range(this.ctx)
+ return range.getMinYMaxY(seriesIndex).highestY
+ }
+
+ getLowestValueInSeries(seriesIndex = 0) {
+ const range = new Range(this.ctx)
+ return range.getMinYMaxY(seriesIndex).lowestY
+ }
+
+ getSeriesTotal() {
+ return this.w.globals.seriesTotals
+ }
+
+ toggleDataPointSelection(seriesIndex, dataPointIndex) {
+ return this.updateHelpers.toggleDataPointSelection(
+ seriesIndex,
+ dataPointIndex
+ )
+ }
+
+ zoomX(min, max) {
+ this.ctx.toolbar.zoomUpdateOptions(min, max)
+ }
+
+ setLocale(localeName) {
+ this.localization.setCurrentLocaleValues(localeName)
+ }
+
+ dataURI(options) {
+ const exp = new Exports(this.ctx)
+ return exp.dataURI(options)
+ }
+
+ exportToCSV(options = {}) {
+ const exp = new Exports(this.ctx)
+ return exp.exportToCSV(options)
+ }
+
+ paper() {
+ return this.w.globals.dom.Paper
+ }
+
+ _parentResizeCallback() {
+ if (
+ this.w.globals.animationEnded &&
+ this.w.config.chart.redrawOnParentResize
+ ) {
+ this._windowResize()
+ }
+ }
+
+ /**
+ * Handle window resize and re-draw the whole chart.
+ */
+ _windowResize() {
+ clearTimeout(this.w.globals.resizeTimer)
+ this.w.globals.resizeTimer = window.setTimeout(() => {
+ this.w.globals.resized = true
+ this.w.globals.dataChanged = false
+
+ // we need to redraw the whole chart on window resize (with a small delay).
+ this.ctx.update()
+ }, 150)
+ }
+
+ _windowResizeHandler() {
+ let { redrawOnWindowResize: redraw } = this.w.config.chart
+
+ if (typeof redraw === 'function') {
+ redraw = redraw()
+ }
+
+ redraw && this._windowResize()
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/apexcharts.css b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/apexcharts.css
new file mode 100644
index 0000000..a558fc8
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/apexcharts.css
@@ -0,0 +1,683 @@
+@keyframes opaque {
+ 0% {
+ opacity: 0
+ }
+
+ to {
+ opacity: 1
+ }
+}
+
+@keyframes resizeanim {
+
+ 0%,
+ to {
+ opacity: 0
+ }
+}
+
+.apexcharts-canvas {
+ position: relative;
+ direction: ltr !important;
+ user-select: none
+}
+
+.apexcharts-canvas ::-webkit-scrollbar {
+ -webkit-appearance: none;
+ width: 6px
+}
+
+.apexcharts-canvas ::-webkit-scrollbar-thumb {
+ border-radius: 4px;
+ background-color: rgba(0, 0, 0, .5);
+ box-shadow: 0 0 1px rgba(255, 255, 255, .5);
+ -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, .5)
+}
+
+.apexcharts-inner {
+ position: relative
+}
+
+.apexcharts-text tspan {
+ font-family: inherit
+}
+
+rect.legend-mouseover-inactive,
+.legend-mouseover-inactive rect,
+.legend-mouseover-inactive path,
+.legend-mouseover-inactive circle,
+.legend-mouseover-inactive line,
+.legend-mouseover-inactive text.apexcharts-yaxis-title-text,
+.legend-mouseover-inactive text.apexcharts-yaxis-label {
+ transition: .15s ease all;
+ opacity: .2
+}
+
+.apexcharts-legend-text {
+ padding-left: 15px;
+ margin-left: -15px;
+}
+
+.apexcharts-series-collapsed {
+ opacity: 0
+}
+
+.apexcharts-tooltip {
+ border-radius: 5px;
+ box-shadow: 2px 2px 6px -4px #999;
+ cursor: default;
+ font-size: 14px;
+ left: 62px;
+ opacity: 0;
+ pointer-events: none;
+ position: absolute;
+ top: 20px;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ white-space: nowrap;
+ z-index: 12;
+ transition: .15s ease all
+}
+
+.apexcharts-tooltip.apexcharts-active {
+ opacity: 1;
+ transition: .15s ease all
+}
+
+.apexcharts-tooltip.apexcharts-theme-light {
+ border: 1px solid #e3e3e3;
+ background: rgba(255, 255, 255, .96)
+}
+
+.apexcharts-tooltip.apexcharts-theme-dark {
+ color: #fff;
+ background: rgba(30, 30, 30, .8)
+}
+
+.apexcharts-tooltip * {
+ font-family: inherit
+}
+
+.apexcharts-tooltip-title {
+ padding: 6px;
+ font-size: 15px;
+ margin-bottom: 4px
+}
+
+.apexcharts-tooltip.apexcharts-theme-light .apexcharts-tooltip-title {
+ background: #eceff1;
+ border-bottom: 1px solid #ddd
+}
+
+.apexcharts-tooltip.apexcharts-theme-dark .apexcharts-tooltip-title {
+ background: rgba(0, 0, 0, .7);
+ border-bottom: 1px solid #333
+}
+
+.apexcharts-tooltip-text-goals-value,
+.apexcharts-tooltip-text-y-value,
+.apexcharts-tooltip-text-z-value {
+ display: inline-block;
+ margin-left: 5px;
+ font-weight: 600
+}
+
+.apexcharts-tooltip-text-goals-label:empty,
+.apexcharts-tooltip-text-goals-value:empty,
+.apexcharts-tooltip-text-y-label:empty,
+.apexcharts-tooltip-text-y-value:empty,
+.apexcharts-tooltip-text-z-value:empty,
+.apexcharts-tooltip-title:empty {
+ display: none
+}
+
+.apexcharts-tooltip-text-goals-label,
+.apexcharts-tooltip-text-goals-value {
+ padding: 6px 0 5px
+}
+
+.apexcharts-tooltip-goals-group,
+.apexcharts-tooltip-text-goals-label,
+.apexcharts-tooltip-text-goals-value {
+ display: flex
+}
+
+.apexcharts-tooltip-text-goals-label:not(:empty),
+.apexcharts-tooltip-text-goals-value:not(:empty) {
+ margin-top: -6px
+}
+
+.apexcharts-tooltip-marker {
+ width: 12px;
+ height: 12px;
+ position: relative;
+ top: 0;
+ margin-right: 10px;
+ border-radius: 50%
+}
+
+.apexcharts-tooltip-series-group {
+ padding: 0 10px;
+ display: none;
+ text-align: left;
+ justify-content: left;
+ align-items: center
+}
+
+.apexcharts-tooltip-series-group.apexcharts-active .apexcharts-tooltip-marker {
+ opacity: 1
+}
+
+.apexcharts-tooltip-series-group.apexcharts-active,
+.apexcharts-tooltip-series-group:last-child {
+ padding-bottom: 4px
+}
+
+.apexcharts-tooltip-y-group {
+ padding: 6px 0 5px
+}
+
+.apexcharts-custom-tooltip,
+.apexcharts-tooltip-box {
+ padding: 4px 8px
+}
+
+.apexcharts-tooltip-boxPlot {
+ display: flex;
+ flex-direction: column-reverse
+}
+
+.apexcharts-tooltip-box>div {
+ margin: 4px 0
+}
+
+.apexcharts-tooltip-box span.value {
+ font-weight: 700
+}
+
+.apexcharts-tooltip-rangebar {
+ padding: 5px 8px
+}
+
+.apexcharts-tooltip-rangebar .category {
+ font-weight: 600;
+ color: #777
+}
+
+.apexcharts-tooltip-rangebar .series-name {
+ font-weight: 700;
+ display: block;
+ margin-bottom: 5px
+}
+
+.apexcharts-xaxistooltip,
+.apexcharts-yaxistooltip {
+ opacity: 0;
+ pointer-events: none;
+ color: #373d3f;
+ font-size: 13px;
+ text-align: center;
+ border-radius: 2px;
+ position: absolute;
+ z-index: 10;
+ background: #eceff1;
+ border: 1px solid #90a4ae
+}
+
+.apexcharts-xaxistooltip {
+ padding: 9px 10px;
+ transition: .15s ease all
+}
+
+.apexcharts-xaxistooltip.apexcharts-theme-dark {
+ background: rgba(0, 0, 0, .7);
+ border: 1px solid rgba(0, 0, 0, .5);
+ color: #fff
+}
+
+.apexcharts-xaxistooltip:after,
+.apexcharts-xaxistooltip:before {
+ left: 50%;
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none
+}
+
+.apexcharts-xaxistooltip:after {
+ border-color: transparent;
+ border-width: 6px;
+ margin-left: -6px
+}
+
+.apexcharts-xaxistooltip:before {
+ border-color: transparent;
+ border-width: 7px;
+ margin-left: -7px
+}
+
+.apexcharts-xaxistooltip-bottom:after,
+.apexcharts-xaxistooltip-bottom:before {
+ bottom: 100%
+}
+
+.apexcharts-xaxistooltip-top:after,
+.apexcharts-xaxistooltip-top:before {
+ top: 100%
+}
+
+.apexcharts-xaxistooltip-bottom:after {
+ border-bottom-color: #eceff1
+}
+
+.apexcharts-xaxistooltip-bottom:before {
+ border-bottom-color: #90a4ae
+}
+
+.apexcharts-xaxistooltip-bottom.apexcharts-theme-dark:after,
+.apexcharts-xaxistooltip-bottom.apexcharts-theme-dark:before {
+ border-bottom-color: rgba(0, 0, 0, .5)
+}
+
+.apexcharts-xaxistooltip-top:after {
+ border-top-color: #eceff1
+}
+
+.apexcharts-xaxistooltip-top:before {
+ border-top-color: #90a4ae
+}
+
+.apexcharts-xaxistooltip-top.apexcharts-theme-dark:after,
+.apexcharts-xaxistooltip-top.apexcharts-theme-dark:before {
+ border-top-color: rgba(0, 0, 0, .5)
+}
+
+.apexcharts-xaxistooltip.apexcharts-active {
+ opacity: 1;
+ transition: .15s ease all
+}
+
+.apexcharts-yaxistooltip {
+ padding: 4px 10px
+}
+
+.apexcharts-yaxistooltip.apexcharts-theme-dark {
+ background: rgba(0, 0, 0, .7);
+ border: 1px solid rgba(0, 0, 0, .5);
+ color: #fff
+}
+
+.apexcharts-yaxistooltip:after,
+.apexcharts-yaxistooltip:before {
+ top: 50%;
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none
+}
+
+.apexcharts-yaxistooltip:after {
+ border-color: transparent;
+ border-width: 6px;
+ margin-top: -6px
+}
+
+.apexcharts-yaxistooltip:before {
+ border-color: transparent;
+ border-width: 7px;
+ margin-top: -7px
+}
+
+.apexcharts-yaxistooltip-left:after,
+.apexcharts-yaxistooltip-left:before {
+ left: 100%
+}
+
+.apexcharts-yaxistooltip-right:after,
+.apexcharts-yaxistooltip-right:before {
+ right: 100%
+}
+
+.apexcharts-yaxistooltip-left:after {
+ border-left-color: #eceff1
+}
+
+.apexcharts-yaxistooltip-left:before {
+ border-left-color: #90a4ae
+}
+
+.apexcharts-yaxistooltip-left.apexcharts-theme-dark:after,
+.apexcharts-yaxistooltip-left.apexcharts-theme-dark:before {
+ border-left-color: rgba(0, 0, 0, .5)
+}
+
+.apexcharts-yaxistooltip-right:after {
+ border-right-color: #eceff1
+}
+
+.apexcharts-yaxistooltip-right:before {
+ border-right-color: #90a4ae
+}
+
+.apexcharts-yaxistooltip-right.apexcharts-theme-dark:after,
+.apexcharts-yaxistooltip-right.apexcharts-theme-dark:before {
+ border-right-color: rgba(0, 0, 0, .5)
+}
+
+.apexcharts-yaxistooltip.apexcharts-active {
+ opacity: 1
+}
+
+.apexcharts-yaxistooltip-hidden {
+ display: none
+}
+
+.apexcharts-xcrosshairs,
+.apexcharts-ycrosshairs {
+ pointer-events: none;
+ opacity: 0;
+ transition: .15s ease all
+}
+
+.apexcharts-xcrosshairs.apexcharts-active,
+.apexcharts-ycrosshairs.apexcharts-active {
+ opacity: 1;
+ transition: .15s ease all
+}
+
+.apexcharts-ycrosshairs-hidden {
+ opacity: 0
+}
+
+.apexcharts-selection-rect {
+ cursor: move
+}
+
+.svg_select_boundingRect,
+.svg_select_points_rot {
+ pointer-events: none;
+ opacity: 0;
+ visibility: hidden
+}
+
+.apexcharts-selection-rect+g .svg_select_boundingRect,
+.apexcharts-selection-rect+g .svg_select_points_rot {
+ opacity: 0;
+ visibility: hidden
+}
+
+.apexcharts-selection-rect+g .svg_select_points_l,
+.apexcharts-selection-rect+g .svg_select_points_r {
+ cursor: ew-resize;
+ opacity: 1;
+ visibility: visible
+}
+
+.svg_select_points {
+ fill: #efefef;
+ stroke: #333;
+ rx: 2
+}
+
+.apexcharts-svg.apexcharts-zoomable.hovering-zoom {
+ cursor: crosshair
+}
+
+.apexcharts-svg.apexcharts-zoomable.hovering-pan {
+ cursor: move
+}
+
+.apexcharts-menu-icon,
+.apexcharts-pan-icon,
+.apexcharts-reset-icon,
+.apexcharts-selection-icon,
+.apexcharts-toolbar-custom-icon,
+.apexcharts-zoom-icon,
+.apexcharts-zoomin-icon,
+.apexcharts-zoomout-icon {
+ cursor: pointer;
+ width: 20px;
+ height: 20px;
+ line-height: 24px;
+ color: #6e8192;
+ text-align: center
+}
+
+.apexcharts-menu-icon svg,
+.apexcharts-reset-icon svg,
+.apexcharts-zoom-icon svg,
+.apexcharts-zoomin-icon svg,
+.apexcharts-zoomout-icon svg {
+ fill: #6e8192
+}
+
+.apexcharts-selection-icon svg {
+ fill: #444;
+ transform: scale(.76)
+}
+
+.apexcharts-theme-dark .apexcharts-menu-icon svg,
+.apexcharts-theme-dark .apexcharts-pan-icon svg,
+.apexcharts-theme-dark .apexcharts-reset-icon svg,
+.apexcharts-theme-dark .apexcharts-selection-icon svg,
+.apexcharts-theme-dark .apexcharts-toolbar-custom-icon svg,
+.apexcharts-theme-dark .apexcharts-zoom-icon svg,
+.apexcharts-theme-dark .apexcharts-zoomin-icon svg,
+.apexcharts-theme-dark .apexcharts-zoomout-icon svg {
+ fill: #f3f4f5
+}
+
+.apexcharts-canvas .apexcharts-reset-zoom-icon.apexcharts-selected svg,
+.apexcharts-canvas .apexcharts-selection-icon.apexcharts-selected svg,
+.apexcharts-canvas .apexcharts-zoom-icon.apexcharts-selected svg {
+ fill: #008ffb
+}
+
+.apexcharts-theme-light .apexcharts-menu-icon:hover svg,
+.apexcharts-theme-light .apexcharts-reset-icon:hover svg,
+.apexcharts-theme-light .apexcharts-selection-icon:not(.apexcharts-selected):hover svg,
+.apexcharts-theme-light .apexcharts-zoom-icon:not(.apexcharts-selected):hover svg,
+.apexcharts-theme-light .apexcharts-zoomin-icon:hover svg,
+.apexcharts-theme-light .apexcharts-zoomout-icon:hover svg {
+ fill: #333
+}
+
+.apexcharts-menu-icon,
+.apexcharts-selection-icon {
+ position: relative
+}
+
+.apexcharts-reset-icon {
+ margin-left: 5px
+}
+
+.apexcharts-menu-icon,
+.apexcharts-reset-icon,
+.apexcharts-zoom-icon {
+ transform: scale(.85)
+}
+
+.apexcharts-zoomin-icon,
+.apexcharts-zoomout-icon {
+ transform: scale(.7)
+}
+
+.apexcharts-zoomout-icon {
+ margin-right: 3px
+}
+
+.apexcharts-pan-icon {
+ transform: scale(.62);
+ position: relative;
+ left: 1px;
+ top: 0
+}
+
+.apexcharts-pan-icon svg {
+ fill: #fff;
+ stroke: #6e8192;
+ stroke-width: 2
+}
+
+.apexcharts-pan-icon.apexcharts-selected svg {
+ stroke: #008ffb
+}
+
+.apexcharts-pan-icon:not(.apexcharts-selected):hover svg {
+ stroke: #333
+}
+
+.apexcharts-toolbar {
+ position: absolute;
+ z-index: 11;
+ max-width: 176px;
+ text-align: right;
+ border-radius: 3px;
+ padding: 0 6px 2px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center
+}
+
+.apexcharts-menu {
+ background: #fff;
+ position: absolute;
+ top: 100%;
+ border: 1px solid #ddd;
+ border-radius: 3px;
+ padding: 3px;
+ right: 10px;
+ opacity: 0;
+ min-width: 110px;
+ transition: .15s ease all;
+ pointer-events: none
+}
+
+.apexcharts-menu.apexcharts-menu-open {
+ opacity: 1;
+ pointer-events: all;
+ transition: .15s ease all
+}
+
+.apexcharts-menu-item {
+ padding: 6px 7px;
+ font-size: 12px;
+ cursor: pointer
+}
+
+.apexcharts-theme-light .apexcharts-menu-item:hover {
+ background: #eee
+}
+
+.apexcharts-theme-dark .apexcharts-menu {
+ background: rgba(0, 0, 0, .7);
+ color: #fff
+}
+
+@media screen and (min-width:768px) {
+ .apexcharts-canvas:hover .apexcharts-toolbar {
+ opacity: 1
+ }
+}
+
+.apexcharts-canvas .apexcharts-element-hidden,
+.apexcharts-datalabel.apexcharts-element-hidden,
+.apexcharts-hide .apexcharts-series-points {
+ opacity: 0;
+}
+
+.apexcharts-hidden-element-shown {
+ opacity: 1;
+ transition: 0.25s ease all;
+}
+
+.apexcharts-datalabel,
+.apexcharts-datalabel-label,
+.apexcharts-datalabel-value,
+.apexcharts-datalabels,
+.apexcharts-pie-label {
+ cursor: default;
+ pointer-events: none
+}
+
+.apexcharts-pie-label-delay {
+ opacity: 0;
+ animation-name: opaque;
+ animation-duration: .3s;
+ animation-fill-mode: forwards;
+ animation-timing-function: ease
+}
+
+.apexcharts-radialbar-label {
+ cursor: pointer;
+}
+
+.apexcharts-annotation-rect,
+.apexcharts-area-series .apexcharts-area,
+.apexcharts-area-series .apexcharts-series-markers .apexcharts-marker.no-pointer-events,
+.apexcharts-gridline,
+.apexcharts-line,
+.apexcharts-line-series .apexcharts-series-markers .apexcharts-marker.no-pointer-events,
+.apexcharts-point-annotation-label,
+.apexcharts-radar-series path:not(.apexcharts-marker),
+.apexcharts-radar-series polygon,
+.apexcharts-toolbar svg,
+.apexcharts-tooltip .apexcharts-marker,
+.apexcharts-xaxis-annotation-label,
+.apexcharts-yaxis-annotation-label,
+.apexcharts-zoom-rect {
+ pointer-events: none
+}
+
+.apexcharts-tooltip-active .apexcharts-marker {
+ transition: .15s ease all
+}
+
+.resize-triggers {
+ animation: 1ms resizeanim;
+ visibility: hidden;
+ opacity: 0;
+ height: 100%;
+ width: 100%;
+ overflow: hidden
+}
+
+.contract-trigger:before,
+.resize-triggers,
+.resize-triggers>div {
+ content: " ";
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0
+}
+
+.resize-triggers>div {
+ height: 100%;
+ width: 100%;
+ background: #eee;
+ overflow: auto
+}
+
+.contract-trigger:before {
+ overflow: hidden;
+ width: 200%;
+ height: 200%
+}
+
+.apexcharts-bar-goals-markers {
+ pointer-events: none
+}
+
+.apexcharts-bar-shadows {
+ pointer-events: none
+}
+
+.apexcharts-rangebar-goals-markers {
+ pointer-events: none
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-camera.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-camera.svg
new file mode 100644
index 0000000..3f052f2
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-camera.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-home.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-home.svg
new file mode 100644
index 0000000..676d2d3
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-home.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-menu.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-menu.svg
new file mode 100644
index 0000000..770b192
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-menu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-minus-square.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-minus-square.svg
new file mode 100644
index 0000000..c4988e8
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-minus-square.svg
@@ -0,0 +1,9 @@
+
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-minus.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-minus.svg
new file mode 100644
index 0000000..f0a7ec8
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-minus.svg
@@ -0,0 +1,4 @@
+
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-pan-hand.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-pan-hand.svg
new file mode 100644
index 0000000..1768e5e
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-pan-hand.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-pan.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-pan.svg
new file mode 100644
index 0000000..ae65a94
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-pan.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-plus-square.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-plus-square.svg
new file mode 100644
index 0000000..f1b885f
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-plus-square.svg
@@ -0,0 +1,4 @@
+
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-plus.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-plus.svg
new file mode 100644
index 0000000..b376ab5
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-plus.svg
@@ -0,0 +1,4 @@
+
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-refresh.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-refresh.svg
new file mode 100644
index 0000000..81c46c6
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-refresh.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-reset.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-reset.svg
new file mode 100644
index 0000000..2ee1dc3
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-reset.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-select.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-select.svg
new file mode 100644
index 0000000..326ab03
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-select.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-select1.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-select1.svg
new file mode 100644
index 0000000..529a226
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-select1.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-zoom-in.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-zoom-in.svg
new file mode 100644
index 0000000..3d9355a
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-zoom-in.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-zoom-out.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-zoom-out.svg
new file mode 100644
index 0000000..74310b6
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-zoom-out.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-zoom.svg b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-zoom.svg
new file mode 100644
index 0000000..346fdb4
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/assets/ico-zoom.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Bar.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Bar.js
new file mode 100644
index 0000000..bbc9694
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Bar.js
@@ -0,0 +1,674 @@
+import BarDataLabels from './common/bar/DataLabels'
+import BarHelpers from './common/bar/Helpers'
+import CoreUtils from '../modules/CoreUtils'
+import Utils from '../utils/Utils'
+import Filters from '../modules/Filters'
+import Graphics from '../modules/Graphics'
+import Series from '../modules/Series'
+
+/**
+ * ApexCharts Bar Class responsible for drawing both Columns and Bars.
+ *
+ * @module Bar
+ **/
+
+class Bar {
+ constructor(ctx, xyRatios) {
+ this.ctx = ctx
+ this.w = ctx.w
+ const w = this.w
+ this.barOptions = w.config.plotOptions.bar
+
+ this.isHorizontal = this.barOptions.horizontal
+ this.strokeWidth = w.config.stroke.width
+ this.isNullValue = false
+
+ this.isRangeBar = w.globals.seriesRange.length && this.isHorizontal
+
+ this.isVerticalGroupedRangeBar =
+ !w.globals.isBarHorizontal &&
+ w.globals.seriesRange.length &&
+ w.config.plotOptions.bar.rangeBarGroupRows
+
+ this.isFunnel = this.barOptions.isFunnel
+ this.xyRatios = xyRatios
+
+ if (this.xyRatios !== null) {
+ this.xRatio = xyRatios.xRatio
+ this.yRatio = xyRatios.yRatio
+ this.invertedXRatio = xyRatios.invertedXRatio
+ this.invertedYRatio = xyRatios.invertedYRatio
+ this.baseLineY = xyRatios.baseLineY
+ this.baseLineInvertedY = xyRatios.baseLineInvertedY
+ }
+ this.yaxisIndex = 0
+ this.translationsIndex = 0
+ this.seriesLen = 0
+ this.pathArr = []
+
+ const ser = new Series(this.ctx)
+ this.lastActiveBarSerieIndex = ser.getActiveConfigSeriesIndex('desc', [
+ 'bar',
+ 'column',
+ ])
+
+ this.columnGroupIndices = []
+ const barSeriesIndices = ser.getBarSeriesIndices()
+ const coreUtils = new CoreUtils(this.ctx)
+ this.stackedSeriesTotals = coreUtils.getStackedSeriesTotals(
+ this.w.config.series
+ .map((s, i) => {
+ return barSeriesIndices.indexOf(i) === -1 ? i : -1
+ })
+ .filter((s) => {
+ return s !== -1
+ })
+ )
+
+ this.barHelpers = new BarHelpers(this)
+ }
+
+ /** primary draw method which is called on bar object
+ * @memberof Bar
+ * @param {array} series - user supplied series values
+ * @param {int} seriesIndex - the index by which series will be drawn on the svg
+ * @return {node} element which is supplied to parent chart draw method for appending
+ **/
+ draw(series, seriesIndex) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+
+ const coreUtils = new CoreUtils(this.ctx, w)
+ series = coreUtils.getLogSeries(series)
+ this.series = series
+ this.yRatio = coreUtils.getLogYRatios(this.yRatio)
+
+ this.barHelpers.initVariables(series)
+
+ let ret = graphics.group({
+ class: 'apexcharts-bar-series apexcharts-plot-series',
+ })
+
+ if (w.config.dataLabels.enabled) {
+ if (this.totalItems > this.barOptions.dataLabels.maxItems) {
+ console.warn(
+ 'WARNING: DataLabels are enabled but there are too many to display. This may cause performance issue when rendering - ApexCharts'
+ )
+ }
+ }
+
+ for (let i = 0, bc = 0; i < series.length; i++, bc++) {
+ let x,
+ y,
+ xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
+ yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
+ zeroH, // zeroH is the baseline where 0 meets y axis
+ zeroW // zeroW is the baseline where 0 meets x axis
+
+ let yArrj = [] // hold y values of current iterating series
+ let xArrj = [] // hold x values of current iterating series
+
+ let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
+
+ let { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
+
+ // el to which series will be drawn
+ let elSeries = graphics.group({
+ class: `apexcharts-series`,
+ rel: i + 1,
+ seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
+ 'data:realIndex': realIndex,
+ })
+
+ this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex)
+
+ if (series[i].length > 0) {
+ this.visibleI = this.visibleI + 1
+ }
+
+ let barHeight = 0
+ let barWidth = 0
+
+ if (this.yRatio.length > 1) {
+ this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex]
+ this.translationsIndex = realIndex
+ }
+ let translationsIndex = this.translationsIndex
+
+ this.isReversed =
+ w.config.yaxis[this.yaxisIndex] &&
+ w.config.yaxis[this.yaxisIndex].reversed
+
+ let initPositions = this.barHelpers.initialPositions()
+
+ y = initPositions.y
+ barHeight = initPositions.barHeight
+ yDivision = initPositions.yDivision
+ zeroW = initPositions.zeroW
+
+ x = initPositions.x
+ barWidth = initPositions.barWidth
+ xDivision = initPositions.xDivision
+ zeroH = initPositions.zeroH
+
+ if (!this.horizontal) {
+ xArrj.push(x + barWidth / 2)
+ }
+
+ // eldatalabels
+ let elDataLabelsWrap = graphics.group({
+ class: 'apexcharts-datalabels',
+ 'data:realIndex': realIndex,
+ })
+
+ w.globals.delayedElements.push({
+ el: elDataLabelsWrap.node,
+ })
+ elDataLabelsWrap.node.classList.add('apexcharts-element-hidden')
+
+ let elGoalsMarkers = graphics.group({
+ class: 'apexcharts-bar-goals-markers',
+ })
+
+ let elBarShadows = graphics.group({
+ class: 'apexcharts-bar-shadows',
+ })
+
+ w.globals.delayedElements.push({
+ el: elBarShadows.node,
+ })
+ elBarShadows.node.classList.add('apexcharts-element-hidden')
+
+ for (let j = 0; j < series[i].length; j++) {
+ const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
+
+ let paths = null
+ const pathsParams = {
+ indexes: {
+ i,
+ j,
+ realIndex,
+ translationsIndex,
+ bc,
+ },
+ x,
+ y,
+ strokeWidth,
+ elSeries,
+ }
+ if (this.isHorizontal) {
+ paths = this.drawBarPaths({
+ ...pathsParams,
+ barHeight,
+ zeroW,
+ yDivision,
+ })
+ barWidth = this.series[i][j] / this.invertedYRatio
+ } else {
+ paths = this.drawColumnPaths({
+ ...pathsParams,
+ xDivision,
+ barWidth,
+ zeroH,
+ })
+ barHeight = this.series[i][j] / this.yRatio[translationsIndex]
+ }
+
+ let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex)
+
+ if (
+ this.isFunnel &&
+ this.barOptions.isFunnel3d &&
+ this.pathArr.length &&
+ j > 0
+ ) {
+ const barShadow = this.barHelpers.drawBarShadow({
+ color:
+ typeof pathFill === 'string' && pathFill?.indexOf('url') === -1
+ ? pathFill
+ : Utils.hexToRgba(w.globals.colors[i]),
+ prevPaths: this.pathArr[this.pathArr.length - 1],
+ currPaths: paths,
+ })
+
+ if (barShadow) {
+ elBarShadows.add(barShadow)
+ }
+ }
+ this.pathArr.push(paths)
+
+ const barGoalLine = this.barHelpers.drawGoalLine({
+ barXPosition: paths.barXPosition,
+ barYPosition: paths.barYPosition,
+ goalX: paths.goalX,
+ goalY: paths.goalY,
+ barHeight,
+ barWidth,
+ })
+
+ if (barGoalLine) {
+ elGoalsMarkers.add(barGoalLine)
+ }
+
+ y = paths.y
+ x = paths.x
+
+ // push current X
+ if (j > 0) {
+ xArrj.push(x + barWidth / 2)
+ }
+
+ yArrj.push(y)
+
+ this.renderSeries({
+ realIndex,
+ pathFill,
+ j,
+ i,
+ columnGroupIndex,
+ pathFrom: paths.pathFrom,
+ pathTo: paths.pathTo,
+ strokeWidth,
+ elSeries,
+ x,
+ y,
+ series,
+ barHeight: Math.abs(paths.barHeight ? paths.barHeight : barHeight),
+ barWidth: Math.abs(paths.barWidth ? paths.barWidth : barWidth),
+ elDataLabelsWrap,
+ elGoalsMarkers,
+ elBarShadows,
+ visibleSeries: this.visibleI,
+ type: 'bar',
+ })
+ }
+
+ // push all x val arrays into main xArr
+ w.globals.seriesXvalues[realIndex] = xArrj
+ w.globals.seriesYvalues[realIndex] = yArrj
+
+ ret.add(elSeries)
+ }
+
+ return ret
+ }
+
+ renderSeries({
+ realIndex,
+ pathFill,
+ lineFill,
+ j,
+ i,
+ columnGroupIndex,
+ pathFrom,
+ pathTo,
+ strokeWidth,
+ elSeries,
+ x, // x pos
+ y, // y pos
+ y1, // absolute value
+ y2, // absolute value
+ series,
+ barHeight,
+ barWidth,
+ barXPosition,
+ barYPosition,
+ elDataLabelsWrap,
+ elGoalsMarkers,
+ elBarShadows,
+ visibleSeries,
+ type,
+ classes,
+ }) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+
+ if (!lineFill) {
+ // if user provided a function in colors, we need to eval here
+ // Note: the position of this function logic (ex. stroke: { colors: ["",function(){}] }) i.e array index 1 depicts the realIndex/seriesIndex.
+ function fetchColor(i) {
+ const exp = w.config.stroke.colors
+ let c
+ if (Array.isArray(exp) && exp.length > 0) {
+ c = exp[i]
+ if (!c) c = ''
+ if (typeof c === 'function') {
+ return c({
+ value: w.globals.series[i][j],
+ dataPointIndex: j,
+ w,
+ })
+ }
+ }
+ return c
+ }
+
+ const checkAvailableColor =
+ typeof w.globals.stroke.colors[realIndex] === 'function'
+ ? fetchColor(realIndex)
+ : w.globals.stroke.colors[realIndex]
+
+ /* fix apexcharts#341 */
+ lineFill = this.barOptions.distributed
+ ? w.globals.stroke.colors[j]
+ : checkAvailableColor
+ }
+
+ if (w.config.series[i].data[j] && w.config.series[i].data[j].strokeColor) {
+ lineFill = w.config.series[i].data[j].strokeColor
+ }
+
+ if (this.isNullValue) {
+ pathFill = 'none'
+ }
+
+ let delay =
+ ((j / w.config.chart.animations.animateGradually.delay) *
+ (w.config.chart.animations.speed / w.globals.dataPoints)) /
+ 2.4
+
+ let renderedPath = graphics.renderPaths({
+ i,
+ j,
+ realIndex,
+ pathFrom,
+ pathTo,
+ stroke: lineFill,
+ strokeWidth,
+ strokeLineCap: w.config.stroke.lineCap,
+ fill: pathFill,
+ animationDelay: delay,
+ initialSpeed: w.config.chart.animations.speed,
+ dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed,
+ className: `apexcharts-${type}-area ${classes}`,
+ chartType: type,
+ })
+
+ renderedPath.attr('clip-path', `url(#gridRectBarMask${w.globals.cuid})`)
+
+ const forecast = w.config.forecastDataPoints
+ if (forecast.count > 0) {
+ if (j >= w.globals.dataPoints - forecast.count) {
+ renderedPath.node.setAttribute('stroke-dasharray', forecast.dashArray)
+ renderedPath.node.setAttribute('stroke-width', forecast.strokeWidth)
+ renderedPath.node.setAttribute('fill-opacity', forecast.fillOpacity)
+ }
+ }
+
+ if (typeof y1 !== 'undefined' && typeof y2 !== 'undefined') {
+ renderedPath.attr('data-range-y1', y1)
+ renderedPath.attr('data-range-y2', y2)
+ }
+
+ const filters = new Filters(this.ctx)
+ filters.setSelectionFilter(renderedPath, realIndex, j)
+ elSeries.add(renderedPath)
+
+ let barDataLabels = new BarDataLabels(this)
+ let dataLabelsObj = barDataLabels.handleBarDataLabels({
+ x,
+ y,
+ y1,
+ y2,
+ i,
+ j,
+ series,
+ realIndex,
+ columnGroupIndex,
+ barHeight,
+ barWidth,
+ barXPosition,
+ barYPosition,
+ renderedPath,
+ visibleSeries,
+ })
+ if (dataLabelsObj.dataLabels !== null) {
+ elDataLabelsWrap.add(dataLabelsObj.dataLabels)
+ }
+
+ if (dataLabelsObj.totalDataLabels) {
+ elDataLabelsWrap.add(dataLabelsObj.totalDataLabels)
+ }
+
+ elSeries.add(elDataLabelsWrap)
+
+ if (elGoalsMarkers) {
+ elSeries.add(elGoalsMarkers)
+ }
+
+ if (elBarShadows) {
+ elSeries.add(elBarShadows)
+ }
+ return elSeries
+ }
+
+ drawBarPaths({
+ indexes,
+ barHeight,
+ strokeWidth,
+ zeroW,
+ x,
+ y,
+ yDivision,
+ elSeries,
+ }) {
+ let w = this.w
+
+ let i = indexes.i
+ let j = indexes.j
+ let barYPosition
+
+ if (w.globals.isXNumeric) {
+ y =
+ (w.globals.seriesX[i][j] - w.globals.minX) / this.invertedXRatio -
+ barHeight
+ barYPosition = y + barHeight * this.visibleI
+ } else {
+ if (w.config.plotOptions.bar.hideZeroBarsWhenGrouped) {
+ let nonZeroColumns = 0
+ let zeroEncounters = 0
+ w.globals.seriesPercent.forEach((_s, _si) => {
+ if (_s[j]) {
+ nonZeroColumns++
+ }
+
+ if (_si < i && _s[j] === 0) {
+ zeroEncounters++
+ }
+ })
+
+ if (nonZeroColumns > 0) {
+ barHeight = (this.seriesLen * barHeight) / nonZeroColumns
+ }
+ barYPosition = y + barHeight * this.visibleI
+ barYPosition -= barHeight * zeroEncounters
+ } else {
+ barYPosition = y + barHeight * this.visibleI
+ }
+ }
+
+ if (this.isFunnel) {
+ zeroW =
+ zeroW -
+ (this.barHelpers.getXForValue(this.series[i][j], zeroW) - zeroW) / 2
+ }
+
+ x = this.barHelpers.getXForValue(this.series[i][j], zeroW)
+
+ const paths = this.barHelpers.getBarpaths({
+ barYPosition,
+ barHeight,
+ x1: zeroW,
+ x2: x,
+ strokeWidth,
+ isReversed: this.isReversed,
+ series: this.series,
+ realIndex: indexes.realIndex,
+ i,
+ j,
+ w,
+ })
+
+ if (!w.globals.isXNumeric) {
+ y = y + yDivision
+ }
+
+ this.barHelpers.barBackground({
+ j,
+ i,
+ y1: barYPosition - barHeight * this.visibleI,
+ y2: barHeight * this.seriesLen,
+ elSeries,
+ })
+
+ return {
+ pathTo: paths.pathTo,
+ pathFrom: paths.pathFrom,
+ x1: zeroW,
+ x,
+ y,
+ goalX: this.barHelpers.getGoalValues('x', zeroW, null, i, j),
+ barYPosition,
+ barHeight,
+ }
+ }
+
+ drawColumnPaths({
+ indexes,
+ x,
+ y,
+ xDivision,
+ barWidth,
+ zeroH,
+ strokeWidth,
+ elSeries,
+ }) {
+ let w = this.w
+
+ let realIndex = indexes.realIndex
+ let translationsIndex = indexes.translationsIndex
+ let i = indexes.i
+ let j = indexes.j
+ let bc = indexes.bc
+ let barXPosition
+
+ if (w.globals.isXNumeric) {
+ const xForNumericX = this.getBarXForNumericXAxis({
+ x,
+ j,
+ realIndex,
+ barWidth,
+ })
+ x = xForNumericX.x
+ barXPosition = xForNumericX.barXPosition
+ } else {
+ if (w.config.plotOptions.bar.hideZeroBarsWhenGrouped) {
+ const { nonZeroColumns, zeroEncounters } =
+ this.barHelpers.getZeroValueEncounters({ i, j })
+
+ if (nonZeroColumns > 0) {
+ barWidth = (this.seriesLen * barWidth) / nonZeroColumns
+ }
+ barXPosition = x + barWidth * this.visibleI
+ barXPosition -= barWidth * zeroEncounters
+ } else {
+ barXPosition = x + barWidth * this.visibleI
+ }
+ }
+
+ y = this.barHelpers.getYForValue(
+ this.series[i][j],
+ zeroH,
+ translationsIndex
+ )
+
+ const paths = this.barHelpers.getColumnPaths({
+ barXPosition,
+ barWidth,
+ y1: zeroH,
+ y2: y,
+ strokeWidth,
+ isReversed: this.isReversed,
+ series: this.series,
+ realIndex: realIndex,
+ i,
+ j,
+ w,
+ })
+
+ if (!w.globals.isXNumeric) {
+ x = x + xDivision
+ }
+
+ this.barHelpers.barBackground({
+ bc,
+ j,
+ i,
+ x1: barXPosition - strokeWidth / 2 - barWidth * this.visibleI,
+ x2: barWidth * this.seriesLen + strokeWidth / 2,
+ elSeries,
+ })
+
+ return {
+ pathTo: paths.pathTo,
+ pathFrom: paths.pathFrom,
+ x,
+ y,
+ goalY: this.barHelpers.getGoalValues(
+ 'y',
+ null,
+ zeroH,
+ i,
+ j,
+ translationsIndex
+ ),
+ barXPosition,
+ barWidth,
+ }
+ }
+
+ getBarXForNumericXAxis({ x, barWidth, realIndex, j }) {
+ const w = this.w
+ let sxI = realIndex
+ if (!w.globals.seriesX[realIndex].length) {
+ sxI = w.globals.maxValsInArrayIndex
+ }
+ if (w.globals.seriesX[sxI][j]) {
+ x =
+ (w.globals.seriesX[sxI][j] - w.globals.minX) / this.xRatio -
+ (barWidth * this.seriesLen) / 2
+ }
+
+ return {
+ barXPosition: x + barWidth * this.visibleI,
+ x,
+ }
+ }
+
+ /** getPreviousPath is a common function for bars/columns which is used to get previous paths when data changes.
+ * @memberof Bar
+ * @param {int} realIndex - current iterating i
+ * @param {int} j - current iterating series's j index
+ * @return {string} pathFrom is the string which will be appended in animations
+ **/
+ getPreviousPath(realIndex, j) {
+ let w = this.w
+ let pathFrom
+ for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
+ let gpp = w.globals.previousPaths[pp]
+
+ if (
+ gpp.paths &&
+ gpp.paths.length > 0 &&
+ parseInt(gpp.realIndex, 10) === parseInt(realIndex, 10)
+ ) {
+ if (typeof w.globals.previousPaths[pp].paths[j] !== 'undefined') {
+ pathFrom = w.globals.previousPaths[pp].paths[j].d
+ }
+ }
+ }
+ return pathFrom
+ }
+}
+
+export default Bar
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/BarStacked.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/BarStacked.js
new file mode 100644
index 0000000..1255156
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/BarStacked.js
@@ -0,0 +1,570 @@
+import CoreUtils from '../modules/CoreUtils'
+import Bar from './Bar'
+import Graphics from '../modules/Graphics'
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts BarStacked Class responsible for drawing both Stacked Columns and Bars.
+ *
+ * @module BarStacked
+ * The whole calculation for stacked bar/column is different from normal bar/column,
+ * hence it makes sense to derive a new class for it extending most of the props of Parent Bar
+ **/
+
+class BarStacked extends Bar {
+ draw(series, seriesIndex) {
+ let w = this.w
+ this.graphics = new Graphics(this.ctx)
+ this.bar = new Bar(this.ctx, this.xyRatios)
+
+ const coreUtils = new CoreUtils(this.ctx, w)
+ series = coreUtils.getLogSeries(series)
+ this.yRatio = coreUtils.getLogYRatios(this.yRatio)
+
+ this.barHelpers.initVariables(series)
+
+ if (w.config.chart.stackType === '100%') {
+ series = w.globals.comboCharts
+ ? seriesIndex.map((_) => w.globals.seriesPercent[_])
+ : w.globals.seriesPercent.slice()
+ }
+
+ this.series = series
+ this.barHelpers.initializeStackedPrevVars(this)
+
+ let ret = this.graphics.group({
+ class: 'apexcharts-bar-series apexcharts-plot-series',
+ })
+
+ let x = 0
+ let y = 0
+
+ for (let i = 0, bc = 0; i < series.length; i++, bc++) {
+ let xDivision // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
+ let yDivision // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
+ let zeroH // zeroH is the baseline where 0 meets y axis
+ let zeroW // zeroW is the baseline where 0 meets x axis
+
+ let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
+ let { groupIndex, columnGroupIndex } =
+ this.barHelpers.getGroupIndex(realIndex)
+ this.groupCtx = this[w.globals.seriesGroups[groupIndex]]
+
+ let xArrValues = []
+ let yArrValues = []
+
+ let translationsIndex = 0
+ if (this.yRatio.length > 1) {
+ this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0]
+ translationsIndex = realIndex
+ }
+
+ this.isReversed =
+ w.config.yaxis[this.yaxisIndex] &&
+ w.config.yaxis[this.yaxisIndex].reversed
+
+ // el to which series will be drawn
+ let elSeries = this.graphics.group({
+ class: `apexcharts-series`,
+ seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
+ rel: i + 1,
+ 'data:realIndex': realIndex,
+ })
+ this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex)
+
+ // eldatalabels
+ let elDataLabelsWrap = this.graphics.group({
+ class: 'apexcharts-datalabels',
+ 'data:realIndex': realIndex,
+ })
+
+ let elGoalsMarkers = this.graphics.group({
+ class: 'apexcharts-bar-goals-markers',
+ })
+
+ let barHeight = 0
+ let barWidth = 0
+
+ let initPositions = this.initialPositions(
+ x,
+ y,
+ xDivision,
+ yDivision,
+ zeroH,
+ zeroW,
+ translationsIndex
+ )
+ y = initPositions.y
+ barHeight = initPositions.barHeight
+ yDivision = initPositions.yDivision
+ zeroW = initPositions.zeroW
+
+ x = initPositions.x
+ barWidth = initPositions.barWidth
+ xDivision = initPositions.xDivision
+ zeroH = initPositions.zeroH
+
+ w.globals.barHeight = barHeight
+ w.globals.barWidth = barWidth
+
+ this.barHelpers.initializeStackedXYVars(this)
+
+ // where all stack bar disappear after collapsing the first series
+ if (
+ this.groupCtx.prevY.length === 1 &&
+ this.groupCtx.prevY[0].every((val) => isNaN(val))
+ ) {
+ this.groupCtx.prevY[0] = this.groupCtx.prevY[0].map(() => zeroH)
+ this.groupCtx.prevYF[0] = this.groupCtx.prevYF[0].map(() => 0)
+ }
+
+ for (let j = 0; j < w.globals.dataPoints; j++) {
+ const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
+ const commonPathOpts = {
+ indexes: { i, j, realIndex, translationsIndex, bc },
+ strokeWidth,
+ x,
+ y,
+ elSeries,
+ columnGroupIndex,
+ seriesGroup: w.globals.seriesGroups[groupIndex],
+ }
+ let paths = null
+ if (this.isHorizontal) {
+ paths = this.drawStackedBarPaths({
+ ...commonPathOpts,
+ zeroW,
+ barHeight,
+ yDivision,
+ })
+ barWidth = this.series[i][j] / this.invertedYRatio
+ } else {
+ paths = this.drawStackedColumnPaths({
+ ...commonPathOpts,
+ xDivision,
+ barWidth,
+ zeroH,
+ })
+ barHeight = this.series[i][j] / this.yRatio[translationsIndex]
+ }
+
+ const barGoalLine = this.barHelpers.drawGoalLine({
+ barXPosition: paths.barXPosition,
+ barYPosition: paths.barYPosition,
+ goalX: paths.goalX,
+ goalY: paths.goalY,
+ barHeight,
+ barWidth,
+ })
+
+ if (barGoalLine) {
+ elGoalsMarkers.add(barGoalLine)
+ }
+
+ y = paths.y
+ x = paths.x
+
+ xArrValues.push(x)
+ yArrValues.push(y)
+
+ let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex)
+
+ let classes = ''
+
+ if (w.globals.isBarHorizontal) {
+ if (
+ this.barHelpers.arrBorderRadius[realIndex][j] === 'bottom' &&
+ w.globals.series[realIndex][j] > 0
+ ) {
+ classes = 'apexcharts-flip-x'
+ }
+ } else {
+ if (
+ this.barHelpers.arrBorderRadius[realIndex][j] === 'bottom' &&
+ w.globals.series[realIndex][j] > 0
+ ) {
+ classes = 'apexcharts-flip-y'
+ }
+ }
+ elSeries = this.renderSeries({
+ realIndex,
+ pathFill,
+ j,
+ i,
+ columnGroupIndex,
+ pathFrom: paths.pathFrom,
+ pathTo: paths.pathTo,
+ strokeWidth,
+ elSeries,
+ x,
+ y,
+ series,
+ barHeight,
+ barWidth,
+ elDataLabelsWrap,
+ elGoalsMarkers,
+ type: 'bar',
+ visibleSeries: columnGroupIndex,
+ classes,
+ })
+ }
+
+ // push all x val arrays into main xArr
+ w.globals.seriesXvalues[realIndex] = xArrValues
+ w.globals.seriesYvalues[realIndex] = yArrValues
+
+ // push all current y values array to main PrevY Array
+ this.groupCtx.prevY.push(this.groupCtx.yArrj)
+ this.groupCtx.prevYF.push(this.groupCtx.yArrjF)
+ this.groupCtx.prevYVal.push(this.groupCtx.yArrjVal)
+ this.groupCtx.prevX.push(this.groupCtx.xArrj)
+ this.groupCtx.prevXF.push(this.groupCtx.xArrjF)
+ this.groupCtx.prevXVal.push(this.groupCtx.xArrjVal)
+
+ ret.add(elSeries)
+ }
+
+ return ret
+ }
+
+ initialPositions(
+ x,
+ y,
+ xDivision,
+ yDivision,
+ zeroH,
+ zeroW,
+ translationsIndex
+ ) {
+ let w = this.w
+
+ let barHeight, barWidth
+ if (this.isHorizontal) {
+ // height divided into equal parts
+ yDivision = w.globals.gridHeight / w.globals.dataPoints
+
+ let userBarHeight = w.config.plotOptions.bar.barHeight
+ if (String(userBarHeight).indexOf('%') === -1) {
+ barHeight = parseInt(userBarHeight, 10)
+ } else {
+ barHeight = (yDivision * parseInt(userBarHeight, 10)) / 100
+ }
+ zeroW =
+ w.globals.padHorizontal +
+ (this.isReversed
+ ? w.globals.gridWidth - this.baseLineInvertedY
+ : this.baseLineInvertedY)
+
+ // initial y position is half of barHeight * half of number of Bars
+ y = (yDivision - barHeight) / 2
+ } else {
+ // width divided into equal parts
+ xDivision = w.globals.gridWidth / w.globals.dataPoints
+
+ barWidth = xDivision
+
+ let userColumnWidth = w.config.plotOptions.bar.columnWidth
+ if (w.globals.isXNumeric && w.globals.dataPoints > 1) {
+ xDivision = w.globals.minXDiff / this.xRatio
+ barWidth = (xDivision * parseInt(this.barOptions.columnWidth, 10)) / 100
+ } else if (String(userColumnWidth).indexOf('%') === -1) {
+ barWidth = parseInt(userColumnWidth, 10)
+ } else {
+ barWidth *= parseInt(userColumnWidth, 10) / 100
+ }
+
+ if (this.isReversed) {
+ zeroH = this.baseLineY[translationsIndex]
+ } else {
+ zeroH = w.globals.gridHeight - this.baseLineY[translationsIndex]
+ }
+
+ // initial x position is the left-most edge of the first bar relative to
+ // the left-most side of the grid area.
+ x = w.globals.padHorizontal + (xDivision - barWidth) / 2
+ }
+
+ // Up to this point, barWidth is the width that will accommodate all bars
+ // at each datapoint or category.
+
+ // The crude subdivision here assumes the series within each group are
+ // stacked. If there is no stacking then the barWidth/barHeight is
+ // further divided later by the number of series in the group. So, eg, two
+ // groups of three series would become six bars side-by-side unstacked,
+ // or two bars stacked.
+ let subDivisions = w.globals.barGroups.length || 1
+
+ return {
+ x,
+ y,
+ yDivision,
+ xDivision,
+ barHeight: barHeight / subDivisions,
+ barWidth: barWidth / subDivisions,
+ zeroH,
+ zeroW,
+ }
+ }
+
+ drawStackedBarPaths({
+ indexes,
+ barHeight,
+ strokeWidth,
+ zeroW,
+ x,
+ y,
+ columnGroupIndex,
+ seriesGroup,
+ yDivision,
+ elSeries,
+ }) {
+ let w = this.w
+ let barYPosition = y + columnGroupIndex * barHeight
+ let barXPosition
+ let i = indexes.i
+ let j = indexes.j
+ let realIndex = indexes.realIndex
+ let translationsIndex = indexes.translationsIndex
+
+ let prevBarW = 0
+ for (let k = 0; k < this.groupCtx.prevXF.length; k++) {
+ prevBarW = prevBarW + this.groupCtx.prevXF[k][j]
+ }
+
+ let gsi = i // an index to keep track of the series inside a group
+ gsi = seriesGroup.indexOf(w.config.series[realIndex].name)
+
+ if (gsi > 0) {
+ let bXP = zeroW
+
+ if (this.groupCtx.prevXVal[gsi - 1][j] < 0) {
+ bXP =
+ this.series[i][j] >= 0
+ ? this.groupCtx.prevX[gsi - 1][j] +
+ prevBarW -
+ (this.isReversed ? prevBarW : 0) * 2
+ : this.groupCtx.prevX[gsi - 1][j]
+ } else if (this.groupCtx.prevXVal[gsi - 1][j] >= 0) {
+ bXP =
+ this.series[i][j] >= 0
+ ? this.groupCtx.prevX[gsi - 1][j]
+ : this.groupCtx.prevX[gsi - 1][j] -
+ prevBarW +
+ (this.isReversed ? prevBarW : 0) * 2
+ }
+
+ barXPosition = bXP
+ } else {
+ // the first series will not have prevX values
+ barXPosition = zeroW
+ }
+
+ if (this.series[i][j] === null) {
+ x = barXPosition
+ } else {
+ x =
+ barXPosition +
+ this.series[i][j] / this.invertedYRatio -
+ (this.isReversed ? this.series[i][j] / this.invertedYRatio : 0) * 2
+ }
+
+ const paths = this.barHelpers.getBarpaths({
+ barYPosition,
+ barHeight,
+ x1: barXPosition,
+ x2: x,
+ strokeWidth,
+ isReversed: this.isReversed,
+ series: this.series,
+ realIndex: indexes.realIndex,
+ seriesGroup,
+ i,
+ j,
+ w,
+ })
+
+ this.barHelpers.barBackground({
+ j,
+ i,
+ y1: barYPosition,
+ y2: barHeight,
+ elSeries,
+ })
+
+ y = y + yDivision
+
+ return {
+ pathTo: paths.pathTo,
+ pathFrom: paths.pathFrom,
+ goalX: this.barHelpers.getGoalValues(
+ 'x',
+ zeroW,
+ null,
+ i,
+ j,
+ translationsIndex
+ ),
+ barXPosition,
+ barYPosition,
+ x,
+ y,
+ }
+ }
+
+ drawStackedColumnPaths({
+ indexes,
+ x,
+ y,
+ xDivision,
+ barWidth,
+ zeroH,
+ columnGroupIndex,
+ seriesGroup,
+ elSeries,
+ }) {
+ let w = this.w
+ let i = indexes.i
+ let j = indexes.j
+ let bc = indexes.bc
+ let realIndex = indexes.realIndex
+ let translationsIndex = indexes.translationsIndex
+
+ if (w.globals.isXNumeric) {
+ let seriesVal = w.globals.seriesX[realIndex][j]
+ if (!seriesVal) seriesVal = 0
+ // TODO: move the barWidth factor to barXPosition
+ x =
+ (seriesVal - w.globals.minX) / this.xRatio -
+ (barWidth / 2) * w.globals.barGroups.length
+ }
+
+ let barXPosition = x + columnGroupIndex * barWidth
+ let barYPosition
+
+ let prevBarH = 0
+ for (let k = 0; k < this.groupCtx.prevYF.length; k++) {
+ // fix issue #1215
+ // in case where this.groupCtx.prevYF[k][j] is NaN, use 0 instead
+ prevBarH =
+ prevBarH +
+ (!isNaN(this.groupCtx.prevYF[k][j]) ? this.groupCtx.prevYF[k][j] : 0)
+ }
+
+ let gsi = i // an index to keep track of the series inside a group
+ if (seriesGroup) {
+ gsi = seriesGroup.indexOf(w.globals.seriesNames[realIndex])
+ }
+ if (
+ (gsi > 0 && !w.globals.isXNumeric) ||
+ (gsi > 0 &&
+ w.globals.isXNumeric &&
+ w.globals.seriesX[realIndex - 1][j] === w.globals.seriesX[realIndex][j])
+ ) {
+ let bYP
+ let prevYValue
+ const p = Math.min(this.yRatio.length + 1, realIndex + 1)
+ if (
+ this.groupCtx.prevY[gsi - 1] !== undefined &&
+ this.groupCtx.prevY[gsi - 1].length
+ ) {
+ for (let ii = 1; ii < p; ii++) {
+ if (!isNaN(this.groupCtx.prevY[gsi - ii]?.[j])) {
+ // find the previous available value to give prevYValue
+ prevYValue = this.groupCtx.prevY[gsi - ii][j]
+ // if found it, break the loop
+ break
+ }
+ }
+ }
+
+ for (let ii = 1; ii < p; ii++) {
+ // find the previous available value(non-NaN) to give bYP
+ if (this.groupCtx.prevYVal[gsi - ii]?.[j] < 0) {
+ bYP =
+ this.series[i][j] >= 0
+ ? prevYValue - prevBarH + (this.isReversed ? prevBarH : 0) * 2
+ : prevYValue
+ // found it? break the loop
+ break
+ } else if (this.groupCtx.prevYVal[gsi - ii]?.[j] >= 0) {
+ bYP =
+ this.series[i][j] >= 0
+ ? prevYValue
+ : prevYValue + prevBarH - (this.isReversed ? prevBarH : 0) * 2
+ // found it? break the loop
+ break
+ }
+ }
+
+ if (typeof bYP === 'undefined') bYP = w.globals.gridHeight
+
+ // if this.prevYF[0] is all 0 resulted from line #486
+ // AND every arr starting from the second only contains NaN
+ if (
+ this.groupCtx.prevYF[0]?.every((val) => val === 0) &&
+ this.groupCtx.prevYF
+ .slice(1, gsi)
+ .every((arr) => arr.every((val) => isNaN(val)))
+ ) {
+ barYPosition = zeroH
+ } else {
+ // Nothing special
+ barYPosition = bYP
+ }
+ } else {
+ // the first series will not have prevY values, also if the prev index's
+ // series X doesn't matches the current index's series X, then start from
+ // zero
+ barYPosition = zeroH
+ }
+
+ if (this.series[i][j]) {
+ y =
+ barYPosition -
+ this.series[i][j] / this.yRatio[translationsIndex] +
+ (this.isReversed
+ ? this.series[i][j] / this.yRatio[translationsIndex]
+ : 0) *
+ 2
+ } else {
+ // fixes #3610
+ y = barYPosition
+ }
+
+ const paths = this.barHelpers.getColumnPaths({
+ barXPosition,
+ barWidth,
+ y1: barYPosition,
+ y2: y,
+ yRatio: this.yRatio[translationsIndex],
+ strokeWidth: this.strokeWidth,
+ isReversed: this.isReversed,
+ series: this.series,
+ seriesGroup,
+ realIndex: indexes.realIndex,
+ i,
+ j,
+ w,
+ })
+
+ this.barHelpers.barBackground({
+ bc,
+ j,
+ i,
+ x1: barXPosition,
+ x2: barWidth,
+ elSeries,
+ })
+
+ return {
+ pathTo: paths.pathTo,
+ pathFrom: paths.pathFrom,
+ goalY: this.barHelpers.getGoalValues('y', null, zeroH, i, j),
+ barXPosition,
+ x: w.globals.isXNumeric ? x : x + xDivision,
+ y,
+ }
+ }
+}
+
+export default BarStacked
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/BoxCandleStick.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/BoxCandleStick.js
new file mode 100644
index 0000000..424657d
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/BoxCandleStick.js
@@ -0,0 +1,435 @@
+import CoreUtils from '../modules/CoreUtils'
+import Bar from './Bar'
+import Fill from '../modules/Fill'
+import Graphics from '../modules/Graphics'
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts BoxCandleStick Class responsible for drawing both Stacked Columns and Bars.
+ *
+ * @module BoxCandleStick
+ **/
+
+class BoxCandleStick extends Bar {
+ draw(series, ctype, seriesIndex) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+ let type = w.globals.comboCharts ? ctype : w.config.chart.type
+ let fill = new Fill(this.ctx)
+
+ this.candlestickOptions = this.w.config.plotOptions.candlestick
+ this.boxOptions = this.w.config.plotOptions.boxPlot
+ this.isHorizontal = w.config.plotOptions.bar.horizontal
+
+ const coreUtils = new CoreUtils(this.ctx, w)
+ series = coreUtils.getLogSeries(series)
+ this.series = series
+ this.yRatio = coreUtils.getLogYRatios(this.yRatio)
+
+ this.barHelpers.initVariables(series)
+
+ let ret = graphics.group({
+ class: `apexcharts-${type}-series apexcharts-plot-series`,
+ })
+
+ for (let i = 0; i < series.length; i++) {
+ this.isBoxPlot =
+ w.config.chart.type === 'boxPlot' ||
+ w.config.series[i].type === 'boxPlot'
+
+ let x,
+ y,
+ xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
+ yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
+ zeroH, // zeroH is the baseline where 0 meets y axis
+ zeroW // zeroW is the baseline where 0 meets x axis
+
+ let yArrj = [] // hold y values of current iterating series
+ let xArrj = [] // hold x values of current iterating series
+
+ let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
+ // As BoxCandleStick derives from Bar, we need this to render.
+ let { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
+
+ // el to which series will be drawn
+ let elSeries = graphics.group({
+ class: `apexcharts-series`,
+ seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
+ rel: i + 1,
+ 'data:realIndex': realIndex,
+ })
+
+ this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex)
+
+ if (series[i].length > 0) {
+ this.visibleI = this.visibleI + 1
+ }
+
+ let barHeight = 0
+ let barWidth = 0
+
+ let translationsIndex = 0
+ if (this.yRatio.length > 1) {
+ this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0]
+ translationsIndex = realIndex
+ }
+
+ let initPositions = this.barHelpers.initialPositions()
+
+ y = initPositions.y
+ barHeight = initPositions.barHeight
+ yDivision = initPositions.yDivision
+ zeroW = initPositions.zeroW
+
+ x = initPositions.x
+ barWidth = initPositions.barWidth
+ xDivision = initPositions.xDivision
+ zeroH = initPositions.zeroH
+
+ xArrj.push(x + barWidth / 2)
+
+ // eldatalabels
+ let elDataLabelsWrap = graphics.group({
+ class: 'apexcharts-datalabels',
+ 'data:realIndex': realIndex,
+ })
+
+ for (let j = 0; j < w.globals.dataPoints; j++) {
+ const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
+
+ let paths = null
+ const pathsParams = {
+ indexes: {
+ i,
+ j,
+ realIndex,
+ translationsIndex,
+ },
+ x,
+ y,
+ strokeWidth,
+ elSeries,
+ }
+
+ if (this.isHorizontal) {
+ paths = this.drawHorizontalBoxPaths({
+ ...pathsParams,
+ yDivision,
+ barHeight,
+ zeroW,
+ })
+ } else {
+ paths = this.drawVerticalBoxPaths({
+ ...pathsParams,
+ xDivision,
+ barWidth,
+ zeroH,
+ })
+ }
+
+ y = paths.y
+ x = paths.x
+
+ // push current X
+ if (j > 0) {
+ xArrj.push(x + barWidth / 2)
+ }
+
+ yArrj.push(y)
+
+ paths.pathTo.forEach((pathTo, pi) => {
+ let lineFill =
+ !this.isBoxPlot && this.candlestickOptions.wick.useFillColor
+ ? paths.color[pi]
+ : w.globals.stroke.colors[i]
+
+ let pathFill = fill.fillPath({
+ seriesNumber: realIndex,
+ dataPointIndex: j,
+ color: paths.color[pi],
+ value: series[i][j],
+ })
+
+ this.renderSeries({
+ realIndex,
+ pathFill,
+ lineFill,
+ j,
+ i,
+ pathFrom: paths.pathFrom,
+ pathTo,
+ strokeWidth,
+ elSeries,
+ x,
+ y,
+ series,
+ columnGroupIndex,
+ barHeight,
+ barWidth,
+ elDataLabelsWrap,
+ visibleSeries: this.visibleI,
+ type: w.config.chart.type,
+ })
+ })
+ }
+
+ // push all x val arrays into main xArr
+ w.globals.seriesXvalues[realIndex] = xArrj
+ w.globals.seriesYvalues[realIndex] = yArrj
+
+ ret.add(elSeries)
+ }
+
+ return ret
+ }
+
+ drawVerticalBoxPaths({
+ indexes,
+ x,
+ y,
+ xDivision,
+ barWidth,
+ zeroH,
+ strokeWidth,
+ }) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+
+ let i = indexes.i
+ let j = indexes.j
+
+ let isPositive = true
+ let colorPos = w.config.plotOptions.candlestick.colors.upward
+ let colorNeg = w.config.plotOptions.candlestick.colors.downward
+ let color = ''
+
+ if (this.isBoxPlot) {
+ color = [this.boxOptions.colors.lower, this.boxOptions.colors.upper]
+ }
+
+ const yRatio = this.yRatio[indexes.translationsIndex]
+ let realIndex = indexes.realIndex
+
+ const ohlc = this.getOHLCValue(realIndex, j)
+ let l1 = zeroH
+ let l2 = zeroH
+
+ if (ohlc.o > ohlc.c) {
+ isPositive = false
+ }
+
+ let y1 = Math.min(ohlc.o, ohlc.c)
+ let y2 = Math.max(ohlc.o, ohlc.c)
+ let m = ohlc.m
+
+ if (w.globals.isXNumeric) {
+ x =
+ (w.globals.seriesX[realIndex][j] - w.globals.minX) / this.xRatio -
+ barWidth / 2
+ }
+
+ let barXPosition = x + barWidth * this.visibleI
+
+ if (
+ typeof this.series[i][j] === 'undefined' ||
+ this.series[i][j] === null
+ ) {
+ y1 = zeroH
+ y2 = zeroH
+ } else {
+ y1 = zeroH - y1 / yRatio
+ y2 = zeroH - y2 / yRatio
+ l1 = zeroH - ohlc.h / yRatio
+ l2 = zeroH - ohlc.l / yRatio
+ m = zeroH - ohlc.m / yRatio
+ }
+
+ let pathTo = graphics.move(barXPosition, zeroH)
+ let pathFrom = graphics.move(barXPosition + barWidth / 2, y1)
+ if (w.globals.previousPaths.length > 0) {
+ pathFrom = this.getPreviousPath(realIndex, j, true)
+ }
+
+ if (this.isBoxPlot) {
+ pathTo = [
+ graphics.move(barXPosition, y1) +
+ graphics.line(barXPosition + barWidth / 2, y1) +
+ graphics.line(barXPosition + barWidth / 2, l1) +
+ graphics.line(barXPosition + barWidth / 4, l1) +
+ graphics.line(barXPosition + barWidth - barWidth / 4, l1) +
+ graphics.line(barXPosition + barWidth / 2, l1) +
+ graphics.line(barXPosition + barWidth / 2, y1) +
+ graphics.line(barXPosition + barWidth, y1) +
+ graphics.line(barXPosition + barWidth, m) +
+ graphics.line(barXPosition, m) +
+ graphics.line(barXPosition, y1 + strokeWidth / 2),
+ graphics.move(barXPosition, m) +
+ graphics.line(barXPosition + barWidth, m) +
+ graphics.line(barXPosition + barWidth, y2) +
+ graphics.line(barXPosition + barWidth / 2, y2) +
+ graphics.line(barXPosition + barWidth / 2, l2) +
+ graphics.line(barXPosition + barWidth - barWidth / 4, l2) +
+ graphics.line(barXPosition + barWidth / 4, l2) +
+ graphics.line(barXPosition + barWidth / 2, l2) +
+ graphics.line(barXPosition + barWidth / 2, y2) +
+ graphics.line(barXPosition, y2) +
+ graphics.line(barXPosition, m) +
+ 'z',
+ ]
+ } else {
+ // candlestick
+ pathTo = [
+ graphics.move(barXPosition, y2) +
+ graphics.line(barXPosition + barWidth / 2, y2) +
+ graphics.line(barXPosition + barWidth / 2, l1) +
+ graphics.line(barXPosition + barWidth / 2, y2) +
+ graphics.line(barXPosition + barWidth, y2) +
+ graphics.line(barXPosition + barWidth, y1) +
+ graphics.line(barXPosition + barWidth / 2, y1) +
+ graphics.line(barXPosition + barWidth / 2, l2) +
+ graphics.line(barXPosition + barWidth / 2, y1) +
+ graphics.line(barXPosition, y1) +
+ graphics.line(barXPosition, y2 - strokeWidth / 2),
+ ]
+ }
+
+ pathFrom = pathFrom + graphics.move(barXPosition, y1)
+
+ if (!w.globals.isXNumeric) {
+ x = x + xDivision
+ }
+
+ return {
+ pathTo,
+ pathFrom,
+ x,
+ y: y2,
+ barXPosition,
+ color: this.isBoxPlot ? color : isPositive ? [colorPos] : [colorNeg],
+ }
+ }
+
+ drawHorizontalBoxPaths({
+ indexes,
+ x,
+ y,
+ yDivision,
+ barHeight,
+ zeroW,
+ strokeWidth,
+ }) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+
+ let i = indexes.i
+ let j = indexes.j
+
+ let color = this.boxOptions.colors.lower
+
+ if (this.isBoxPlot) {
+ color = [this.boxOptions.colors.lower, this.boxOptions.colors.upper]
+ }
+
+ const yRatio = this.invertedYRatio
+ let realIndex = indexes.realIndex
+
+ const ohlc = this.getOHLCValue(realIndex, j)
+ let l1 = zeroW
+ let l2 = zeroW
+
+ let x1 = Math.min(ohlc.o, ohlc.c)
+ let x2 = Math.max(ohlc.o, ohlc.c)
+ let m = ohlc.m
+
+ if (w.globals.isXNumeric) {
+ y =
+ (w.globals.seriesX[realIndex][j] - w.globals.minX) /
+ this.invertedXRatio -
+ barHeight / 2
+ }
+
+ let barYPosition = y + barHeight * this.visibleI
+
+ if (
+ typeof this.series[i][j] === 'undefined' ||
+ this.series[i][j] === null
+ ) {
+ x1 = zeroW
+ x2 = zeroW
+ } else {
+ x1 = zeroW + x1 / yRatio
+ x2 = zeroW + x2 / yRatio
+ l1 = zeroW + ohlc.h / yRatio
+ l2 = zeroW + ohlc.l / yRatio
+ m = zeroW + ohlc.m / yRatio
+ }
+
+ let pathTo = graphics.move(zeroW, barYPosition)
+ let pathFrom = graphics.move(x1, barYPosition + barHeight / 2)
+ if (w.globals.previousPaths.length > 0) {
+ pathFrom = this.getPreviousPath(realIndex, j, true)
+ }
+
+ pathTo = [
+ graphics.move(x1, barYPosition) +
+ graphics.line(x1, barYPosition + barHeight / 2) +
+ graphics.line(l1, barYPosition + barHeight / 2) +
+ graphics.line(l1, barYPosition + barHeight / 2 - barHeight / 4) +
+ graphics.line(l1, barYPosition + barHeight / 2 + barHeight / 4) +
+ graphics.line(l1, barYPosition + barHeight / 2) +
+ graphics.line(x1, barYPosition + barHeight / 2) +
+ graphics.line(x1, barYPosition + barHeight) +
+ graphics.line(m, barYPosition + barHeight) +
+ graphics.line(m, barYPosition) +
+ graphics.line(x1 + strokeWidth / 2, barYPosition),
+ graphics.move(m, barYPosition) +
+ graphics.line(m, barYPosition + barHeight) +
+ graphics.line(x2, barYPosition + barHeight) +
+ graphics.line(x2, barYPosition + barHeight / 2) +
+ graphics.line(l2, barYPosition + barHeight / 2) +
+ graphics.line(l2, barYPosition + barHeight - barHeight / 4) +
+ graphics.line(l2, barYPosition + barHeight / 4) +
+ graphics.line(l2, barYPosition + barHeight / 2) +
+ graphics.line(x2, barYPosition + barHeight / 2) +
+ graphics.line(x2, barYPosition) +
+ graphics.line(m, barYPosition) +
+ 'z',
+ ]
+
+ pathFrom = pathFrom + graphics.move(x1, barYPosition)
+
+ if (!w.globals.isXNumeric) {
+ y = y + yDivision
+ }
+
+ return {
+ pathTo,
+ pathFrom,
+ x: x2,
+ y,
+ barYPosition,
+ color,
+ }
+ }
+ getOHLCValue(i, j) {
+ const w = this.w
+
+ return {
+ o: this.isBoxPlot
+ ? w.globals.seriesCandleH[i][j]
+ : w.globals.seriesCandleO[i][j],
+ h: this.isBoxPlot
+ ? w.globals.seriesCandleO[i][j]
+ : w.globals.seriesCandleH[i][j],
+ m: w.globals.seriesCandleM[i][j],
+ l: this.isBoxPlot
+ ? w.globals.seriesCandleC[i][j]
+ : w.globals.seriesCandleL[i][j],
+ c: this.isBoxPlot
+ ? w.globals.seriesCandleL[i][j]
+ : w.globals.seriesCandleC[i][j],
+ }
+ }
+}
+
+export default BoxCandleStick
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/HeatMap.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/HeatMap.js
new file mode 100644
index 0000000..04056aa
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/HeatMap.js
@@ -0,0 +1,240 @@
+import Animations from '../modules/Animations'
+import Graphics from '../modules/Graphics'
+import Fill from '../modules/Fill'
+import Utils from '../utils/Utils'
+import Helpers from './common/treemap/Helpers'
+import Filters from '../modules/Filters'
+
+/**
+ * ApexCharts HeatMap Class.
+ * @module HeatMap
+ **/
+
+export default class HeatMap {
+ constructor(ctx, xyRatios) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.xRatio = xyRatios.xRatio
+ this.yRatio = xyRatios.yRatio
+
+ this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation
+
+ this.helpers = new Helpers(ctx)
+ this.rectRadius = this.w.config.plotOptions.heatmap.radius
+ this.strokeWidth = this.w.config.stroke.show
+ ? this.w.config.stroke.width
+ : 0
+ }
+
+ draw(series) {
+ let w = this.w
+ const graphics = new Graphics(this.ctx)
+
+ let ret = graphics.group({
+ class: 'apexcharts-heatmap',
+ })
+
+ ret.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
+
+ // width divided into equal parts
+ let xDivision = w.globals.gridWidth / w.globals.dataPoints
+ let yDivision = w.globals.gridHeight / w.globals.series.length
+
+ let y1 = 0
+ let rev = false
+
+ this.negRange = this.helpers.checkColorRange()
+
+ let heatSeries = series.slice()
+
+ if (w.config.yaxis[0].reversed) {
+ rev = true
+ heatSeries.reverse()
+ }
+
+ for (
+ let i = rev ? 0 : heatSeries.length - 1;
+ rev ? i < heatSeries.length : i >= 0;
+ rev ? i++ : i--
+ ) {
+ // el to which series will be drawn
+ let elSeries = graphics.group({
+ class: `apexcharts-series apexcharts-heatmap-series`,
+ seriesName: Utils.escapeString(w.globals.seriesNames[i]),
+ rel: i + 1,
+ 'data:realIndex': i,
+ })
+ this.ctx.series.addCollapsedClassToSeries(elSeries, i)
+
+ if (w.config.chart.dropShadow.enabled) {
+ const shadow = w.config.chart.dropShadow
+ const filters = new Filters(this.ctx)
+ filters.dropShadow(elSeries, shadow, i)
+ }
+
+ let x1 = 0
+ let shadeIntensity = w.config.plotOptions.heatmap.shadeIntensity
+
+ for (let j = 0; j < heatSeries[i].length; j++) {
+ let heatColor = this.helpers.getShadeColor(
+ w.config.chart.type,
+ i,
+ j,
+ this.negRange
+ )
+ let color = heatColor.color
+ let heatColorProps = heatColor.colorProps
+
+ if (w.config.fill.type === 'image') {
+ const fill = new Fill(this.ctx)
+
+ color = fill.fillPath({
+ seriesNumber: i,
+ dataPointIndex: j,
+ opacity: w.globals.hasNegs
+ ? heatColorProps.percent < 0
+ ? 1 - (1 + heatColorProps.percent / 100)
+ : shadeIntensity + heatColorProps.percent / 100
+ : heatColorProps.percent / 100,
+ patternID: Utils.randomId(),
+ width: w.config.fill.image.width
+ ? w.config.fill.image.width
+ : xDivision,
+ height: w.config.fill.image.height
+ ? w.config.fill.image.height
+ : yDivision,
+ })
+ }
+
+ let radius = this.rectRadius
+
+ let rect = graphics.drawRect(x1, y1, xDivision, yDivision, radius)
+ rect.attr({
+ cx: x1,
+ cy: y1,
+ })
+
+ rect.node.classList.add('apexcharts-heatmap-rect')
+ elSeries.add(rect)
+
+ rect.attr({
+ fill: color,
+ i,
+ index: i,
+ j,
+ val: series[i][j],
+ 'stroke-width': this.strokeWidth,
+ stroke: w.config.plotOptions.heatmap.useFillColorAsStroke
+ ? color
+ : w.globals.stroke.colors[0],
+ color,
+ })
+
+ this.helpers.addListeners(rect)
+
+ if (w.config.chart.animations.enabled && !w.globals.dataChanged) {
+ let speed = 1
+ if (!w.globals.resized) {
+ speed = w.config.chart.animations.speed
+ }
+ this.animateHeatMap(rect, x1, y1, xDivision, yDivision, speed)
+ }
+
+ if (w.globals.dataChanged) {
+ let speed = 1
+ if (this.dynamicAnim.enabled && w.globals.shouldAnimate) {
+ speed = this.dynamicAnim.speed
+
+ let colorFrom =
+ w.globals.previousPaths[i] &&
+ w.globals.previousPaths[i][j] &&
+ w.globals.previousPaths[i][j].color
+
+ if (!colorFrom) colorFrom = 'rgba(255, 255, 255, 0)'
+
+ this.animateHeatColor(
+ rect,
+ Utils.isColorHex(colorFrom)
+ ? colorFrom
+ : Utils.rgb2hex(colorFrom),
+ Utils.isColorHex(color) ? color : Utils.rgb2hex(color),
+ speed
+ )
+ }
+ }
+
+ let formatter = w.config.dataLabels.formatter
+ let formattedText = formatter(w.globals.series[i][j], {
+ value: w.globals.series[i][j],
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ })
+
+ let dataLabels = this.helpers.calculateDataLabels({
+ text: formattedText,
+ x: x1 + xDivision / 2,
+ y: y1 + yDivision / 2,
+ i,
+ j,
+ colorProps: heatColorProps,
+ series: heatSeries,
+ })
+ if (dataLabels !== null) {
+ elSeries.add(dataLabels)
+ }
+
+ x1 = x1 + xDivision
+ }
+
+ y1 = y1 + yDivision
+
+ ret.add(elSeries)
+ }
+
+ // adjust yaxis labels for heatmap
+ let yAxisScale = w.globals.yAxisScale[0].result.slice()
+ if (w.config.yaxis[0].reversed) {
+ yAxisScale.unshift('')
+ } else {
+ yAxisScale.push('')
+ }
+ w.globals.yAxisScale[0].result = yAxisScale
+
+ return ret
+ }
+
+ animateHeatMap(el, x, y, width, height, speed) {
+ const animations = new Animations(this.ctx)
+ animations.animateRect(
+ el,
+ {
+ x: x + width / 2,
+ y: y + height / 2,
+ width: 0,
+ height: 0,
+ },
+ {
+ x,
+ y,
+ width,
+ height,
+ },
+ speed,
+ () => {
+ animations.animationCompleted(el)
+ }
+ )
+ }
+
+ animateHeatColor(el, colorFrom, colorTo, speed) {
+ el.attr({
+ fill: colorFrom,
+ })
+ .animate(speed)
+ .attr({
+ fill: colorTo,
+ })
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Line.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Line.js
new file mode 100644
index 0000000..9e11724
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Line.js
@@ -0,0 +1,1139 @@
+import CoreUtils from '../modules/CoreUtils'
+import Graphics from '../modules/Graphics'
+import Fill from '../modules/Fill'
+import DataLabels from '../modules/DataLabels'
+import Markers from '../modules/Markers'
+import Scatter from './Scatter'
+import Utils from '../utils/Utils'
+import Helpers from './common/line/Helpers'
+import { svgPath, spline } from '../libs/monotone-cubic'
+/**
+ * ApexCharts Line Class responsible for drawing Line / Area / RangeArea Charts.
+ * This class is also responsible for generating values for Bubble/Scatter charts, so need to rename it to Axis Charts to avoid confusions
+ * @module Line
+ **/
+
+class Line {
+ constructor(ctx, xyRatios, isPointsChart) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.xyRatios = xyRatios
+
+ this.pointsChart =
+ !(
+ this.w.config.chart.type !== 'bubble' &&
+ this.w.config.chart.type !== 'scatter'
+ ) || isPointsChart
+
+ this.scatter = new Scatter(this.ctx)
+
+ this.noNegatives = this.w.globals.minX === Number.MAX_VALUE
+
+ this.lineHelpers = new Helpers(this)
+ this.markers = new Markers(this.ctx)
+
+ this.prevSeriesY = []
+ this.categoryAxisCorrection = 0
+ this.yaxisIndex = 0
+ }
+
+ draw(series, ctype, seriesIndex, seriesRangeEnd) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+ let type = w.globals.comboCharts ? ctype : w.config.chart.type
+ let ret = graphics.group({
+ class: `apexcharts-${type}-series apexcharts-plot-series`,
+ })
+
+ const coreUtils = new CoreUtils(this.ctx, w)
+ this.yRatio = this.xyRatios.yRatio
+ this.zRatio = this.xyRatios.zRatio
+ this.xRatio = this.xyRatios.xRatio
+ this.baseLineY = this.xyRatios.baseLineY
+
+ series = coreUtils.getLogSeries(series)
+ this.yRatio = coreUtils.getLogYRatios(this.yRatio)
+ // We call draw() for each series group
+ this.prevSeriesY = []
+
+ // push all series in an array, so we can draw in reverse order
+ // (for stacked charts)
+ let allSeries = []
+
+ for (let i = 0; i < series.length; i++) {
+ series = this.lineHelpers.sameValueSeriesFix(i, series)
+
+ let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
+ let translationsIndex = this.yRatio.length > 1 ? realIndex : 0
+
+ this._initSerieVariables(series, i, realIndex)
+
+ let yArrj = [] // hold y values of current iterating series
+ let y2Arrj = [] // holds y2 values in range-area charts
+ let xArrj = [] // hold x values of current iterating series
+
+ let x = w.globals.padHorizontal + this.categoryAxisCorrection
+ let y = 1
+
+ let linePaths = []
+ let areaPaths = []
+
+ this.ctx.series.addCollapsedClassToSeries(this.elSeries, realIndex)
+
+ if (w.globals.isXNumeric && w.globals.seriesX.length > 0) {
+ x = (w.globals.seriesX[realIndex][0] - w.globals.minX) / this.xRatio
+ }
+
+ xArrj.push(x)
+
+ let pX = x
+ let pY
+ let pY2
+ let prevX = pX
+ let prevY = this.zeroY
+ let prevY2 = this.zeroY
+ let lineYPosition = 0
+
+ // the first value in the current series is not null or undefined
+ let firstPrevY = this.lineHelpers.determineFirstPrevY({
+ i,
+ realIndex,
+ series,
+ prevY,
+ lineYPosition,
+ translationsIndex,
+ })
+ prevY = firstPrevY.prevY
+ if (w.config.stroke.curve === 'monotoneCubic' && series[i][0] === null) {
+ // we have to discard the y position if 1st dataPoint is null as it
+ // causes issues with monotoneCubic path creation
+ yArrj.push(null)
+ } else {
+ yArrj.push(prevY)
+ }
+ pY = prevY
+
+ // y2 are needed for range-area charts
+ let firstPrevY2
+
+ if (type === 'rangeArea') {
+ firstPrevY2 = this.lineHelpers.determineFirstPrevY({
+ i,
+ realIndex,
+ series: seriesRangeEnd,
+ prevY: prevY2,
+ lineYPosition,
+ translationsIndex,
+ })
+ prevY2 = firstPrevY2.prevY
+ pY2 = prevY2
+ y2Arrj.push(yArrj[0] !== null ? prevY2 : null)
+ }
+
+ let pathsFrom = this._calculatePathsFrom({
+ type,
+ series,
+ i,
+ realIndex,
+ translationsIndex,
+ prevX,
+ prevY,
+ prevY2,
+ })
+
+ // RangeArea will resume with these for the upper path creation
+ let rYArrj = [yArrj[0]]
+ let rY2Arrj = [y2Arrj[0]]
+
+ const iteratingOpts = {
+ type,
+ series,
+ realIndex,
+ translationsIndex,
+ i,
+ x,
+ y,
+ pX,
+ pY,
+ pathsFrom,
+ linePaths,
+ areaPaths,
+ seriesIndex,
+ lineYPosition,
+ xArrj,
+ yArrj,
+ y2Arrj,
+ seriesRangeEnd,
+ }
+
+ let paths = this._iterateOverDataPoints({
+ ...iteratingOpts,
+ iterations: type === 'rangeArea' ? series[i].length - 1 : undefined,
+ isRangeStart: true,
+ })
+
+ if (type === 'rangeArea') {
+ let pathsFrom2 = this._calculatePathsFrom({
+ series: seriesRangeEnd,
+ i,
+ realIndex,
+ prevX,
+ prevY: prevY2,
+ })
+ let rangePaths = this._iterateOverDataPoints({
+ ...iteratingOpts,
+ series: seriesRangeEnd,
+ xArrj: [x],
+ yArrj: rYArrj,
+ y2Arrj: rY2Arrj,
+ pY: pY2,
+ areaPaths: paths.areaPaths,
+ pathsFrom: pathsFrom2,
+ iterations: seriesRangeEnd[i].length - 1,
+ isRangeStart: false,
+ })
+
+ // Path may be segmented by nulls in data.
+ // paths.linePaths should hold (segments * 2) paths (upper and lower)
+ // the first n segments belong to the lower and the last n segments
+ // belong to the upper.
+ // paths.linePaths and rangePaths.linepaths are actually equivalent
+ // but we retain the distinction below for consistency with the
+ // unsegmented paths conditional branch.
+ let segments = paths.linePaths.length / 2
+ for (let s = 0; s < segments; s++) {
+ paths.linePaths[s] =
+ rangePaths.linePaths[s + segments] + paths.linePaths[s]
+ }
+ paths.linePaths.splice(segments)
+ paths.pathFromLine = rangePaths.pathFromLine + paths.pathFromLine
+ } else {
+ paths.pathFromArea += 'z'
+ }
+
+ this._handlePaths({ type, realIndex, i, paths })
+
+ this.elSeries.add(this.elPointsMain)
+ this.elSeries.add(this.elDataLabelsWrap)
+
+ allSeries.push(this.elSeries)
+ }
+
+ if (typeof w.config.series[0]?.zIndex !== 'undefined') {
+ allSeries.sort(
+ (a, b) =>
+ Number(a.node.getAttribute('zIndex')) -
+ Number(b.node.getAttribute('zIndex'))
+ )
+ }
+
+ if (w.config.chart.stacked) {
+ for (let s = allSeries.length - 1; s >= 0; s--) {
+ ret.add(allSeries[s])
+ }
+ } else {
+ for (let s = 0; s < allSeries.length; s++) {
+ ret.add(allSeries[s])
+ }
+ }
+
+ return ret
+ }
+
+ _initSerieVariables(series, i, realIndex) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+
+ // width divided into equal parts
+ this.xDivision =
+ w.globals.gridWidth /
+ (w.globals.dataPoints - (w.config.xaxis.tickPlacement === 'on' ? 1 : 0))
+
+ this.strokeWidth = Array.isArray(w.config.stroke.width)
+ ? w.config.stroke.width[realIndex]
+ : w.config.stroke.width
+
+ let translationsIndex = 0
+ if (this.yRatio.length > 1) {
+ this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex]
+ translationsIndex = realIndex
+ }
+
+ this.isReversed =
+ w.config.yaxis[this.yaxisIndex] &&
+ w.config.yaxis[this.yaxisIndex].reversed
+
+ // zeroY is the 0 value in y series which can be used in negative charts
+ this.zeroY =
+ w.globals.gridHeight -
+ this.baseLineY[translationsIndex] -
+ (this.isReversed ? w.globals.gridHeight : 0) +
+ (this.isReversed ? this.baseLineY[translationsIndex] * 2 : 0)
+
+ this.areaBottomY = this.zeroY
+ if (
+ this.zeroY > w.globals.gridHeight ||
+ w.config.plotOptions.area.fillTo === 'end'
+ ) {
+ this.areaBottomY = w.globals.gridHeight
+ }
+
+ this.categoryAxisCorrection = this.xDivision / 2
+
+ // el to which series will be drawn
+ this.elSeries = graphics.group({
+ class: `apexcharts-series`,
+ zIndex:
+ typeof w.config.series[realIndex].zIndex !== 'undefined'
+ ? w.config.series[realIndex].zIndex
+ : realIndex,
+ seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
+ })
+
+ // points
+ this.elPointsMain = graphics.group({
+ class: 'apexcharts-series-markers-wrap',
+ 'data:realIndex': realIndex,
+ })
+
+ // eldatalabels
+ this.elDataLabelsWrap = graphics.group({
+ class: 'apexcharts-datalabels',
+ 'data:realIndex': realIndex,
+ })
+
+ let longestSeries = series[i].length === w.globals.dataPoints
+ this.elSeries.attr({
+ 'data:longestSeries': longestSeries,
+ rel: i + 1,
+ 'data:realIndex': realIndex,
+ })
+
+ this.appendPathFrom = true
+ }
+
+ _calculatePathsFrom({
+ type,
+ series,
+ i,
+ realIndex,
+ translationsIndex,
+ prevX,
+ prevY,
+ prevY2,
+ }) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+ let linePath, areaPath, pathFromLine, pathFromArea
+
+ if (series[i][0] === null) {
+ // when the first value itself is null, we need to move the pointer to a location where a null value is not found
+ for (let s = 0; s < series[i].length; s++) {
+ if (series[i][s] !== null) {
+ prevX = this.xDivision * s
+ prevY = this.zeroY - series[i][s] / this.yRatio[translationsIndex]
+ linePath = graphics.move(prevX, prevY)
+ areaPath = graphics.move(prevX, this.areaBottomY)
+ break
+ }
+ }
+ } else {
+ linePath = graphics.move(prevX, prevY)
+
+ if (type === 'rangeArea') {
+ linePath = graphics.move(prevX, prevY2) + graphics.line(prevX, prevY)
+ }
+ areaPath =
+ graphics.move(prevX, this.areaBottomY) + graphics.line(prevX, prevY)
+ }
+
+ pathFromLine = graphics.move(0, this.zeroY) + graphics.line(0, this.zeroY)
+ pathFromArea = graphics.move(0, this.zeroY) + graphics.line(0, this.zeroY)
+
+ if (w.globals.previousPaths.length > 0) {
+ const pathFrom = this.lineHelpers.checkPreviousPaths({
+ pathFromLine,
+ pathFromArea,
+ realIndex,
+ })
+ pathFromLine = pathFrom.pathFromLine
+ pathFromArea = pathFrom.pathFromArea
+ }
+
+ return {
+ prevX,
+ prevY,
+ linePath,
+ areaPath,
+ pathFromLine,
+ pathFromArea,
+ }
+ }
+
+ _handlePaths({ type, realIndex, i, paths }) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+ const fill = new Fill(this.ctx)
+
+ // push all current y values array to main PrevY Array
+ this.prevSeriesY.push(paths.yArrj)
+
+ // push all x val arrays into main xArr
+ w.globals.seriesXvalues[realIndex] = paths.xArrj
+ w.globals.seriesYvalues[realIndex] = paths.yArrj
+
+ const forecast = w.config.forecastDataPoints
+ if (forecast.count > 0 && type !== 'rangeArea') {
+ const forecastCutoff =
+ w.globals.seriesXvalues[realIndex][
+ w.globals.seriesXvalues[realIndex].length - forecast.count - 1
+ ]
+ const elForecastMask = graphics.drawRect(
+ forecastCutoff,
+ 0,
+ w.globals.gridWidth,
+ w.globals.gridHeight,
+ 0
+ )
+ w.globals.dom.elForecastMask.appendChild(elForecastMask.node)
+
+ const elNonForecastMask = graphics.drawRect(
+ 0,
+ 0,
+ forecastCutoff,
+ w.globals.gridHeight,
+ 0
+ )
+ w.globals.dom.elNonForecastMask.appendChild(elNonForecastMask.node)
+ }
+
+ // these elements will be shown after area path animation completes
+ if (!this.pointsChart) {
+ w.globals.delayedElements.push({
+ el: this.elPointsMain.node,
+ index: realIndex,
+ })
+ }
+
+ const defaultRenderedPathOptions = {
+ i,
+ realIndex,
+ animationDelay: i,
+ initialSpeed: w.config.chart.animations.speed,
+ dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed,
+ className: `apexcharts-${type}`,
+ }
+
+ if (type === 'area') {
+ let pathFill = fill.fillPath({
+ seriesNumber: realIndex,
+ })
+
+ for (let p = 0; p < paths.areaPaths.length; p++) {
+ let renderedPath = graphics.renderPaths({
+ ...defaultRenderedPathOptions,
+ pathFrom: paths.pathFromArea,
+ pathTo: paths.areaPaths[p],
+ stroke: 'none',
+ strokeWidth: 0,
+ strokeLineCap: null,
+ fill: pathFill,
+ })
+
+ this.elSeries.add(renderedPath)
+ }
+ }
+
+ if (w.config.stroke.show && !this.pointsChart) {
+ let lineFill = null
+ if (type === 'line') {
+ lineFill = fill.fillPath({
+ seriesNumber: realIndex,
+ i,
+ })
+ } else {
+ if (w.config.stroke.fill.type === 'solid') {
+ lineFill = w.globals.stroke.colors[realIndex]
+ } else {
+ const prevFill = w.config.fill
+ w.config.fill = w.config.stroke.fill
+
+ lineFill = fill.fillPath({
+ seriesNumber: realIndex,
+ i,
+ })
+ w.config.fill = prevFill
+ }
+ }
+
+ // range-area paths are drawn using linePaths
+ for (let p = 0; p < paths.linePaths.length; p++) {
+ let pathFill = lineFill
+ if (type === 'rangeArea') {
+ pathFill = fill.fillPath({
+ seriesNumber: realIndex,
+ })
+ }
+ const linePathCommonOpts = {
+ ...defaultRenderedPathOptions,
+ pathFrom: paths.pathFromLine,
+ pathTo: paths.linePaths[p],
+ stroke: lineFill,
+ strokeWidth: this.strokeWidth,
+ strokeLineCap: w.config.stroke.lineCap,
+ fill: type === 'rangeArea' ? pathFill : 'none',
+ }
+ let renderedPath = graphics.renderPaths(linePathCommonOpts)
+ this.elSeries.add(renderedPath)
+ renderedPath.attr('fill-rule', `evenodd`)
+
+ if (forecast.count > 0 && type !== 'rangeArea') {
+ let renderedForecastPath = graphics.renderPaths(linePathCommonOpts)
+
+ renderedForecastPath.node.setAttribute(
+ 'stroke-dasharray',
+ forecast.dashArray
+ )
+
+ if (forecast.strokeWidth) {
+ renderedForecastPath.node.setAttribute(
+ 'stroke-width',
+ forecast.strokeWidth
+ )
+ }
+
+ this.elSeries.add(renderedForecastPath)
+ renderedForecastPath.attr(
+ 'clip-path',
+ `url(#forecastMask${w.globals.cuid})`
+ )
+ renderedPath.attr(
+ 'clip-path',
+ `url(#nonForecastMask${w.globals.cuid})`
+ )
+ }
+ }
+ }
+ }
+
+ _iterateOverDataPoints({
+ type,
+ series,
+ iterations,
+ realIndex,
+ translationsIndex,
+ i,
+ x,
+ y,
+ pX,
+ pY,
+ pathsFrom,
+ linePaths,
+ areaPaths,
+ seriesIndex,
+ lineYPosition,
+ xArrj,
+ yArrj,
+ y2Arrj,
+ isRangeStart,
+ seriesRangeEnd,
+ }) {
+ const w = this.w
+ let graphics = new Graphics(this.ctx)
+ let yRatio = this.yRatio
+ let { prevY, linePath, areaPath, pathFromLine, pathFromArea } = pathsFrom
+
+ const minY = Utils.isNumber(w.globals.minYArr[realIndex])
+ ? w.globals.minYArr[realIndex]
+ : w.globals.minY
+
+ if (!iterations) {
+ iterations =
+ w.globals.dataPoints > 1
+ ? w.globals.dataPoints - 1
+ : w.globals.dataPoints
+ }
+
+ const getY = (_y, lineYPos) => {
+ return (
+ lineYPos -
+ _y / yRatio[translationsIndex] +
+ (this.isReversed ? _y / yRatio[translationsIndex] : 0) * 2
+ )
+ }
+
+ let y2 = y
+
+ let stackSeries =
+ (w.config.chart.stacked && !w.globals.comboCharts) ||
+ (w.config.chart.stacked &&
+ w.globals.comboCharts &&
+ (!this.w.config.chart.stackOnlyBar ||
+ this.w.config.series[realIndex]?.type === 'bar' ||
+ this.w.config.series[realIndex]?.type === 'column'))
+
+ let curve = w.config.stroke.curve
+ if (Array.isArray(curve)) {
+ if (Array.isArray(seriesIndex)) {
+ curve = curve[seriesIndex[i]]
+ } else {
+ curve = curve[i]
+ }
+ }
+
+ let pathState = 0
+ let segmentStartX
+
+ for (let j = 0; j < iterations; j++) {
+ const isNull =
+ typeof series[i][j + 1] === 'undefined' || series[i][j + 1] === null
+
+ if (w.globals.isXNumeric) {
+ let sX = w.globals.seriesX[realIndex][j + 1]
+ if (typeof w.globals.seriesX[realIndex][j + 1] === 'undefined') {
+ /* fix #374 */
+ sX = w.globals.seriesX[realIndex][iterations - 1]
+ }
+ x = (sX - w.globals.minX) / this.xRatio
+ } else {
+ x = x + this.xDivision
+ }
+
+ if (stackSeries) {
+ if (
+ i > 0 &&
+ w.globals.collapsedSeries.length < w.config.series.length - 1
+ ) {
+ // a collapsed series in a stacked chart may provide wrong result
+ // for the next series, hence find the prevIndex of prev series
+ // which is not collapsed - fixes apexcharts.js#1372
+ const prevIndex = (pi) => {
+ for (let pii = pi; pii > 0; pii--) {
+ if (
+ w.globals.collapsedSeriesIndices.indexOf(
+ seriesIndex?.[pii] || pii
+ ) > -1
+ ) {
+ pii--
+ } else {
+ return pii
+ }
+ }
+ return 0
+ }
+ lineYPosition = this.prevSeriesY[prevIndex(i - 1)][j + 1]
+ } else {
+ // the first series will not have prevY values
+ lineYPosition = this.zeroY
+ }
+ } else {
+ lineYPosition = this.zeroY
+ }
+
+ if (isNull) {
+ y = getY(minY, lineYPosition)
+ } else {
+ y = getY(series[i][j + 1], lineYPosition)
+
+ if (type === 'rangeArea') {
+ y2 = getY(seriesRangeEnd[i][j + 1], lineYPosition)
+ }
+ }
+
+ // push current X
+ xArrj.push(x)
+
+ // push current Y that will be used as next series's bottom position
+ if (
+ isNull &&
+ (w.config.stroke.curve === 'smooth' ||
+ w.config.stroke.curve === 'monotoneCubic')
+ ) {
+ yArrj.push(null)
+ y2Arrj.push(null)
+ } else {
+ yArrj.push(y)
+ y2Arrj.push(y2)
+ }
+
+ let pointsPos = this.lineHelpers.calculatePoints({
+ series,
+ x,
+ y,
+ realIndex,
+ i,
+ j,
+ prevY,
+ })
+
+ let calculatedPaths = this._createPaths({
+ type,
+ series,
+ i,
+ realIndex,
+ j,
+ x,
+ y,
+ y2,
+ xArrj,
+ yArrj,
+ y2Arrj,
+ pX,
+ pY,
+ pathState,
+ segmentStartX,
+ linePath,
+ areaPath,
+ linePaths,
+ areaPaths,
+ curve,
+ isRangeStart,
+ })
+
+ areaPaths = calculatedPaths.areaPaths
+ linePaths = calculatedPaths.linePaths
+ pX = calculatedPaths.pX
+ pY = calculatedPaths.pY
+ pathState = calculatedPaths.pathState
+ segmentStartX = calculatedPaths.segmentStartX
+ areaPath = calculatedPaths.areaPath
+ linePath = calculatedPaths.linePath
+
+ if (
+ this.appendPathFrom &&
+ !(curve === 'monotoneCubic' && type === 'rangeArea')
+ ) {
+ pathFromLine += graphics.line(x, this.zeroY)
+ pathFromArea += graphics.line(x, this.zeroY)
+ }
+
+ this.handleNullDataPoints(series, pointsPos, i, j, realIndex)
+
+ this._handleMarkersAndLabels({
+ type,
+ pointsPos,
+ i,
+ j,
+ realIndex,
+ isRangeStart,
+ })
+ }
+
+ return {
+ yArrj,
+ xArrj,
+ pathFromArea,
+ areaPaths,
+ pathFromLine,
+ linePaths,
+ linePath,
+ areaPath,
+ }
+ }
+
+ _handleMarkersAndLabels({ type, pointsPos, isRangeStart, i, j, realIndex }) {
+ const w = this.w
+ let dataLabels = new DataLabels(this.ctx)
+
+ if (!this.pointsChart) {
+ if (w.globals.series[i].length > 1) {
+ this.elPointsMain.node.classList.add('apexcharts-element-hidden')
+ }
+
+ let elPointsWrap = this.markers.plotChartMarkers(
+ pointsPos,
+ realIndex,
+ j + 1
+ )
+ if (elPointsWrap !== null) {
+ this.elPointsMain.add(elPointsWrap)
+ }
+ } else {
+ // scatter / bubble chart points creation
+ this.scatter.draw(this.elSeries, j, {
+ realIndex,
+ pointsPos,
+ zRatio: this.zRatio,
+ elParent: this.elPointsMain,
+ })
+ }
+
+ let drawnLabels = dataLabels.drawDataLabel({
+ type,
+ isRangeStart,
+ pos: pointsPos,
+ i: realIndex,
+ j: j + 1,
+ })
+ if (drawnLabels !== null) {
+ this.elDataLabelsWrap.add(drawnLabels)
+ }
+ }
+
+ _createPaths({
+ type,
+ series,
+ i,
+ realIndex,
+ j,
+ x,
+ y,
+ xArrj,
+ yArrj,
+ y2,
+ y2Arrj,
+ pX,
+ pY,
+ pathState,
+ segmentStartX,
+ linePath,
+ areaPath,
+ linePaths,
+ areaPaths,
+ curve,
+ isRangeStart,
+ }) {
+ let graphics = new Graphics(this.ctx)
+ const areaBottomY = this.areaBottomY
+ let rangeArea = type === 'rangeArea'
+ let isLowerRangeAreaPath = type === 'rangeArea' && isRangeStart
+
+ switch (curve) {
+ case 'monotoneCubic':
+ let yAj = isRangeStart ? yArrj : y2Arrj
+ let getSmoothInputs = (xArr, yArr) => {
+ return xArr
+ .map((_, i) => {
+ return [_, yArr[i]]
+ })
+ .filter((_) => _[1] !== null)
+ }
+ let getSegmentLengths = (yArr) => {
+ // Get the segment lengths so the segments can be extracted from
+ // the null-filtered smoothInputs array
+ let segLens = []
+ let count = 0
+ yArr.forEach((_) => {
+ if (_ !== null) {
+ count++
+ } else if (count > 0) {
+ segLens.push(count)
+ count = 0
+ }
+ })
+ if (count > 0) {
+ segLens.push(count)
+ }
+ return segLens
+ }
+ let getSegments = (yArr, points) => {
+ let segLens = getSegmentLengths(yArr)
+ let segments = []
+ for (let i = 0, len = 0; i < segLens.length; len += segLens[i++]) {
+ segments[i] = spline.slice(points, len, len + segLens[i])
+ }
+ return segments
+ }
+
+ switch (pathState) {
+ case 0:
+ // Find start of segment
+ if (yAj[j + 1] === null) {
+ break
+ }
+ pathState = 1
+ // continue through to pathState 1
+ case 1:
+ if (
+ !(rangeArea
+ ? xArrj.length === series[i].length
+ : j === series[i].length - 2)
+ ) {
+ break
+ }
+ // continue through to pathState 2
+ case 2:
+ // Interpolate the full series with nulls excluded then extract the
+ // null delimited segments with interpolated points included.
+ const _xAj = isRangeStart ? xArrj : xArrj.slice().reverse()
+ const _yAj = isRangeStart ? yAj : yAj.slice().reverse()
+
+ const smoothInputs = getSmoothInputs(_xAj, _yAj)
+ const points =
+ smoothInputs.length > 1
+ ? spline.points(smoothInputs)
+ : smoothInputs
+
+ let smoothInputsLower = []
+ if (rangeArea) {
+ if (isLowerRangeAreaPath) {
+ // As we won't be needing it, borrow areaPaths to retain our
+ // rangeArea lower points.
+ areaPaths = smoothInputs
+ } else {
+ // Retrieve the corresponding lower raw interpolated points so we
+ // can join onto its end points. Note: the upper Y2 segments will
+ // be in the reverse order relative to the lower segments.
+ smoothInputsLower = areaPaths.reverse()
+ }
+ }
+
+ let segmentCount = 0
+ let smoothInputsIndex = 0
+ getSegments(_yAj, points).forEach((_) => {
+ segmentCount++
+ let svgPoints = svgPath(_)
+ let _start = smoothInputsIndex
+ smoothInputsIndex += _.length
+ let _end = smoothInputsIndex - 1
+ if (isLowerRangeAreaPath) {
+ linePath =
+ graphics.move(
+ smoothInputs[_start][0],
+ smoothInputs[_start][1]
+ ) + svgPoints
+ } else if (rangeArea) {
+ linePath =
+ graphics.move(
+ smoothInputsLower[_start][0],
+ smoothInputsLower[_start][1]
+ ) +
+ graphics.line(
+ smoothInputs[_start][0],
+ smoothInputs[_start][1]
+ ) +
+ svgPoints +
+ graphics.line(
+ smoothInputsLower[_end][0],
+ smoothInputsLower[_end][1]
+ )
+ } else {
+ linePath =
+ graphics.move(
+ smoothInputs[_start][0],
+ smoothInputs[_start][1]
+ ) + svgPoints
+ areaPath =
+ linePath +
+ graphics.line(smoothInputs[_end][0], areaBottomY) +
+ graphics.line(smoothInputs[_start][0], areaBottomY) +
+ 'z'
+ areaPaths.push(areaPath)
+ }
+ linePaths.push(linePath)
+ })
+
+ if (rangeArea && segmentCount > 1 && !isLowerRangeAreaPath) {
+ // Reverse the order of the upper path segments
+ let upperLinePaths = linePaths.slice(segmentCount).reverse()
+ linePaths.splice(segmentCount)
+ upperLinePaths.forEach((u) => linePaths.push(u))
+ }
+ pathState = 0
+ break
+ }
+ break
+ case 'smooth':
+ let length = (x - pX) * 0.35
+ if (series[i][j] === null) {
+ pathState = 0
+ } else {
+ switch (pathState) {
+ case 0:
+ // Beginning of segment
+ segmentStartX = pX
+ if (isLowerRangeAreaPath) {
+ // Need to add path portion that will join to the upper path
+ linePath = graphics.move(pX, y2Arrj[j]) + graphics.line(pX, pY)
+ } else {
+ linePath = graphics.move(pX, pY)
+ }
+ areaPath = graphics.move(pX, pY)
+
+ // Check for single isolated point
+ if (series[i][j + 1] === null) {
+ linePaths.push(linePath)
+ areaPaths.push(areaPath)
+ // Stay in pathState = 0;
+ break
+ }
+ pathState = 1
+ if (j < series[i].length - 2) {
+ let p = graphics.curve(pX + length, pY, x - length, y, x, y)
+ linePath += p
+ areaPath += p
+ break
+ }
+ // Continue on with pathState 1 to finish the path and exit
+ case 1:
+ // Continuing with segment
+ if (series[i][j + 1] === null) {
+ // Segment ends here
+ if (isLowerRangeAreaPath) {
+ linePath += graphics.line(pX, y2)
+ } else {
+ linePath += graphics.move(pX, pY)
+ }
+ areaPath +=
+ graphics.line(pX, areaBottomY) +
+ graphics.line(segmentStartX, areaBottomY) +
+ 'z'
+ linePaths.push(linePath)
+ areaPaths.push(areaPath)
+ pathState = -1
+ } else {
+ let p = graphics.curve(pX + length, pY, x - length, y, x, y)
+ linePath += p
+ areaPath += p
+ if (j >= series[i].length - 2) {
+ if (isLowerRangeAreaPath) {
+ // Need to add path portion that will join to the upper path
+ linePath +=
+ graphics.curve(x, y, x, y, x, y2) + graphics.move(x, y2)
+ }
+ areaPath +=
+ graphics.curve(x, y, x, y, x, areaBottomY) +
+ graphics.line(segmentStartX, areaBottomY) +
+ 'z'
+ linePaths.push(linePath)
+ areaPaths.push(areaPath)
+ pathState = -1
+ }
+ }
+ break
+ }
+ }
+
+ pX = x
+ pY = y
+
+ break
+ default:
+ let pathToPoint = (curve, x, y) => {
+ let path = []
+ switch (curve) {
+ case 'stepline':
+ path = graphics.line(x, null, 'H') + graphics.line(null, y, 'V')
+ break
+ case 'linestep':
+ path = graphics.line(null, y, 'V') + graphics.line(x, null, 'H')
+ break
+ case 'straight':
+ path = graphics.line(x, y)
+ break
+ }
+ return path
+ }
+ if (series[i][j] === null) {
+ pathState = 0
+ } else {
+ switch (pathState) {
+ case 0:
+ // Beginning of segment
+ segmentStartX = pX
+ if (isLowerRangeAreaPath) {
+ // Need to add path portion that will join to the upper path
+ linePath = graphics.move(pX, y2Arrj[j]) + graphics.line(pX, pY)
+ } else {
+ linePath = graphics.move(pX, pY)
+ }
+ areaPath = graphics.move(pX, pY)
+
+ // Check for single isolated point
+ if (series[i][j + 1] === null) {
+ linePaths.push(linePath)
+ areaPaths.push(areaPath)
+ // Stay in pathState = 0
+ break
+ }
+ pathState = 1
+ if (j < series[i].length - 2) {
+ let p = pathToPoint(curve, x, y)
+ linePath += p
+ areaPath += p
+ break
+ }
+ // Continue on with pathState 1 to finish the path and exit
+ case 1:
+ // Continuing with segment
+ if (series[i][j + 1] === null) {
+ // Segment ends here
+ if (isLowerRangeAreaPath) {
+ linePath += graphics.line(pX, y2)
+ } else {
+ linePath += graphics.move(pX, pY)
+ }
+ areaPath +=
+ graphics.line(pX, areaBottomY) +
+ graphics.line(segmentStartX, areaBottomY) +
+ 'z'
+ linePaths.push(linePath)
+ areaPaths.push(areaPath)
+ pathState = -1
+ } else {
+ let p = pathToPoint(curve, x, y)
+ linePath += p
+ areaPath += p
+ if (j >= series[i].length - 2) {
+ if (isLowerRangeAreaPath) {
+ // Need to add path portion that will join to the upper path
+ linePath += graphics.line(x, y2)
+ }
+ areaPath +=
+ graphics.line(x, areaBottomY) +
+ graphics.line(segmentStartX, areaBottomY) +
+ 'z'
+ linePaths.push(linePath)
+ areaPaths.push(areaPath)
+ pathState = -1
+ }
+ }
+ break
+ }
+ }
+
+ pX = x
+ pY = y
+
+ break
+ }
+
+ return {
+ linePaths,
+ areaPaths,
+ pX,
+ pY,
+ pathState,
+ segmentStartX,
+ linePath,
+ areaPath,
+ }
+ }
+
+ handleNullDataPoints(series, pointsPos, i, j, realIndex) {
+ const w = this.w
+ if (
+ (series[i][j] === null && w.config.markers.showNullDataPoints) ||
+ series[i].length === 1
+ ) {
+ let pSize = this.strokeWidth - w.config.markers.strokeWidth / 2
+ if (!(pSize > 0)) {
+ pSize = 0
+ }
+ // fixes apexcharts.js#1282, #1252
+ let elPointsWrap = this.markers.plotChartMarkers(
+ pointsPos,
+ realIndex,
+ j + 1,
+ pSize,
+ true
+ )
+ if (elPointsWrap !== null) {
+ this.elPointsMain.add(elPointsWrap)
+ }
+ }
+ }
+}
+
+export default Line
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Pie.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Pie.js
new file mode 100644
index 0000000..edd4f4c
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Pie.js
@@ -0,0 +1,1021 @@
+import Animations from '../modules/Animations'
+import Fill from '../modules/Fill'
+import Utils from '../utils/Utils'
+import Graphics from '../modules/Graphics'
+import Filters from '../modules/Filters'
+import Scales from '../modules/Scales'
+import Helpers from './common/circle/Helpers'
+/**
+ * ApexCharts Pie Class for drawing Pie / Donut Charts.
+ * @module Pie
+ **/
+
+class Pie {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ const w = this.w
+
+ this.chartType = this.w.config.chart.type
+
+ this.initialAnim = this.w.config.chart.animations.enabled
+ this.dynamicAnim =
+ this.initialAnim &&
+ this.w.config.chart.animations.dynamicAnimation.enabled
+
+ this.animBeginArr = [0]
+ this.animDur = 0
+
+ this.donutDataLabels = this.w.config.plotOptions.pie.donut.labels
+
+ this.lineColorArr =
+ w.globals.stroke.colors !== undefined
+ ? w.globals.stroke.colors
+ : w.globals.colors
+
+ this.defaultSize = Math.min(w.globals.gridWidth, w.globals.gridHeight)
+
+ this.centerY = this.defaultSize / 2
+ this.centerX = w.globals.gridWidth / 2
+
+ if (w.config.chart.type === 'radialBar') {
+ this.fullAngle = 360
+ } else {
+ this.fullAngle = Math.abs(
+ w.config.plotOptions.pie.endAngle - w.config.plotOptions.pie.startAngle
+ )
+ }
+ this.initialAngle = w.config.plotOptions.pie.startAngle % this.fullAngle
+
+ w.globals.radialSize =
+ this.defaultSize / 2.05 -
+ w.config.stroke.width -
+ (!w.config.chart.sparkline.enabled ? w.config.chart.dropShadow.blur : 0)
+
+ this.donutSize =
+ (w.globals.radialSize *
+ parseInt(w.config.plotOptions.pie.donut.size, 10)) /
+ 100
+
+ let scaleSize = w.config.plotOptions.pie.customScale
+ let halfW = w.globals.gridWidth / 2
+ let halfH = w.globals.gridHeight / 2
+ this.translateX = halfW - halfW * scaleSize
+ this.translateY = halfH - halfH * scaleSize
+
+ this.dataLabelsGroup = new Graphics(this.ctx).group({
+ class: 'apexcharts-datalabels-group',
+ transform: `translate(${this.translateX}, ${this.translateY}) scale(${scaleSize})`,
+ })
+
+ this.maxY = 0
+ this.sliceLabels = []
+ this.sliceSizes = []
+
+ this.prevSectorAngleArr = [] // for dynamic animations
+ }
+
+ draw(series) {
+ let self = this
+ let w = this.w
+
+ const graphics = new Graphics(this.ctx)
+
+ let elPie = graphics.group({
+ class: 'apexcharts-pie',
+ })
+
+ if (w.globals.noData) return elPie
+
+ let total = 0
+ for (let k = 0; k < series.length; k++) {
+ // CALCULATE THE TOTAL
+ total += Utils.negToZero(series[k])
+ }
+
+ let sectorAngleArr = []
+
+ // el to which series will be drawn
+ let elSeries = graphics.group()
+
+ // prevent division by zero error if there is no data
+ if (total === 0) {
+ total = 0.00001
+ }
+
+ series.forEach((m) => {
+ this.maxY = Math.max(this.maxY, m)
+ })
+
+ // override maxY if user provided in config
+ if (w.config.yaxis[0].max) {
+ this.maxY = w.config.yaxis[0].max
+ }
+
+ if (w.config.grid.position === 'back' && this.chartType === 'polarArea') {
+ this.drawPolarElements(elPie)
+ }
+
+ for (let i = 0; i < series.length; i++) {
+ // CALCULATE THE ANGLES
+ let angle = (this.fullAngle * Utils.negToZero(series[i])) / total
+ sectorAngleArr.push(angle)
+
+ if (this.chartType === 'polarArea') {
+ sectorAngleArr[i] = this.fullAngle / series.length
+ this.sliceSizes.push((w.globals.radialSize * series[i]) / this.maxY)
+ } else {
+ this.sliceSizes.push(w.globals.radialSize)
+ }
+ }
+
+ if (w.globals.dataChanged) {
+ let prevTotal = 0
+ for (let k = 0; k < w.globals.previousPaths.length; k++) {
+ // CALCULATE THE PREV TOTAL
+ prevTotal += Utils.negToZero(w.globals.previousPaths[k])
+ }
+
+ let previousAngle
+
+ for (let i = 0; i < w.globals.previousPaths.length; i++) {
+ // CALCULATE THE PREVIOUS ANGLES
+ previousAngle =
+ (this.fullAngle * Utils.negToZero(w.globals.previousPaths[i])) /
+ prevTotal
+ this.prevSectorAngleArr.push(previousAngle)
+ }
+ }
+
+ // on small chart size after few count of resizes browser window donutSize can be negative
+ if (this.donutSize < 0) {
+ this.donutSize = 0
+ }
+
+ if (this.chartType === 'donut') {
+ // draw the inner circle and add some text to it
+ const circle = graphics.drawCircle(this.donutSize)
+
+ circle.attr({
+ cx: this.centerX,
+ cy: this.centerY,
+ fill: w.config.plotOptions.pie.donut.background
+ ? w.config.plotOptions.pie.donut.background
+ : 'transparent',
+ })
+
+ elSeries.add(circle)
+ }
+
+ let elG = self.drawArcs(sectorAngleArr, series)
+
+ // add slice dataLabels at the end
+ this.sliceLabels.forEach((s) => {
+ elG.add(s)
+ })
+
+ elSeries.attr({
+ transform: `translate(${this.translateX}, ${this.translateY}) scale(${w.config.plotOptions.pie.customScale})`,
+ })
+
+ elSeries.add(elG)
+
+ elPie.add(elSeries)
+
+ if (this.donutDataLabels.show) {
+ let dataLabels = this.renderInnerDataLabels(
+ this.dataLabelsGroup,
+ this.donutDataLabels,
+ {
+ hollowSize: this.donutSize,
+ centerX: this.centerX,
+ centerY: this.centerY,
+ opacity: this.donutDataLabels.show,
+ }
+ )
+
+ elPie.add(dataLabels)
+ }
+
+ if (w.config.grid.position === 'front' && this.chartType === 'polarArea') {
+ this.drawPolarElements(elPie)
+ }
+
+ return elPie
+ }
+
+ // core function for drawing pie arcs
+ drawArcs(sectorAngleArr, series) {
+ let w = this.w
+ const filters = new Filters(this.ctx)
+
+ let graphics = new Graphics(this.ctx)
+ let fill = new Fill(this.ctx)
+ let g = graphics.group({
+ class: 'apexcharts-slices',
+ })
+
+ let startAngle = this.initialAngle
+ let prevStartAngle = this.initialAngle
+ let endAngle = this.initialAngle
+ let prevEndAngle = this.initialAngle
+
+ this.strokeWidth = w.config.stroke.show ? w.config.stroke.width : 0
+
+ for (let i = 0; i < sectorAngleArr.length; i++) {
+ let elPieArc = graphics.group({
+ class: `apexcharts-series apexcharts-pie-series`,
+ seriesName: Utils.escapeString(w.globals.seriesNames[i]),
+ rel: i + 1,
+ 'data:realIndex': i,
+ })
+
+ g.add(elPieArc)
+
+ startAngle = endAngle
+ prevStartAngle = prevEndAngle
+
+ endAngle = startAngle + sectorAngleArr[i]
+ prevEndAngle = prevStartAngle + this.prevSectorAngleArr[i]
+
+ const angle =
+ endAngle < startAngle
+ ? this.fullAngle + endAngle - startAngle
+ : endAngle - startAngle
+
+ let pathFill = fill.fillPath({
+ seriesNumber: i,
+ size: this.sliceSizes[i],
+ value: series[i],
+ }) // additionally, pass size for gradient drawing in the fillPath function
+
+ let path = this.getChangedPath(prevStartAngle, prevEndAngle)
+
+ let elPath = graphics.drawPath({
+ d: path,
+ stroke: Array.isArray(this.lineColorArr)
+ ? this.lineColorArr[i]
+ : this.lineColorArr,
+ strokeWidth: 0,
+ fill: pathFill,
+ fillOpacity: w.config.fill.opacity,
+ classes: `apexcharts-pie-area apexcharts-${this.chartType.toLowerCase()}-slice-${i}`,
+ })
+
+ elPath.attr({
+ index: 0,
+ j: i,
+ })
+
+ filters.setSelectionFilter(elPath, 0, i)
+
+ if (w.config.chart.dropShadow.enabled) {
+ const shadow = w.config.chart.dropShadow
+ filters.dropShadow(elPath, shadow, i)
+ }
+
+ this.addListeners(elPath, this.donutDataLabels)
+
+ Graphics.setAttrs(elPath.node, {
+ 'data:angle': angle,
+ 'data:startAngle': startAngle,
+ 'data:strokeWidth': this.strokeWidth,
+ 'data:value': series[i],
+ })
+
+ let labelPosition = {
+ x: 0,
+ y: 0,
+ }
+
+ if (this.chartType === 'pie' || this.chartType === 'polarArea') {
+ labelPosition = Utils.polarToCartesian(
+ this.centerX,
+ this.centerY,
+ w.globals.radialSize / 1.25 +
+ w.config.plotOptions.pie.dataLabels.offset,
+ (startAngle + angle / 2) % this.fullAngle
+ )
+ } else if (this.chartType === 'donut') {
+ labelPosition = Utils.polarToCartesian(
+ this.centerX,
+ this.centerY,
+ (w.globals.radialSize + this.donutSize) / 2 +
+ w.config.plotOptions.pie.dataLabels.offset,
+ (startAngle + angle / 2) % this.fullAngle
+ )
+ }
+
+ elPieArc.add(elPath)
+
+ // Animation code starts
+ let dur = 0
+ if (this.initialAnim && !w.globals.resized && !w.globals.dataChanged) {
+ dur = (angle / this.fullAngle) * w.config.chart.animations.speed
+
+ if (dur === 0) dur = 1
+ this.animDur = dur + this.animDur
+ this.animBeginArr.push(this.animDur)
+ } else {
+ this.animBeginArr.push(0)
+ }
+
+ if (this.dynamicAnim && w.globals.dataChanged) {
+ this.animatePaths(elPath, {
+ size: this.sliceSizes[i],
+ endAngle,
+ startAngle,
+ prevStartAngle,
+ prevEndAngle,
+ animateStartingPos: true,
+ i,
+ animBeginArr: this.animBeginArr,
+ shouldSetPrevPaths: true,
+ dur: w.config.chart.animations.dynamicAnimation.speed,
+ })
+ } else {
+ this.animatePaths(elPath, {
+ size: this.sliceSizes[i],
+ endAngle,
+ startAngle,
+ i,
+ totalItems: sectorAngleArr.length - 1,
+ animBeginArr: this.animBeginArr,
+ dur,
+ })
+ }
+ // animation code ends
+
+ if (
+ w.config.plotOptions.pie.expandOnClick &&
+ this.chartType !== 'polarArea'
+ ) {
+ elPath.node.addEventListener('mouseup', this.pieClicked.bind(this, i))
+ }
+
+ if (
+ typeof w.globals.selectedDataPoints[0] !== 'undefined' &&
+ w.globals.selectedDataPoints[0].indexOf(i) > -1
+ ) {
+ this.pieClicked(i)
+ }
+
+ if (w.config.dataLabels.enabled) {
+ let xPos = labelPosition.x
+ let yPos = labelPosition.y
+ let text = (100 * angle) / this.fullAngle + '%'
+
+ if (
+ angle !== 0 &&
+ w.config.plotOptions.pie.dataLabels.minAngleToShowLabel <
+ sectorAngleArr[i]
+ ) {
+ let formatter = w.config.dataLabels.formatter
+ if (formatter !== undefined) {
+ text = formatter(w.globals.seriesPercent[i][0], {
+ seriesIndex: i,
+ w,
+ })
+ }
+ let foreColor = w.globals.dataLabels.style.colors[i]
+
+ const elPieLabelWrap = graphics.group({
+ class: `apexcharts-datalabels`,
+ })
+ let elPieLabel = graphics.drawText({
+ x: xPos,
+ y: yPos,
+ text,
+ textAnchor: 'middle',
+ fontSize: w.config.dataLabels.style.fontSize,
+ fontFamily: w.config.dataLabels.style.fontFamily,
+ fontWeight: w.config.dataLabels.style.fontWeight,
+ foreColor,
+ })
+
+ elPieLabelWrap.add(elPieLabel)
+ if (w.config.dataLabels.dropShadow.enabled) {
+ const textShadow = w.config.dataLabels.dropShadow
+ filters.dropShadow(elPieLabel, textShadow)
+ }
+
+ elPieLabel.node.classList.add('apexcharts-pie-label')
+ if (
+ w.config.chart.animations.animate &&
+ w.globals.resized === false
+ ) {
+ elPieLabel.node.classList.add('apexcharts-pie-label-delay')
+ elPieLabel.node.style.animationDelay =
+ w.config.chart.animations.speed / 940 + 's'
+ }
+
+ this.sliceLabels.push(elPieLabelWrap)
+ }
+ }
+ }
+
+ return g
+ }
+
+ addListeners(elPath, dataLabels) {
+ const graphics = new Graphics(this.ctx)
+ // append filters on mouseenter and mouseleave
+ elPath.node.addEventListener(
+ 'mouseenter',
+ graphics.pathMouseEnter.bind(this, elPath)
+ )
+
+ elPath.node.addEventListener(
+ 'mouseleave',
+ graphics.pathMouseLeave.bind(this, elPath)
+ )
+ elPath.node.addEventListener(
+ 'mouseleave',
+ this.revertDataLabelsInner.bind(this, elPath.node, dataLabels)
+ )
+ elPath.node.addEventListener(
+ 'mousedown',
+ graphics.pathMouseDown.bind(this, elPath)
+ )
+
+ if (!this.donutDataLabels.total.showAlways) {
+ elPath.node.addEventListener(
+ 'mouseenter',
+ this.printDataLabelsInner.bind(this, elPath.node, dataLabels)
+ )
+
+ elPath.node.addEventListener(
+ 'mousedown',
+ this.printDataLabelsInner.bind(this, elPath.node, dataLabels)
+ )
+ }
+ }
+
+ // This function can be used for other circle charts too
+ animatePaths(el, opts) {
+ let w = this.w
+ let me = this
+
+ let angle =
+ opts.endAngle < opts.startAngle
+ ? this.fullAngle + opts.endAngle - opts.startAngle
+ : opts.endAngle - opts.startAngle
+ let prevAngle = angle
+
+ let fromStartAngle = opts.startAngle
+ let toStartAngle = opts.startAngle
+
+ if (opts.prevStartAngle !== undefined && opts.prevEndAngle !== undefined) {
+ fromStartAngle = opts.prevEndAngle
+ prevAngle =
+ opts.prevEndAngle < opts.prevStartAngle
+ ? this.fullAngle + opts.prevEndAngle - opts.prevStartAngle
+ : opts.prevEndAngle - opts.prevStartAngle
+ }
+ if (opts.i === w.config.series.length - 1) {
+ // some adjustments for the last overlapping paths
+ if (angle + toStartAngle > this.fullAngle) {
+ opts.endAngle = opts.endAngle - (angle + toStartAngle)
+ } else if (angle + toStartAngle < this.fullAngle) {
+ opts.endAngle =
+ opts.endAngle + (this.fullAngle - (angle + toStartAngle))
+ }
+ }
+
+ if (angle === this.fullAngle) angle = this.fullAngle - 0.01
+
+ me.animateArc(el, fromStartAngle, toStartAngle, angle, prevAngle, opts)
+ }
+
+ animateArc(el, fromStartAngle, toStartAngle, angle, prevAngle, opts) {
+ let me = this
+ const w = this.w
+ const animations = new Animations(this.ctx)
+
+ let size = opts.size
+
+ let path
+
+ if (isNaN(fromStartAngle) || isNaN(prevAngle)) {
+ fromStartAngle = toStartAngle
+ prevAngle = angle
+ opts.dur = 0
+ }
+
+ let currAngle = angle
+ let startAngle = toStartAngle
+ let fromAngle =
+ fromStartAngle < toStartAngle
+ ? this.fullAngle + fromStartAngle - toStartAngle
+ : fromStartAngle - toStartAngle
+
+ if (w.globals.dataChanged && opts.shouldSetPrevPaths) {
+ // to avoid flicker when updating, set prev path first and then animate from there
+ if (opts.prevEndAngle) {
+ path = me.getPiePath({
+ me,
+ startAngle: opts.prevStartAngle,
+ angle:
+ opts.prevEndAngle < opts.prevStartAngle
+ ? this.fullAngle + opts.prevEndAngle - opts.prevStartAngle
+ : opts.prevEndAngle - opts.prevStartAngle,
+ size,
+ })
+ el.attr({ d: path })
+ }
+ }
+
+ if (opts.dur !== 0) {
+ el.animate(opts.dur, w.globals.easing, opts.animBeginArr[opts.i])
+ .afterAll(function () {
+ if (
+ me.chartType === 'pie' ||
+ me.chartType === 'donut' ||
+ me.chartType === 'polarArea'
+ ) {
+ this.animate(w.config.chart.animations.dynamicAnimation.speed).attr(
+ {
+ 'stroke-width': me.strokeWidth,
+ }
+ )
+ }
+
+ if (opts.i === w.config.series.length - 1) {
+ animations.animationCompleted(el)
+ }
+ })
+ .during((pos) => {
+ currAngle = fromAngle + (angle - fromAngle) * pos
+ if (opts.animateStartingPos) {
+ currAngle = prevAngle + (angle - prevAngle) * pos
+ startAngle =
+ fromStartAngle -
+ prevAngle +
+ (toStartAngle - (fromStartAngle - prevAngle)) * pos
+ }
+
+ path = me.getPiePath({
+ me,
+ startAngle,
+ angle: currAngle,
+ size,
+ })
+
+ el.node.setAttribute('data:pathOrig', path)
+
+ el.attr({
+ d: path,
+ })
+ })
+ } else {
+ path = me.getPiePath({
+ me,
+ startAngle,
+ angle,
+ size,
+ })
+
+ if (!opts.isTrack) {
+ w.globals.animationEnded = true
+ }
+ el.node.setAttribute('data:pathOrig', path)
+
+ el.attr({
+ d: path,
+ 'stroke-width': me.strokeWidth,
+ })
+ }
+ }
+
+ pieClicked(i) {
+ let w = this.w
+ let me = this
+ let path
+
+ let size =
+ me.sliceSizes[i] + (w.config.plotOptions.pie.expandOnClick ? 4 : 0)
+ let elPath = w.globals.dom.Paper.select(
+ `.apexcharts-${me.chartType.toLowerCase()}-slice-${i}`
+ ).members[0]
+
+ if (elPath.attr('data:pieClicked') === 'true') {
+ elPath.attr({
+ 'data:pieClicked': 'false',
+ })
+ this.revertDataLabelsInner(elPath.node, this.donutDataLabels)
+
+ let origPath = elPath.attr('data:pathOrig')
+ elPath.attr({
+ d: origPath,
+ })
+ return
+ } else {
+ // reset all elems
+ let allEls = w.globals.dom.baseEl.getElementsByClassName(
+ 'apexcharts-pie-area'
+ )
+ Array.prototype.forEach.call(allEls, (pieSlice) => {
+ pieSlice.setAttribute('data:pieClicked', 'false')
+ let origPath = pieSlice.getAttribute('data:pathOrig')
+ if (origPath) {
+ pieSlice.setAttribute('d', origPath)
+ }
+ })
+ w.globals.capturedDataPointIndex = i
+
+ elPath.attr('data:pieClicked', 'true')
+ }
+
+ let startAngle = parseInt(elPath.attr('data:startAngle'), 10)
+ let angle = parseInt(elPath.attr('data:angle'), 10)
+
+ path = me.getPiePath({
+ me,
+ startAngle,
+ angle,
+ size,
+ })
+
+ if (angle === 360) return
+
+ elPath.plot(path)
+ }
+
+ getChangedPath(prevStartAngle, prevEndAngle) {
+ let path = ''
+ if (this.dynamicAnim && this.w.globals.dataChanged) {
+ path = this.getPiePath({
+ me: this,
+ startAngle: prevStartAngle,
+ angle: prevEndAngle - prevStartAngle,
+ size: this.size,
+ })
+ }
+ return path
+ }
+
+ getPiePath({ me, startAngle, angle, size }) {
+ let path
+ const graphics = new Graphics(this.ctx)
+
+ let startDeg = startAngle
+ let startRadians = (Math.PI * (startDeg - 90)) / 180
+
+ let endDeg = angle + startAngle
+ // prevent overlap
+ if (
+ Math.ceil(endDeg) >=
+ this.fullAngle +
+ (this.w.config.plotOptions.pie.startAngle % this.fullAngle)
+ ) {
+ endDeg =
+ this.fullAngle +
+ (this.w.config.plotOptions.pie.startAngle % this.fullAngle) -
+ 0.01
+ }
+ if (Math.ceil(endDeg) > this.fullAngle) endDeg -= this.fullAngle
+
+ let endRadians = (Math.PI * (endDeg - 90)) / 180
+
+ let x1 = me.centerX + size * Math.cos(startRadians)
+ let y1 = me.centerY + size * Math.sin(startRadians)
+ let x2 = me.centerX + size * Math.cos(endRadians)
+ let y2 = me.centerY + size * Math.sin(endRadians)
+
+ let startInner = Utils.polarToCartesian(
+ me.centerX,
+ me.centerY,
+ me.donutSize,
+ endDeg
+ )
+ let endInner = Utils.polarToCartesian(
+ me.centerX,
+ me.centerY,
+ me.donutSize,
+ startDeg
+ )
+
+ let largeArc = angle > 180 ? 1 : 0
+
+ const pathBeginning = ['M', x1, y1, 'A', size, size, 0, largeArc, 1, x2, y2]
+
+ if (me.chartType === 'donut') {
+ path = [
+ ...pathBeginning,
+ 'L',
+ startInner.x,
+ startInner.y,
+ 'A',
+ me.donutSize,
+ me.donutSize,
+ 0,
+ largeArc,
+ 0,
+ endInner.x,
+ endInner.y,
+ 'L',
+ x1,
+ y1,
+ 'z',
+ ].join(' ')
+ } else if (me.chartType === 'pie' || me.chartType === 'polarArea') {
+ path = [...pathBeginning, 'L', me.centerX, me.centerY, 'L', x1, y1].join(
+ ' '
+ )
+ } else {
+ path = [...pathBeginning].join(' ')
+ }
+
+ return graphics.roundPathCorners(path, this.strokeWidth * 2)
+ }
+
+ drawPolarElements(parent) {
+ const w = this.w
+ const scale = new Scales(this.ctx)
+ const graphics = new Graphics(this.ctx)
+ const helpers = new Helpers(this.ctx)
+
+ const gCircles = graphics.group()
+ const gYAxis = graphics.group()
+
+ const yScale = scale.niceScale(0, Math.ceil(this.maxY), 0)
+
+ const yTexts = yScale.result.reverse()
+ let len = yScale.result.length
+
+ this.maxY = yScale.niceMax
+
+ let circleSize = w.globals.radialSize
+ let diff = circleSize / (len - 1)
+
+ for (let i = 0; i < len - 1; i++) {
+ const circle = graphics.drawCircle(circleSize)
+
+ circle.attr({
+ cx: this.centerX,
+ cy: this.centerY,
+ fill: 'none',
+ 'stroke-width': w.config.plotOptions.polarArea.rings.strokeWidth,
+ stroke: w.config.plotOptions.polarArea.rings.strokeColor,
+ })
+
+ if (w.config.yaxis[0].show) {
+ const yLabel = helpers.drawYAxisTexts(
+ this.centerX,
+ this.centerY -
+ circleSize +
+ parseInt(w.config.yaxis[0].labels.style.fontSize, 10) / 2,
+ i,
+ yTexts[i]
+ )
+
+ gYAxis.add(yLabel)
+ }
+
+ gCircles.add(circle)
+
+ circleSize = circleSize - diff
+ }
+
+ this.drawSpokes(parent)
+
+ parent.add(gCircles)
+ parent.add(gYAxis)
+ }
+
+ renderInnerDataLabels(dataLabelsGroup, dataLabelsConfig, opts) {
+ let w = this.w
+ const graphics = new Graphics(this.ctx)
+
+ const showTotal = dataLabelsConfig.total.show
+
+ dataLabelsGroup.node.innerHTML = ''
+ dataLabelsGroup.node.style.opacity = opts.opacity
+
+ let x = opts.centerX
+ let y = !this.donutDataLabels.total.label
+ ? opts.centerY - opts.centerY / 6
+ : opts.centerY
+
+ let labelColor, valueColor
+
+ if (dataLabelsConfig.name.color === undefined) {
+ labelColor = w.globals.colors[0]
+ } else {
+ labelColor = dataLabelsConfig.name.color
+ }
+ let labelFontSize = dataLabelsConfig.name.fontSize
+ let labelFontFamily = dataLabelsConfig.name.fontFamily
+ let labelFontWeight = dataLabelsConfig.name.fontWeight
+
+ if (dataLabelsConfig.value.color === undefined) {
+ valueColor = w.config.chart.foreColor
+ } else {
+ valueColor = dataLabelsConfig.value.color
+ }
+
+ let lbFormatter = dataLabelsConfig.value.formatter
+ let val = ''
+ let name = ''
+
+ if (showTotal) {
+ labelColor = dataLabelsConfig.total.color
+ labelFontSize = dataLabelsConfig.total.fontSize
+ labelFontFamily = dataLabelsConfig.total.fontFamily
+ labelFontWeight = dataLabelsConfig.total.fontWeight
+ name = !this.donutDataLabels.total.label
+ ? ''
+ : dataLabelsConfig.total.label
+ val = dataLabelsConfig.total.formatter(w)
+ } else {
+ if (w.globals.series.length === 1) {
+ val = lbFormatter(w.globals.series[0], w)
+ name = w.globals.seriesNames[0]
+ }
+ }
+
+ if (name) {
+ name = dataLabelsConfig.name.formatter(
+ name,
+ dataLabelsConfig.total.show,
+ w
+ )
+ }
+
+ if (dataLabelsConfig.name.show) {
+ let elLabel = graphics.drawText({
+ x,
+ y: y + parseFloat(dataLabelsConfig.name.offsetY),
+ text: name,
+ textAnchor: 'middle',
+ foreColor: labelColor,
+ fontSize: labelFontSize,
+ fontWeight: labelFontWeight,
+ fontFamily: labelFontFamily,
+ })
+ elLabel.node.classList.add('apexcharts-datalabel-label')
+ dataLabelsGroup.add(elLabel)
+ }
+
+ if (dataLabelsConfig.value.show) {
+ let valOffset = dataLabelsConfig.name.show
+ ? parseFloat(dataLabelsConfig.value.offsetY) + 16
+ : dataLabelsConfig.value.offsetY
+
+ let elValue = graphics.drawText({
+ x,
+ y: y + valOffset,
+ text: val,
+ textAnchor: 'middle',
+ foreColor: valueColor,
+ fontWeight: dataLabelsConfig.value.fontWeight,
+ fontSize: dataLabelsConfig.value.fontSize,
+ fontFamily: dataLabelsConfig.value.fontFamily,
+ })
+ elValue.node.classList.add('apexcharts-datalabel-value')
+ dataLabelsGroup.add(elValue)
+ }
+
+ // for a multi-series circle chart, we need to show total value instead of first series labels
+
+ return dataLabelsGroup
+ }
+
+ /**
+ *
+ * @param {string} name - The name of the series
+ * @param {string} val - The value of that series
+ * @param {object} el - Optional el (indicates which series was hovered/clicked). If this param is not present, means we need to show total
+ */
+ printInnerLabels(labelsConfig, name, val, el) {
+ const w = this.w
+
+ let labelColor
+
+ if (el) {
+ if (labelsConfig.name.color === undefined) {
+ labelColor =
+ w.globals.colors[parseInt(el.parentNode.getAttribute('rel'), 10) - 1]
+ } else {
+ labelColor = labelsConfig.name.color
+ }
+ } else {
+ if (w.globals.series.length > 1 && labelsConfig.total.show) {
+ labelColor = labelsConfig.total.color
+ }
+ }
+
+ let elLabel = w.globals.dom.baseEl.querySelector(
+ '.apexcharts-datalabel-label'
+ )
+ let elValue = w.globals.dom.baseEl.querySelector(
+ '.apexcharts-datalabel-value'
+ )
+
+ let lbFormatter = labelsConfig.value.formatter
+ val = lbFormatter(val, w)
+
+ // we need to show Total Val - so get the formatter of it
+ if (!el && typeof labelsConfig.total.formatter === 'function') {
+ val = labelsConfig.total.formatter(w)
+ }
+
+ const isTotal = name === labelsConfig.total.label
+ name = !this.donutDataLabels.total.label
+ ? ''
+ : labelsConfig.name.formatter(name, isTotal, w)
+
+ if (elLabel !== null) {
+ elLabel.textContent = name
+ }
+
+ if (elValue !== null) {
+ elValue.textContent = val
+ }
+ if (elLabel !== null) {
+ elLabel.style.fill = labelColor
+ }
+ }
+
+ printDataLabelsInner(el, dataLabelsConfig) {
+ let w = this.w
+
+ let val = el.getAttribute('data:value')
+ let name =
+ w.globals.seriesNames[parseInt(el.parentNode.getAttribute('rel'), 10) - 1]
+
+ if (w.globals.series.length > 1) {
+ this.printInnerLabels(dataLabelsConfig, name, val, el)
+ }
+
+ let dataLabelsGroup = w.globals.dom.baseEl.querySelector(
+ '.apexcharts-datalabels-group'
+ )
+ if (dataLabelsGroup !== null) {
+ dataLabelsGroup.style.opacity = 1
+ }
+ }
+
+ drawSpokes(parent) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+ const spokeConfig = w.config.plotOptions.polarArea.spokes
+
+ if (spokeConfig.strokeWidth === 0) return
+
+ let spokes = []
+
+ let angleDivision = 360 / w.globals.series.length
+ for (let i = 0; i < w.globals.series.length; i++) {
+ spokes.push(
+ Utils.polarToCartesian(
+ this.centerX,
+ this.centerY,
+ w.globals.radialSize,
+ w.config.plotOptions.pie.startAngle + angleDivision * i
+ )
+ )
+ }
+
+ spokes.forEach((p, i) => {
+ const line = graphics.drawLine(
+ p.x,
+ p.y,
+ this.centerX,
+ this.centerY,
+ Array.isArray(spokeConfig.connectorColors)
+ ? spokeConfig.connectorColors[i]
+ : spokeConfig.connectorColors
+ )
+
+ parent.add(line)
+ })
+ }
+
+ revertDataLabelsInner() {
+ const w = this.w
+ if (this.donutDataLabels.show) {
+ let dataLabelsGroup = w.globals.dom.Paper.select(
+ `.apexcharts-datalabels-group`
+ ).members[0]
+
+ let dataLabels = this.renderInnerDataLabels(
+ dataLabelsGroup,
+ this.donutDataLabels,
+ {
+ hollowSize: this.donutSize,
+ centerX: this.centerX,
+ centerY: this.centerY,
+ opacity: this.donutDataLabels.show,
+ }
+ )
+
+ let elPie = w.globals.dom.Paper.select(
+ '.apexcharts-radialbar, .apexcharts-pie'
+ ).members[0]
+ elPie.add(dataLabels)
+ }
+ }
+}
+
+export default Pie
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Radar.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Radar.js
new file mode 100644
index 0000000..6afb01b
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Radar.js
@@ -0,0 +1,536 @@
+import Fill from '../modules/Fill'
+import Graphics from '../modules/Graphics'
+import Markers from '../modules/Markers'
+import DataLabels from '../modules/DataLabels'
+import Filters from '../modules/Filters'
+import Utils from '../utils/Utils'
+import Helpers from './common/circle/Helpers'
+import CoreUtils from '../modules/CoreUtils'
+
+/**
+ * ApexCharts Radar Class for Spider/Radar Charts.
+ * @module Radar
+ **/
+
+class Radar {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.chartType = this.w.config.chart.type
+
+ this.initialAnim = this.w.config.chart.animations.enabled
+ this.dynamicAnim =
+ this.initialAnim &&
+ this.w.config.chart.animations.dynamicAnimation.enabled
+
+ this.animDur = 0
+
+ const w = this.w
+ this.graphics = new Graphics(this.ctx)
+
+ this.lineColorArr =
+ w.globals.stroke.colors !== undefined
+ ? w.globals.stroke.colors
+ : w.globals.colors
+
+ this.defaultSize =
+ w.globals.svgHeight < w.globals.svgWidth
+ ? w.globals.gridHeight
+ : w.globals.gridWidth
+
+ this.isLog = w.config.yaxis[0].logarithmic
+ this.logBase = w.config.yaxis[0].logBase
+
+ this.coreUtils = new CoreUtils(this.ctx)
+ this.maxValue = this.isLog
+ ? this.coreUtils.getLogVal(this.logBase, w.globals.maxY, 0)
+ : w.globals.maxY
+ this.minValue = this.isLog
+ ? this.coreUtils.getLogVal(this.logBase, this.w.globals.minY, 0)
+ : w.globals.minY
+
+ this.polygons = w.config.plotOptions.radar.polygons
+
+ this.strokeWidth = w.config.stroke.show ? w.config.stroke.width : 0
+
+ this.size =
+ this.defaultSize / 2.1 - this.strokeWidth - w.config.chart.dropShadow.blur
+
+ if (w.config.xaxis.labels.show) {
+ this.size = this.size - w.globals.xAxisLabelsWidth / 1.75
+ }
+
+ if (w.config.plotOptions.radar.size !== undefined) {
+ this.size = w.config.plotOptions.radar.size
+ }
+
+ this.dataRadiusOfPercent = []
+ this.dataRadius = []
+ this.angleArr = []
+
+ this.yaxisLabelsTextsPos = []
+ }
+
+ draw(series) {
+ let w = this.w
+ const fill = new Fill(this.ctx)
+
+ const allSeries = []
+ const dataLabels = new DataLabels(this.ctx)
+
+ if (series.length) {
+ this.dataPointsLen = series[w.globals.maxValsInArrayIndex].length
+ }
+ this.disAngle = (Math.PI * 2) / this.dataPointsLen
+
+ let halfW = w.globals.gridWidth / 2
+ let halfH = w.globals.gridHeight / 2
+ let translateX = halfW + w.config.plotOptions.radar.offsetX
+ let translateY = halfH + w.config.plotOptions.radar.offsetY
+
+ let ret = this.graphics.group({
+ class: 'apexcharts-radar-series apexcharts-plot-series',
+ transform: `translate(${translateX || 0}, ${translateY || 0})`,
+ })
+
+ let dataPointsPos = []
+ let elPointsMain = null
+ let elDataPointsMain = null
+
+ this.yaxisLabels = this.graphics.group({
+ class: 'apexcharts-yaxis',
+ })
+
+ series.forEach((s, i) => {
+ let longestSeries = s.length === w.globals.dataPoints
+
+ // el to which series will be drawn
+ let elSeries = this.graphics.group().attr({
+ class: `apexcharts-series`,
+ 'data:longestSeries': longestSeries,
+ seriesName: Utils.escapeString(w.globals.seriesNames[i]),
+ rel: i + 1,
+ 'data:realIndex': i,
+ })
+
+ this.dataRadiusOfPercent[i] = []
+ this.dataRadius[i] = []
+ this.angleArr[i] = []
+
+ s.forEach((dv, j) => {
+ const range = Math.abs(this.maxValue - this.minValue)
+ dv = dv - this.minValue
+
+ if (this.isLog) {
+ dv = this.coreUtils.getLogVal(this.logBase, dv, 0)
+ }
+
+ this.dataRadiusOfPercent[i][j] = dv / range
+
+ this.dataRadius[i][j] = this.dataRadiusOfPercent[i][j] * this.size
+ this.angleArr[i][j] = j * this.disAngle
+ })
+
+ dataPointsPos = this.getDataPointsPos(
+ this.dataRadius[i],
+ this.angleArr[i]
+ )
+ const paths = this.createPaths(dataPointsPos, {
+ x: 0,
+ y: 0,
+ })
+
+ // points
+ elPointsMain = this.graphics.group({
+ class: 'apexcharts-series-markers-wrap apexcharts-element-hidden',
+ })
+
+ // datapoints
+ elDataPointsMain = this.graphics.group({
+ class: `apexcharts-datalabels`,
+ 'data:realIndex': i,
+ })
+
+ w.globals.delayedElements.push({
+ el: elPointsMain.node,
+ index: i,
+ })
+
+ const defaultRenderedPathOptions = {
+ i,
+ realIndex: i,
+ animationDelay: i,
+ initialSpeed: w.config.chart.animations.speed,
+ dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed,
+ className: `apexcharts-radar`,
+ shouldClipToGrid: false,
+ bindEventsOnPaths: false,
+ stroke: w.globals.stroke.colors[i],
+ strokeLineCap: w.config.stroke.lineCap,
+ }
+
+ let pathFrom = null
+
+ if (w.globals.previousPaths.length > 0) {
+ pathFrom = this.getPreviousPath(i)
+ }
+
+ for (let p = 0; p < paths.linePathsTo.length; p++) {
+ let renderedLinePath = this.graphics.renderPaths({
+ ...defaultRenderedPathOptions,
+ pathFrom: pathFrom === null ? paths.linePathsFrom[p] : pathFrom,
+ pathTo: paths.linePathsTo[p],
+ strokeWidth: Array.isArray(this.strokeWidth)
+ ? this.strokeWidth[i]
+ : this.strokeWidth,
+ fill: 'none',
+ drawShadow: false,
+ })
+
+ elSeries.add(renderedLinePath)
+
+ let pathFill = fill.fillPath({
+ seriesNumber: i,
+ })
+
+ let renderedAreaPath = this.graphics.renderPaths({
+ ...defaultRenderedPathOptions,
+ pathFrom: pathFrom === null ? paths.areaPathsFrom[p] : pathFrom,
+ pathTo: paths.areaPathsTo[p],
+ strokeWidth: 0,
+ fill: pathFill,
+ drawShadow: false,
+ })
+
+ if (w.config.chart.dropShadow.enabled) {
+ const filters = new Filters(this.ctx)
+
+ const shadow = w.config.chart.dropShadow
+ filters.dropShadow(
+ renderedAreaPath,
+ Object.assign({}, shadow, { noUserSpaceOnUse: true }),
+ i
+ )
+ }
+
+ elSeries.add(renderedAreaPath)
+ }
+
+ s.forEach((sj, j) => {
+ let markers = new Markers(this.ctx)
+
+ let opts = markers.getMarkerConfig({
+ cssClass: 'apexcharts-marker',
+ seriesIndex: i,
+ dataPointIndex: j,
+ })
+
+ let point = this.graphics.drawMarker(
+ dataPointsPos[j].x,
+ dataPointsPos[j].y,
+ opts
+ )
+
+ point.attr('rel', j)
+ point.attr('j', j)
+ point.attr('index', i)
+ point.node.setAttribute('default-marker-size', opts.pSize)
+
+ let elPointsWrap = this.graphics.group({
+ class: 'apexcharts-series-markers',
+ })
+
+ if (elPointsWrap) {
+ elPointsWrap.add(point)
+ }
+
+ elPointsMain.add(elPointsWrap)
+
+ elSeries.add(elPointsMain)
+
+ const dataLabelsConfig = w.config.dataLabels
+
+ if (dataLabelsConfig.enabled) {
+ let text = dataLabelsConfig.formatter(w.globals.series[i][j], {
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ })
+
+ dataLabels.plotDataLabelsText({
+ x: dataPointsPos[j].x,
+ y: dataPointsPos[j].y,
+ text,
+ textAnchor: 'middle',
+ i,
+ j: i,
+ parent: elDataPointsMain,
+ offsetCorrection: false,
+ dataLabelsConfig: {
+ ...dataLabelsConfig,
+ },
+ })
+ }
+ elSeries.add(elDataPointsMain)
+ })
+
+ allSeries.push(elSeries)
+ })
+
+ this.drawPolygons({
+ parent: ret,
+ })
+
+ if (w.config.xaxis.labels.show) {
+ const xaxisTexts = this.drawXAxisTexts()
+ ret.add(xaxisTexts)
+ }
+
+ allSeries.forEach((elS) => {
+ ret.add(elS)
+ })
+
+ ret.add(this.yaxisLabels)
+
+ return ret
+ }
+
+ drawPolygons(opts) {
+ const w = this.w
+ const { parent } = opts
+ const helpers = new Helpers(this.ctx)
+
+ const yaxisTexts = w.globals.yAxisScale[0].result.reverse()
+ const layers = yaxisTexts.length
+
+ let radiusSizes = []
+ let layerDis = this.size / (layers - 1)
+ for (let i = 0; i < layers; i++) {
+ radiusSizes[i] = layerDis * i
+ }
+ radiusSizes.reverse()
+
+ let polygonStrings = []
+ let lines = []
+
+ radiusSizes.forEach((radiusSize, r) => {
+ const polygon = Utils.getPolygonPos(radiusSize, this.dataPointsLen)
+ let string = ''
+
+ polygon.forEach((p, i) => {
+ if (r === 0) {
+ const line = this.graphics.drawLine(
+ p.x,
+ p.y,
+ 0,
+ 0,
+ Array.isArray(this.polygons.connectorColors)
+ ? this.polygons.connectorColors[i]
+ : this.polygons.connectorColors
+ )
+
+ lines.push(line)
+ }
+
+ if (i === 0) {
+ this.yaxisLabelsTextsPos.push({
+ x: p.x,
+ y: p.y,
+ })
+ }
+
+ string += p.x + ',' + p.y + ' '
+ })
+
+ polygonStrings.push(string)
+ })
+
+ polygonStrings.forEach((p, i) => {
+ const strokeColors = this.polygons.strokeColors
+ const strokeWidth = this.polygons.strokeWidth
+ const polygon = this.graphics.drawPolygon(
+ p,
+ Array.isArray(strokeColors) ? strokeColors[i] : strokeColors,
+ Array.isArray(strokeWidth) ? strokeWidth[i] : strokeWidth,
+ w.globals.radarPolygons.fill.colors[i]
+ )
+ parent.add(polygon)
+ })
+
+ lines.forEach((l) => {
+ parent.add(l)
+ })
+
+ if (w.config.yaxis[0].show) {
+ this.yaxisLabelsTextsPos.forEach((p, i) => {
+ const yText = helpers.drawYAxisTexts(p.x, p.y, i, yaxisTexts[i])
+ this.yaxisLabels.add(yText)
+ })
+ }
+ }
+
+ drawXAxisTexts() {
+ const w = this.w
+
+ const xaxisLabelsConfig = w.config.xaxis.labels
+ let elXAxisWrap = this.graphics.group({
+ class: 'apexcharts-xaxis',
+ })
+
+ let polygonPos = Utils.getPolygonPos(this.size, this.dataPointsLen)
+
+ w.globals.labels.forEach((label, i) => {
+ let formatter = w.config.xaxis.labels.formatter
+ let dataLabels = new DataLabels(this.ctx)
+
+ if (polygonPos[i]) {
+ let textPos = this.getTextPos(polygonPos[i], this.size)
+
+ let text = formatter(label, {
+ seriesIndex: -1,
+ dataPointIndex: i,
+ w,
+ })
+
+ const dataLabelText = dataLabels.plotDataLabelsText({
+ x: textPos.newX,
+ y: textPos.newY,
+ text,
+ textAnchor: textPos.textAnchor,
+ i,
+ j: i,
+ parent: elXAxisWrap,
+ className: 'apexcharts-xaxis-label',
+ color:
+ Array.isArray(xaxisLabelsConfig.style.colors) &&
+ xaxisLabelsConfig.style.colors[i]
+ ? xaxisLabelsConfig.style.colors[i]
+ : '#a8a8a8',
+ dataLabelsConfig: {
+ textAnchor: textPos.textAnchor,
+ dropShadow: { enabled: false },
+ ...xaxisLabelsConfig,
+ },
+ offsetCorrection: false,
+ })
+
+ dataLabelText.on('click', (e) => {
+ if (typeof w.config.chart.events.xAxisLabelClick === 'function') {
+ const opts = Object.assign({}, w, {
+ labelIndex: i,
+ })
+
+ w.config.chart.events.xAxisLabelClick(e, this.ctx, opts)
+ }
+ })
+ }
+ })
+
+ return elXAxisWrap
+ }
+
+ createPaths(pos, origin) {
+ let linePathsTo = []
+ let linePathsFrom = []
+ let areaPathsTo = []
+ let areaPathsFrom = []
+
+ if (pos.length) {
+ linePathsFrom = [this.graphics.move(origin.x, origin.y)]
+ areaPathsFrom = [this.graphics.move(origin.x, origin.y)]
+
+ let linePathTo = this.graphics.move(pos[0].x, pos[0].y)
+ let areaPathTo = this.graphics.move(pos[0].x, pos[0].y)
+
+ pos.forEach((p, i) => {
+ linePathTo += this.graphics.line(p.x, p.y)
+ areaPathTo += this.graphics.line(p.x, p.y)
+ if (i === pos.length - 1) {
+ linePathTo += 'Z'
+ areaPathTo += 'Z'
+ }
+ })
+
+ linePathsTo.push(linePathTo)
+ areaPathsTo.push(areaPathTo)
+ }
+
+ return {
+ linePathsFrom,
+ linePathsTo,
+ areaPathsFrom,
+ areaPathsTo,
+ }
+ }
+
+ getTextPos(pos, polygonSize) {
+ let limit = 10
+ let textAnchor = 'middle'
+
+ let newX = pos.x
+ let newY = pos.y
+
+ if (Math.abs(pos.x) >= limit) {
+ if (pos.x > 0) {
+ textAnchor = 'start'
+ newX += 10
+ } else if (pos.x < 0) {
+ textAnchor = 'end'
+ newX -= 10
+ }
+ } else {
+ textAnchor = 'middle'
+ }
+ if (Math.abs(pos.y) >= polygonSize - limit) {
+ if (pos.y < 0) {
+ newY -= 10
+ } else if (pos.y > 0) {
+ newY += 10
+ }
+ }
+
+ return {
+ textAnchor,
+ newX,
+ newY,
+ }
+ }
+
+ getPreviousPath(realIndex) {
+ let w = this.w
+ let pathFrom = null
+ for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
+ let gpp = w.globals.previousPaths[pp]
+
+ if (
+ gpp.paths.length > 0 &&
+ parseInt(gpp.realIndex, 10) === parseInt(realIndex, 10)
+ ) {
+ if (typeof w.globals.previousPaths[pp].paths[0] !== 'undefined') {
+ pathFrom = w.globals.previousPaths[pp].paths[0].d
+ }
+ }
+ }
+ return pathFrom
+ }
+
+ getDataPointsPos(
+ dataRadiusArr,
+ angleArr,
+ dataPointsLen = this.dataPointsLen
+ ) {
+ dataRadiusArr = dataRadiusArr || []
+ angleArr = angleArr || []
+ let dataPointsPosArray = []
+ for (let j = 0; j < dataPointsLen; j++) {
+ let curPointPos = {}
+ curPointPos.x = dataRadiusArr[j] * Math.sin(angleArr[j])
+ curPointPos.y = -dataRadiusArr[j] * Math.cos(angleArr[j])
+ dataPointsPosArray.push(curPointPos)
+ }
+ return dataPointsPosArray
+ }
+}
+
+export default Radar
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Radial.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Radial.js
new file mode 100644
index 0000000..1aefb3c
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Radial.js
@@ -0,0 +1,545 @@
+import Pie from './Pie'
+import Utils from '../utils/Utils'
+import Fill from '../modules/Fill'
+import Graphics from '../modules/Graphics'
+import Filters from '../modules/Filters'
+
+/**
+ * ApexCharts Radial Class for drawing Circle / Semi Circle Charts.
+ * @module Radial
+ **/
+
+class Radial extends Pie {
+ constructor(ctx) {
+ super(ctx)
+
+ this.ctx = ctx
+ this.w = ctx.w
+ this.animBeginArr = [0]
+ this.animDur = 0
+
+ const w = this.w
+ this.startAngle = w.config.plotOptions.radialBar.startAngle
+ this.endAngle = w.config.plotOptions.radialBar.endAngle
+
+ this.totalAngle = Math.abs(
+ w.config.plotOptions.radialBar.endAngle -
+ w.config.plotOptions.radialBar.startAngle
+ )
+
+ this.trackStartAngle = w.config.plotOptions.radialBar.track.startAngle
+ this.trackEndAngle = w.config.plotOptions.radialBar.track.endAngle
+
+ this.barLabels = this.w.config.plotOptions.radialBar.barLabels
+
+ this.donutDataLabels = this.w.config.plotOptions.radialBar.dataLabels
+ this.radialDataLabels = this.donutDataLabels // make a copy for easy reference
+
+ if (!this.trackStartAngle) this.trackStartAngle = this.startAngle
+ if (!this.trackEndAngle) this.trackEndAngle = this.endAngle
+
+ if (this.endAngle === 360) this.endAngle = 359.99
+
+ this.margin = parseInt(w.config.plotOptions.radialBar.track.margin, 10)
+ this.onBarLabelClick = this.onBarLabelClick.bind(this)
+ }
+
+ draw(series) {
+ let w = this.w
+ const graphics = new Graphics(this.ctx)
+
+ let ret = graphics.group({
+ class: 'apexcharts-radialbar',
+ })
+
+ if (w.globals.noData) return ret
+
+ let elSeries = graphics.group()
+
+ let centerY = this.defaultSize / 2
+ let centerX = w.globals.gridWidth / 2
+
+ let size = this.defaultSize / 2.05
+ if (!w.config.chart.sparkline.enabled) {
+ size = size - w.config.stroke.width - w.config.chart.dropShadow.blur
+ }
+ let colorArr = w.globals.fill.colors
+
+ if (w.config.plotOptions.radialBar.track.show) {
+ let elTracks = this.drawTracks({
+ size,
+ centerX,
+ centerY,
+ colorArr,
+ series,
+ })
+ elSeries.add(elTracks)
+ }
+
+ let elG = this.drawArcs({
+ size,
+ centerX,
+ centerY,
+ colorArr,
+ series,
+ })
+
+ let totalAngle = 360
+
+ if (w.config.plotOptions.radialBar.startAngle < 0) {
+ totalAngle = this.totalAngle
+ }
+
+ let angleRatio = (360 - totalAngle) / 360
+ w.globals.radialSize = size - size * angleRatio
+
+ if (this.radialDataLabels.value.show) {
+ let offset = Math.max(
+ this.radialDataLabels.value.offsetY,
+ this.radialDataLabels.name.offsetY
+ )
+ w.globals.radialSize += offset * angleRatio
+ }
+
+ elSeries.add(elG.g)
+
+ if (w.config.plotOptions.radialBar.hollow.position === 'front') {
+ elG.g.add(elG.elHollow)
+ if (elG.dataLabels) {
+ elG.g.add(elG.dataLabels)
+ }
+ }
+
+ ret.add(elSeries)
+
+ return ret
+ }
+
+ drawTracks(opts) {
+ let w = this.w
+ const graphics = new Graphics(this.ctx)
+
+ let g = graphics.group({
+ class: 'apexcharts-tracks',
+ })
+
+ let filters = new Filters(this.ctx)
+ let fill = new Fill(this.ctx)
+
+ let strokeWidth = this.getStrokeWidth(opts)
+
+ opts.size = opts.size - strokeWidth / 2
+
+ for (let i = 0; i < opts.series.length; i++) {
+ let elRadialBarTrack = graphics.group({
+ class: 'apexcharts-radialbar-track apexcharts-track',
+ })
+ g.add(elRadialBarTrack)
+
+ elRadialBarTrack.attr({
+ rel: i + 1,
+ })
+
+ opts.size = opts.size - strokeWidth - this.margin
+
+ const trackConfig = w.config.plotOptions.radialBar.track
+ let pathFill = fill.fillPath({
+ seriesNumber: 0,
+ size: opts.size,
+ fillColors: Array.isArray(trackConfig.background)
+ ? trackConfig.background[i]
+ : trackConfig.background,
+ solid: true,
+ })
+
+ let startAngle = this.trackStartAngle
+ let endAngle = this.trackEndAngle
+
+ if (Math.abs(endAngle) + Math.abs(startAngle) >= 360)
+ endAngle = 360 - Math.abs(this.startAngle) - 0.1
+
+ let elPath = graphics.drawPath({
+ d: '',
+ stroke: pathFill,
+ strokeWidth:
+ (strokeWidth * parseInt(trackConfig.strokeWidth, 10)) / 100,
+ fill: 'none',
+ strokeOpacity: trackConfig.opacity,
+ classes: 'apexcharts-radialbar-area',
+ })
+
+ if (trackConfig.dropShadow.enabled) {
+ const shadow = trackConfig.dropShadow
+ filters.dropShadow(elPath, shadow)
+ }
+
+ elRadialBarTrack.add(elPath)
+
+ elPath.attr('id', 'apexcharts-radialbarTrack-' + i)
+
+ this.animatePaths(elPath, {
+ centerX: opts.centerX,
+ centerY: opts.centerY,
+ endAngle,
+ startAngle,
+ size: opts.size,
+ i,
+ totalItems: 2,
+ animBeginArr: 0,
+ dur: 0,
+ isTrack: true,
+ easing: w.globals.easing,
+ })
+ }
+
+ return g
+ }
+
+ drawArcs(opts) {
+ let w = this.w
+ // size, donutSize, centerX, centerY, colorArr, lineColorArr, sectorAngleArr, series
+
+ let graphics = new Graphics(this.ctx)
+ let fill = new Fill(this.ctx)
+ let filters = new Filters(this.ctx)
+ let g = graphics.group()
+
+ let strokeWidth = this.getStrokeWidth(opts)
+ opts.size = opts.size - strokeWidth / 2
+
+ let hollowFillID = w.config.plotOptions.radialBar.hollow.background
+ let hollowSize =
+ opts.size -
+ strokeWidth * opts.series.length -
+ this.margin * opts.series.length -
+ (strokeWidth *
+ parseInt(w.config.plotOptions.radialBar.track.strokeWidth, 10)) /
+ 100 /
+ 2
+
+ let hollowRadius = hollowSize - w.config.plotOptions.radialBar.hollow.margin
+
+ if (w.config.plotOptions.radialBar.hollow.image !== undefined) {
+ hollowFillID = this.drawHollowImage(opts, g, hollowSize, hollowFillID)
+ }
+
+ let elHollow = this.drawHollow({
+ size: hollowRadius,
+ centerX: opts.centerX,
+ centerY: opts.centerY,
+ fill: hollowFillID ? hollowFillID : 'transparent',
+ })
+
+ if (w.config.plotOptions.radialBar.hollow.dropShadow.enabled) {
+ const shadow = w.config.plotOptions.radialBar.hollow.dropShadow
+ filters.dropShadow(elHollow, shadow)
+ }
+
+ let shown = 1
+ if (!this.radialDataLabels.total.show && w.globals.series.length > 1) {
+ shown = 0
+ }
+
+ let dataLabels = null
+
+ if (this.radialDataLabels.show) {
+ let dataLabelsGroup = w.globals.dom.Paper.select(
+ `.apexcharts-datalabels-group`
+ ).members[0]
+
+ dataLabels = this.renderInnerDataLabels(
+ dataLabelsGroup,
+ this.radialDataLabels,
+ {
+ hollowSize,
+ centerX: opts.centerX,
+ centerY: opts.centerY,
+ opacity: shown,
+ }
+ )
+ }
+
+ if (w.config.plotOptions.radialBar.hollow.position === 'back') {
+ g.add(elHollow)
+ if (dataLabels) {
+ g.add(dataLabels)
+ }
+ }
+
+ let reverseLoop = false
+ if (w.config.plotOptions.radialBar.inverseOrder) {
+ reverseLoop = true
+ }
+
+ for (
+ let i = reverseLoop ? opts.series.length - 1 : 0;
+ reverseLoop ? i >= 0 : i < opts.series.length;
+ reverseLoop ? i-- : i++
+ ) {
+ let elRadialBarArc = graphics.group({
+ class: `apexcharts-series apexcharts-radial-series`,
+ seriesName: Utils.escapeString(w.globals.seriesNames[i]),
+ })
+ g.add(elRadialBarArc)
+
+ elRadialBarArc.attr({
+ rel: i + 1,
+ 'data:realIndex': i,
+ })
+
+ this.ctx.series.addCollapsedClassToSeries(elRadialBarArc, i)
+
+ opts.size = opts.size - strokeWidth - this.margin
+
+ let pathFill = fill.fillPath({
+ seriesNumber: i,
+ size: opts.size,
+ value: opts.series[i],
+ })
+
+ let startAngle = this.startAngle
+ let prevStartAngle
+
+ // if data exceeds 100, make it 100
+ const dataValue =
+ Utils.negToZero(opts.series[i] > 100 ? 100 : opts.series[i]) / 100
+
+ let endAngle = Math.round(this.totalAngle * dataValue) + this.startAngle
+
+ let prevEndAngle
+ if (w.globals.dataChanged) {
+ prevStartAngle = this.startAngle
+ prevEndAngle =
+ Math.round(
+ (this.totalAngle * Utils.negToZero(w.globals.previousPaths[i])) /
+ 100
+ ) + prevStartAngle
+ }
+
+ const currFullAngle = Math.abs(endAngle) + Math.abs(startAngle)
+ if (currFullAngle > 360) {
+ endAngle = endAngle - 0.01
+ }
+
+ const prevFullAngle = Math.abs(prevEndAngle) + Math.abs(prevStartAngle)
+ if (prevFullAngle > 360) {
+ prevEndAngle = prevEndAngle - 0.01
+ }
+
+ let angle = endAngle - startAngle
+
+ const dashArray = Array.isArray(w.config.stroke.dashArray)
+ ? w.config.stroke.dashArray[i]
+ : w.config.stroke.dashArray
+
+ let elPath = graphics.drawPath({
+ d: '',
+ stroke: pathFill,
+ strokeWidth,
+ fill: 'none',
+ fillOpacity: w.config.fill.opacity,
+ classes: 'apexcharts-radialbar-area apexcharts-radialbar-slice-' + i,
+ strokeDashArray: dashArray,
+ })
+
+ Graphics.setAttrs(elPath.node, {
+ 'data:angle': angle,
+ 'data:value': opts.series[i],
+ })
+
+ if (w.config.chart.dropShadow.enabled) {
+ const shadow = w.config.chart.dropShadow
+ filters.dropShadow(elPath, shadow, i)
+ }
+ filters.setSelectionFilter(elPath, 0, i)
+
+ this.addListeners(elPath, this.radialDataLabels)
+
+ elRadialBarArc.add(elPath)
+
+ elPath.attr({
+ index: 0,
+ j: i,
+ })
+
+ if (this.barLabels.enabled) {
+ let barStartCords = Utils.polarToCartesian(
+ opts.centerX,
+ opts.centerY,
+ opts.size,
+ startAngle
+ )
+ let text = this.barLabels.formatter(w.globals.seriesNames[i], {
+ seriesIndex: i,
+ w,
+ })
+ let classes = ['apexcharts-radialbar-label']
+ if (!this.barLabels.onClick) {
+ classes.push('apexcharts-no-click')
+ }
+
+ let textColor = this.barLabels.useSeriesColors
+ ? w.globals.colors[i]
+ : w.config.chart.foreColor
+
+ if (!textColor) {
+ textColor = w.config.chart.foreColor
+ }
+
+ const x = barStartCords.x + this.barLabels.offsetX
+ const y = barStartCords.y + this.barLabels.offsetY
+ let elText = graphics.drawText({
+ x,
+ y,
+ text,
+ textAnchor: 'end',
+ dominantBaseline: 'middle',
+ fontFamily: this.barLabels.fontFamily,
+ fontWeight: this.barLabels.fontWeight,
+ fontSize: this.barLabels.fontSize,
+ foreColor: textColor,
+ cssClass: classes.join(' '),
+ })
+
+ elText.on('click', this.onBarLabelClick)
+
+ elText.attr({
+ rel: i + 1,
+ })
+
+ if (startAngle !== 0) {
+ elText.attr({
+ 'transform-origin': `${x} ${y}`,
+ transform: `rotate(${startAngle} 0 0)`,
+ })
+ }
+
+ elRadialBarArc.add(elText)
+ }
+
+ let dur = 0
+ if (this.initialAnim && !w.globals.resized && !w.globals.dataChanged) {
+ dur = w.config.chart.animations.speed
+ }
+
+ if (w.globals.dataChanged) {
+ dur = w.config.chart.animations.dynamicAnimation.speed
+ }
+ this.animDur = dur / (opts.series.length * 1.2) + this.animDur
+ this.animBeginArr.push(this.animDur)
+
+ this.animatePaths(elPath, {
+ centerX: opts.centerX,
+ centerY: opts.centerY,
+ endAngle,
+ startAngle,
+ prevEndAngle,
+ prevStartAngle,
+ size: opts.size,
+ i,
+ totalItems: 2,
+ animBeginArr: this.animBeginArr,
+ dur,
+ shouldSetPrevPaths: true,
+ easing: w.globals.easing,
+ })
+ }
+
+ return {
+ g,
+ elHollow,
+ dataLabels,
+ }
+ }
+
+ drawHollow(opts) {
+ const graphics = new Graphics(this.ctx)
+
+ let circle = graphics.drawCircle(opts.size * 2)
+
+ circle.attr({
+ class: 'apexcharts-radialbar-hollow',
+ cx: opts.centerX,
+ cy: opts.centerY,
+ r: opts.size,
+ fill: opts.fill,
+ })
+
+ return circle
+ }
+
+ drawHollowImage(opts, g, hollowSize, hollowFillID) {
+ const w = this.w
+ let fill = new Fill(this.ctx)
+
+ let randID = Utils.randomId()
+ let hollowFillImg = w.config.plotOptions.radialBar.hollow.image
+
+ if (w.config.plotOptions.radialBar.hollow.imageClipped) {
+ fill.clippedImgArea({
+ width: hollowSize,
+ height: hollowSize,
+ image: hollowFillImg,
+ patternID: `pattern${w.globals.cuid}${randID}`,
+ })
+ hollowFillID = `url(#pattern${w.globals.cuid}${randID})`
+ } else {
+ const imgWidth = w.config.plotOptions.radialBar.hollow.imageWidth
+ const imgHeight = w.config.plotOptions.radialBar.hollow.imageHeight
+ if (imgWidth === undefined && imgHeight === undefined) {
+ let image = w.globals.dom.Paper.image(hollowFillImg).loaded(function (
+ loader
+ ) {
+ this.move(
+ opts.centerX -
+ loader.width / 2 +
+ w.config.plotOptions.radialBar.hollow.imageOffsetX,
+ opts.centerY -
+ loader.height / 2 +
+ w.config.plotOptions.radialBar.hollow.imageOffsetY
+ )
+ })
+ g.add(image)
+ } else {
+ let image = w.globals.dom.Paper.image(hollowFillImg).loaded(function (
+ loader
+ ) {
+ this.move(
+ opts.centerX -
+ imgWidth / 2 +
+ w.config.plotOptions.radialBar.hollow.imageOffsetX,
+ opts.centerY -
+ imgHeight / 2 +
+ w.config.plotOptions.radialBar.hollow.imageOffsetY
+ )
+ this.size(imgWidth, imgHeight)
+ })
+ g.add(image)
+ }
+ }
+ return hollowFillID
+ }
+
+ getStrokeWidth(opts) {
+ const w = this.w
+ return (
+ (opts.size *
+ (100 - parseInt(w.config.plotOptions.radialBar.hollow.size, 10))) /
+ 100 /
+ (opts.series.length + 1) -
+ this.margin
+ )
+ }
+
+ onBarLabelClick(e) {
+ let seriesIndex = parseInt(e.target.getAttribute('rel'), 10) - 1
+ const legendClick = this.barLabels.onClick
+ const w = this.w
+
+ if (legendClick) {
+ legendClick(w.globals.seriesNames[seriesIndex], { w, seriesIndex })
+ }
+ }
+}
+
+export default Radial
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/RangeBar.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/RangeBar.js
new file mode 100644
index 0000000..8c5d1bf
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/RangeBar.js
@@ -0,0 +1,456 @@
+import Bar from './Bar'
+import Graphics from '../modules/Graphics'
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts RangeBar Class responsible for drawing Range/Timeline Bars.
+ *
+ * @module RangeBar
+ **/
+
+class RangeBar extends Bar {
+ draw(series, seriesIndex) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+
+ this.rangeBarOptions = this.w.config.plotOptions.rangeBar
+
+ this.series = series
+ this.seriesRangeStart = w.globals.seriesRangeStart
+ this.seriesRangeEnd = w.globals.seriesRangeEnd
+
+ this.barHelpers.initVariables(series)
+
+ let ret = graphics.group({
+ class: 'apexcharts-rangebar-series apexcharts-plot-series',
+ })
+
+ for (let i = 0; i < series.length; i++) {
+ let x,
+ y,
+ xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
+ yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
+ zeroH, // zeroH is the baseline where 0 meets y axis
+ zeroW // zeroW is the baseline where 0 meets x axis
+
+ let realIndex = w.globals.comboCharts ? seriesIndex[i] : i
+ let { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
+
+ // el to which series will be drawn
+ let elSeries = graphics.group({
+ class: `apexcharts-series`,
+ seriesName: Utils.escapeString(w.globals.seriesNames[realIndex]),
+ rel: i + 1,
+ 'data:realIndex': realIndex,
+ })
+
+ this.ctx.series.addCollapsedClassToSeries(elSeries, realIndex)
+
+ if (series[i].length > 0) {
+ this.visibleI = this.visibleI + 1
+ }
+
+ let barHeight = 0
+ let barWidth = 0
+
+ let translationsIndex = 0
+ if (this.yRatio.length > 1) {
+ this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex][0]
+ translationsIndex = realIndex
+ }
+
+ let initPositions = this.barHelpers.initialPositions()
+
+ y = initPositions.y
+ zeroW = initPositions.zeroW
+
+ x = initPositions.x
+ barWidth = initPositions.barWidth
+ barHeight = initPositions.barHeight
+ xDivision = initPositions.xDivision
+ yDivision = initPositions.yDivision
+ zeroH = initPositions.zeroH
+
+ // eldatalabels
+ let elDataLabelsWrap = graphics.group({
+ class: 'apexcharts-datalabels',
+ 'data:realIndex': realIndex,
+ })
+
+ let elGoalsMarkers = graphics.group({
+ class: 'apexcharts-rangebar-goals-markers',
+ })
+
+ for (let j = 0; j < w.globals.dataPoints; j++) {
+ const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
+
+ const y1 = this.seriesRangeStart[i][j]
+ const y2 = this.seriesRangeEnd[i][j]
+
+ let paths = null
+ let barXPosition = null
+ let barYPosition = null
+ const params = { x, y, strokeWidth, elSeries }
+
+ let seriesLen = this.seriesLen
+ if (w.config.plotOptions.bar.rangeBarGroupRows) {
+ seriesLen = 1
+ }
+
+ if (typeof w.config.series[i].data[j] === 'undefined') {
+ // no data exists for further indexes, hence we need to get out the innr loop.
+ // As we are iterating over total datapoints, there is a possiblity the series might not have data for j index
+ break
+ }
+
+ if (this.isHorizontal) {
+ barYPosition = y + barHeight * this.visibleI
+
+ let srty = (yDivision - barHeight * seriesLen) / 2
+
+ if (w.config.series[i].data[j].x) {
+ let positions = this.detectOverlappingBars({
+ i,
+ j,
+ barYPosition,
+ srty,
+ barHeight,
+ yDivision,
+ initPositions,
+ })
+
+ barHeight = positions.barHeight
+ barYPosition = positions.barYPosition
+ }
+
+ paths = this.drawRangeBarPaths({
+ indexes: { i, j, realIndex },
+ barHeight,
+ barYPosition,
+ zeroW,
+ yDivision,
+ y1,
+ y2,
+ ...params,
+ })
+
+ barWidth = paths.barWidth
+ } else {
+ if (w.globals.isXNumeric) {
+ x =
+ (w.globals.seriesX[i][j] - w.globals.minX) / this.xRatio -
+ barWidth / 2
+ }
+
+ barXPosition = x + barWidth * this.visibleI
+
+ let srtx = (xDivision - barWidth * seriesLen) / 2
+
+ if (w.config.series[i].data[j].x) {
+ let positions = this.detectOverlappingBars({
+ i,
+ j,
+ barXPosition,
+ srtx,
+ barWidth,
+ xDivision,
+ initPositions,
+ })
+
+ barWidth = positions.barWidth
+ barXPosition = positions.barXPosition
+ }
+
+ paths = this.drawRangeColumnPaths({
+ indexes: { i, j, realIndex, translationsIndex },
+ barWidth,
+ barXPosition,
+ zeroH,
+ xDivision,
+ ...params,
+ })
+
+ barHeight = paths.barHeight
+ }
+
+ const barGoalLine = this.barHelpers.drawGoalLine({
+ barXPosition: paths.barXPosition,
+ barYPosition,
+ goalX: paths.goalX,
+ goalY: paths.goalY,
+ barHeight,
+ barWidth,
+ })
+
+ if (barGoalLine) {
+ elGoalsMarkers.add(barGoalLine)
+ }
+
+ y = paths.y
+ x = paths.x
+
+ let pathFill = this.barHelpers.getPathFillColor(series, i, j, realIndex)
+
+ let lineFill = w.globals.stroke.colors[realIndex]
+
+ this.renderSeries({
+ realIndex,
+ pathFill,
+ lineFill,
+ j,
+ i,
+ x,
+ y,
+ y1,
+ y2,
+ pathFrom: paths.pathFrom,
+ pathTo: paths.pathTo,
+ strokeWidth,
+ elSeries,
+ series,
+ barHeight,
+ barWidth,
+ barXPosition,
+ barYPosition,
+ columnGroupIndex,
+ elDataLabelsWrap,
+ elGoalsMarkers,
+ visibleSeries: this.visibleI,
+ type: 'rangebar',
+ })
+ }
+
+ ret.add(elSeries)
+ }
+
+ return ret
+ }
+
+ detectOverlappingBars({
+ i,
+ j,
+ barYPosition,
+ barXPosition,
+ srty,
+ srtx,
+ barHeight,
+ barWidth,
+ yDivision,
+ xDivision,
+ initPositions,
+ }) {
+ const w = this.w
+ let overlaps = []
+ let rangeName = w.config.series[i].data[j].rangeName
+
+ const x = w.config.series[i].data[j].x
+ const labelX = Array.isArray(x) ? x.join(' ') : x
+
+ const rowIndex = w.globals.labels
+ .map((_) => (Array.isArray(_) ? _.join(' ') : _))
+ .indexOf(labelX)
+ const overlappedIndex = w.globals.seriesRange[i].findIndex(
+ (tx) => tx.x === labelX && tx.overlaps.length > 0
+ )
+
+ if (this.isHorizontal) {
+ if (w.config.plotOptions.bar.rangeBarGroupRows) {
+ barYPosition = srty + yDivision * rowIndex
+ } else {
+ barYPosition = srty + barHeight * this.visibleI + yDivision * rowIndex
+ }
+
+ if (overlappedIndex > -1 && !w.config.plotOptions.bar.rangeBarOverlap) {
+ overlaps = w.globals.seriesRange[i][overlappedIndex].overlaps
+
+ if (overlaps.indexOf(rangeName) > -1) {
+ barHeight = initPositions.barHeight / overlaps.length
+
+ barYPosition =
+ barHeight * this.visibleI +
+ (yDivision * (100 - parseInt(this.barOptions.barHeight, 10))) /
+ 100 /
+ 2 +
+ barHeight * (this.visibleI + overlaps.indexOf(rangeName)) +
+ yDivision * rowIndex
+ }
+ }
+ } else {
+ if (rowIndex > -1 && !w.globals.timescaleLabels.length) {
+ if (w.config.plotOptions.bar.rangeBarGroupRows) {
+ barXPosition = srtx + xDivision * rowIndex
+ } else {
+ barXPosition = srtx + barWidth * this.visibleI + xDivision * rowIndex
+ }
+ }
+
+ if (overlappedIndex > -1 && !w.config.plotOptions.bar.rangeBarOverlap) {
+ overlaps = w.globals.seriesRange[i][overlappedIndex].overlaps
+
+ if (overlaps.indexOf(rangeName) > -1) {
+ barWidth = initPositions.barWidth / overlaps.length
+
+ barXPosition =
+ barWidth * this.visibleI +
+ (xDivision * (100 - parseInt(this.barOptions.barWidth, 10))) /
+ 100 /
+ 2 +
+ barWidth * (this.visibleI + overlaps.indexOf(rangeName)) +
+ xDivision * rowIndex
+ }
+ }
+ }
+
+ return {
+ barYPosition,
+ barXPosition,
+ barHeight,
+ barWidth,
+ }
+ }
+
+ drawRangeColumnPaths({
+ indexes,
+ x,
+ xDivision,
+ barWidth,
+ barXPosition,
+ zeroH,
+ }) {
+ let w = this.w
+
+ const { i, j, realIndex, translationsIndex } = indexes
+
+ const yRatio = this.yRatio[translationsIndex]
+
+ const range = this.getRangeValue(realIndex, j)
+
+ let y1 = Math.min(range.start, range.end)
+ let y2 = Math.max(range.start, range.end)
+
+ if (
+ typeof this.series[i][j] === 'undefined' ||
+ this.series[i][j] === null
+ ) {
+ y1 = zeroH
+ } else {
+ y1 = zeroH - y1 / yRatio
+ y2 = zeroH - y2 / yRatio
+ }
+ const barHeight = Math.abs(y2 - y1)
+
+ const paths = this.barHelpers.getColumnPaths({
+ barXPosition,
+ barWidth,
+ y1,
+ y2,
+ strokeWidth: this.strokeWidth,
+ series: this.seriesRangeEnd,
+ realIndex: realIndex,
+ i: realIndex,
+ j,
+ w,
+ })
+
+ if (!w.globals.isXNumeric) {
+ x = x + xDivision
+ } else {
+ const xForNumericXAxis = this.getBarXForNumericXAxis({
+ x,
+ j,
+ realIndex,
+ barWidth,
+ })
+ x = xForNumericXAxis.x
+ barXPosition = xForNumericXAxis.barXPosition
+ }
+
+ return {
+ pathTo: paths.pathTo,
+ pathFrom: paths.pathFrom,
+ barHeight,
+ x,
+ y: range.start < 0 && range.end < 0 ? y1 : y2,
+ goalY: this.barHelpers.getGoalValues(
+ 'y',
+ null,
+ zeroH,
+ i,
+ j,
+ translationsIndex
+ ),
+ barXPosition,
+ }
+ }
+
+ preventBarOverflow(val) {
+ const w = this.w
+
+ if (val < 0) {
+ val = 0
+ }
+ if (val > w.globals.gridWidth) {
+ val = w.globals.gridWidth
+ }
+
+ return val
+ }
+
+ drawRangeBarPaths({
+ indexes,
+ y,
+ y1,
+ y2,
+ yDivision,
+ barHeight,
+ barYPosition,
+ zeroW,
+ }) {
+ let w = this.w
+
+ const { realIndex, j } = indexes
+
+ let x1 = this.preventBarOverflow(zeroW + y1 / this.invertedYRatio)
+ let x2 = this.preventBarOverflow(zeroW + y2 / this.invertedYRatio)
+
+ const range = this.getRangeValue(realIndex, j)
+
+ const barWidth = Math.abs(x2 - x1)
+
+ const paths = this.barHelpers.getBarpaths({
+ barYPosition,
+ barHeight,
+ x1,
+ x2,
+ strokeWidth: this.strokeWidth,
+ series: this.seriesRangeEnd,
+ i: realIndex,
+ realIndex,
+ j,
+ w,
+ })
+
+ if (!w.globals.isXNumeric) {
+ y = y + yDivision
+ }
+
+ return {
+ pathTo: paths.pathTo,
+ pathFrom: paths.pathFrom,
+ barWidth,
+ x: range.start < 0 && range.end < 0 ? x1 : x2,
+ goalX: this.barHelpers.getGoalValues('x', zeroW, null, realIndex, j),
+ y,
+ }
+ }
+
+ getRangeValue(i, j) {
+ const w = this.w
+ return {
+ start: w.globals.seriesRangeStart[i][j],
+ end: w.globals.seriesRangeEnd[i][j],
+ }
+ }
+}
+
+export default RangeBar
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Scatter.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Scatter.js
new file mode 100644
index 0000000..074b0cd
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Scatter.js
@@ -0,0 +1,177 @@
+import Animations from '../modules/Animations'
+import Fill from '../modules/Fill'
+import Filters from '../modules/Filters'
+import Graphics from '../modules/Graphics'
+import Markers from '../modules/Markers'
+
+/**
+ * ApexCharts Scatter Class.
+ * This Class also handles bubbles chart as currently there is no major difference in drawing them,
+ * @module Scatter
+ **/
+export default class Scatter {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.initialAnim = this.w.config.chart.animations.enabled
+ }
+
+ draw(elSeries, j, opts) {
+ let w = this.w
+
+ let graphics = new Graphics(this.ctx)
+
+ let realIndex = opts.realIndex
+ let pointsPos = opts.pointsPos
+ let zRatio = opts.zRatio
+ let elPointsMain = opts.elParent
+
+ let elPointsWrap = graphics.group({
+ class: `apexcharts-series-markers apexcharts-series-${w.config.chart.type}`,
+ })
+
+ elPointsWrap.attr('clip-path', `url(#gridRectMarkerMask${w.globals.cuid})`)
+
+ if (Array.isArray(pointsPos.x)) {
+ for (let q = 0; q < pointsPos.x.length; q++) {
+ let dataPointIndex = j + 1
+ let shouldDraw = true
+
+ // a small hack as we have 2 points for the first val to connect it
+ if (j === 0 && q === 0) dataPointIndex = 0
+ if (j === 0 && q === 1) dataPointIndex = 1
+
+ let radius = w.globals.markers.size[realIndex]
+
+ if (zRatio !== Infinity) {
+ // means we have a bubble
+ const bubble = w.config.plotOptions.bubble
+ radius = w.globals.seriesZ[realIndex][dataPointIndex]
+
+ if (bubble.zScaling) {
+ radius /= zRatio
+ }
+
+ if (bubble.minBubbleRadius && radius < bubble.minBubbleRadius) {
+ radius = bubble.minBubbleRadius
+ }
+
+ if (bubble.maxBubbleRadius && radius > bubble.maxBubbleRadius) {
+ radius = bubble.maxBubbleRadius
+ }
+ }
+
+ let x = pointsPos.x[q]
+ let y = pointsPos.y[q]
+
+ radius = radius || 0
+
+ if (
+ y === null ||
+ typeof w.globals.series[realIndex][dataPointIndex] === 'undefined'
+ ) {
+ shouldDraw = false
+ }
+
+ if (shouldDraw) {
+ const point = this.drawPoint(
+ x,
+ y,
+ radius,
+ realIndex,
+ dataPointIndex,
+ j
+ )
+ elPointsWrap.add(point)
+ }
+
+ elPointsMain.add(elPointsWrap)
+ }
+ }
+ }
+
+ drawPoint(x, y, radius, realIndex, dataPointIndex, j) {
+ const w = this.w
+
+ let i = realIndex
+ let anim = new Animations(this.ctx)
+ let filters = new Filters(this.ctx)
+ let fill = new Fill(this.ctx)
+ let markers = new Markers(this.ctx)
+ const graphics = new Graphics(this.ctx)
+
+ const markerConfig = markers.getMarkerConfig({
+ cssClass: 'apexcharts-marker',
+ seriesIndex: i,
+ dataPointIndex,
+ radius:
+ w.config.chart.type === 'bubble' ||
+ (w.globals.comboCharts &&
+ w.config.series[realIndex] &&
+ w.config.series[realIndex].type === 'bubble')
+ ? radius
+ : null,
+ })
+
+ let pathFillCircle = fill.fillPath({
+ seriesNumber: realIndex,
+ dataPointIndex,
+ color: markerConfig.pointFillColor,
+ patternUnits: 'objectBoundingBox',
+ value: w.globals.series[realIndex][j],
+ })
+
+ let el = graphics.drawMarker(x, y, markerConfig)
+
+ if (w.config.series[i].data[dataPointIndex]) {
+ if (w.config.series[i].data[dataPointIndex].fillColor) {
+ pathFillCircle = w.config.series[i].data[dataPointIndex].fillColor
+ }
+ }
+
+ el.attr({
+ fill: pathFillCircle,
+ })
+
+ if (w.config.chart.dropShadow.enabled) {
+ const dropShadow = w.config.chart.dropShadow
+ filters.dropShadow(el, dropShadow, realIndex)
+ }
+
+ if (this.initialAnim && !w.globals.dataChanged && !w.globals.resized) {
+ let speed = w.config.chart.animations.speed
+
+ anim.animateMarker(el, speed, w.globals.easing, () => {
+ window.setTimeout(() => {
+ anim.animationCompleted(el)
+ }, 100)
+ })
+ } else {
+ w.globals.animationEnded = true
+ }
+
+ el.attr({
+ rel: dataPointIndex,
+ j: dataPointIndex,
+ index: realIndex,
+ 'default-marker-size': markerConfig.pSize,
+ })
+
+ filters.setSelectionFilter(el, realIndex, dataPointIndex)
+ markers.addEvents(el)
+
+ el.node.classList.add('apexcharts-marker')
+
+ return el
+ }
+
+ centerTextInBubble(y) {
+ let w = this.w
+ y = y + parseInt(w.config.dataLabels.style.fontSize, 10) / 4
+
+ return {
+ y,
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Treemap.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Treemap.js
new file mode 100644
index 0000000..a61169b
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/Treemap.js
@@ -0,0 +1,357 @@
+import '../libs/Treemap-squared'
+import Graphics from '../modules/Graphics'
+import Animations from '../modules/Animations'
+import Fill from '../modules/Fill'
+import Helpers from './common/treemap/Helpers'
+import Filters from '../modules/Filters'
+
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts TreemapChart Class.
+ * @module TreemapChart
+ **/
+
+export default class TreemapChart {
+ constructor(ctx, xyRatios) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.strokeWidth = this.w.config.stroke.width
+ this.helpers = new Helpers(ctx)
+ this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation
+
+ this.labels = []
+ }
+
+ draw(series) {
+ let w = this.w
+ const graphics = new Graphics(this.ctx)
+ const fill = new Fill(this.ctx)
+
+ let ret = graphics.group({
+ class: 'apexcharts-treemap',
+ })
+
+ if (w.globals.noData) return ret
+
+ let ser = []
+ series.forEach((s) => {
+ let d = s.map((v) => {
+ return Math.abs(v)
+ })
+ ser.push(d)
+ })
+
+ this.negRange = this.helpers.checkColorRange()
+
+ w.config.series.forEach((s, i) => {
+ s.data.forEach((l) => {
+ if (!Array.isArray(this.labels[i])) this.labels[i] = []
+ this.labels[i].push(l.x)
+ })
+ })
+
+ const nodes = window.TreemapSquared.generate(
+ ser,
+ w.globals.gridWidth,
+ w.globals.gridHeight
+ )
+
+ nodes.forEach((node, i) => {
+ let elSeries = graphics.group({
+ class: `apexcharts-series apexcharts-treemap-series`,
+ seriesName: Utils.escapeString(w.globals.seriesNames[i]),
+ rel: i + 1,
+ 'data:realIndex': i,
+ })
+
+ if (w.config.chart.dropShadow.enabled) {
+ const shadow = w.config.chart.dropShadow
+ const filters = new Filters(this.ctx)
+ filters.dropShadow(ret, shadow, i)
+ }
+
+ let elDataLabelWrap = graphics.group({
+ class: 'apexcharts-data-labels',
+ })
+
+ node.forEach((r, j) => {
+ const x1 = r[0]
+ const y1 = r[1]
+ const x2 = r[2]
+ const y2 = r[3]
+ let elRect = graphics.drawRect(
+ x1,
+ y1,
+ x2 - x1,
+ y2 - y1,
+ w.config.plotOptions.treemap.borderRadius,
+ '#fff',
+ 1,
+ this.strokeWidth,
+ w.config.plotOptions.treemap.useFillColorAsStroke
+ ? color
+ : w.globals.stroke.colors[i]
+ )
+ elRect.attr({
+ cx: x1,
+ cy: y1,
+ index: i,
+ i,
+ j,
+ width: x2 - x1,
+ height: y2 - y1,
+ })
+
+ let colorProps = this.helpers.getShadeColor(
+ w.config.chart.type,
+ i,
+ j,
+ this.negRange
+ )
+ let color = colorProps.color
+
+ if (
+ typeof w.config.series[i].data[j] !== 'undefined' &&
+ w.config.series[i].data[j].fillColor
+ ) {
+ color = w.config.series[i].data[j].fillColor
+ }
+ let pathFill = fill.fillPath({
+ color,
+ seriesNumber: i,
+ dataPointIndex: j,
+ })
+
+ elRect.node.classList.add('apexcharts-treemap-rect')
+
+ elRect.attr({
+ fill: pathFill,
+ })
+
+ this.helpers.addListeners(elRect)
+
+ let fromRect = {
+ x: x1 + (x2 - x1) / 2,
+ y: y1 + (y2 - y1) / 2,
+ width: 0,
+ height: 0,
+ }
+ let toRect = {
+ x: x1,
+ y: y1,
+ width: x2 - x1,
+ height: y2 - y1,
+ }
+
+ if (w.config.chart.animations.enabled && !w.globals.dataChanged) {
+ let speed = 1
+ if (!w.globals.resized) {
+ speed = w.config.chart.animations.speed
+ }
+ this.animateTreemap(elRect, fromRect, toRect, speed)
+ }
+ if (w.globals.dataChanged) {
+ let speed = 1
+ if (this.dynamicAnim.enabled && w.globals.shouldAnimate) {
+ speed = this.dynamicAnim.speed
+
+ if (
+ w.globals.previousPaths[i] &&
+ w.globals.previousPaths[i][j] &&
+ w.globals.previousPaths[i][j].rect
+ ) {
+ fromRect = w.globals.previousPaths[i][j].rect
+ }
+
+ this.animateTreemap(elRect, fromRect, toRect, speed)
+ }
+ }
+
+ let fontSize = this.getFontSize(r)
+
+ let formattedText = w.config.dataLabels.formatter(this.labels[i][j], {
+ value: w.globals.series[i][j],
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ })
+ if (w.config.plotOptions.treemap.dataLabels.format === 'truncate') {
+ fontSize = parseInt(w.config.dataLabels.style.fontSize, 10)
+ formattedText = this.truncateLabels(
+ formattedText,
+ fontSize,
+ x1,
+ y1,
+ x2,
+ y2
+ )
+ }
+ let dataLabels = null
+
+ if (w.globals.series[i][j]) {
+ dataLabels = this.helpers.calculateDataLabels({
+ text: formattedText,
+ x: (x1 + x2) / 2,
+ y: (y1 + y2) / 2 + this.strokeWidth / 2 + fontSize / 3,
+ i,
+ j,
+ colorProps,
+ fontSize,
+ series,
+ })
+ }
+ if (w.config.dataLabels.enabled && dataLabels) {
+ this.rotateToFitLabel(
+ dataLabels,
+ fontSize,
+ formattedText,
+ x1,
+ y1,
+ x2,
+ y2
+ )
+ }
+ elSeries.add(elRect)
+
+ if (dataLabels !== null) {
+ elSeries.add(dataLabels)
+ }
+ })
+ elSeries.add(elDataLabelWrap)
+
+ ret.add(elSeries)
+ })
+
+ return ret
+ }
+
+ // This calculates a font-size based upon
+ // average label length and the size of the box the label is
+ // going into. The maximum font size is set in chart config.
+ getFontSize(coordinates) {
+ const w = this.w
+
+ // total length of labels (i.e [["Italy"],["Spain", "Greece"]] -> 16)
+ function totalLabelLength(arr) {
+ let i,
+ total = 0
+ if (Array.isArray(arr[0])) {
+ for (i = 0; i < arr.length; i++) {
+ total += totalLabelLength(arr[i])
+ }
+ } else {
+ for (i = 0; i < arr.length; i++) {
+ total += arr[i].length
+ }
+ }
+ return total
+ }
+
+ // count of labels (i.e [["Italy"],["Spain", "Greece"]] -> 3)
+ function countLabels(arr) {
+ let i,
+ total = 0
+ if (Array.isArray(arr[0])) {
+ for (i = 0; i < arr.length; i++) {
+ total += countLabels(arr[i])
+ }
+ } else {
+ for (i = 0; i < arr.length; i++) {
+ total += 1
+ }
+ }
+ return total
+ }
+ let averagelabelsize =
+ totalLabelLength(this.labels) / countLabels(this.labels)
+
+ function fontSize(width, height) {
+ // the font size should be proportional to the size of the box (and the value)
+ // otherwise you can end up creating a visual distortion where two boxes of identical
+ // size have different sized labels, and thus make it look as if the two boxes
+ // represent different sizes
+ let area = width * height
+ let arearoot = Math.pow(area, 0.5)
+ return Math.min(
+ arearoot / averagelabelsize,
+ parseInt(w.config.dataLabels.style.fontSize, 10)
+ )
+ }
+
+ return fontSize(
+ coordinates[2] - coordinates[0],
+ coordinates[3] - coordinates[1]
+ )
+ }
+
+ rotateToFitLabel(elText, fontSize, text, x1, y1, x2, y2) {
+ const graphics = new Graphics(this.ctx)
+ const textRect = graphics.getTextRects(text, fontSize)
+
+ //if the label fits better sideways then rotate it
+ if (
+ textRect.width + this.w.config.stroke.width + 5 > x2 - x1 &&
+ textRect.width <= y2 - y1
+ ) {
+ let labelRotatingCenter = graphics.rotateAroundCenter(elText.node)
+
+ elText.node.setAttribute(
+ 'transform',
+ `rotate(-90 ${labelRotatingCenter.x} ${
+ labelRotatingCenter.y
+ }) translate(${textRect.height / 3})`
+ )
+ }
+ }
+
+ // This is an alternative label formatting method that uses a
+ // consistent font size, and trims the edge of long labels
+ truncateLabels(text, fontSize, x1, y1, x2, y2) {
+ const graphics = new Graphics(this.ctx)
+ const textRect = graphics.getTextRects(text, fontSize)
+
+ // Determine max width based on ideal orientation of text
+ const labelMaxWidth =
+ textRect.width + this.w.config.stroke.width + 5 > x2 - x1 &&
+ y2 - y1 > x2 - x1
+ ? y2 - y1
+ : x2 - x1
+ const truncatedText = graphics.getTextBasedOnMaxWidth({
+ text: text,
+ maxWidth: labelMaxWidth,
+ fontSize: fontSize,
+ })
+
+ // Return empty label when text has been trimmed for very small rects
+ if (text.length !== truncatedText.length && labelMaxWidth / fontSize < 5) {
+ return ''
+ } else {
+ return truncatedText
+ }
+ }
+
+ animateTreemap(el, fromRect, toRect, speed) {
+ const animations = new Animations(this.ctx)
+ animations.animateRect(
+ el,
+ {
+ x: fromRect.x,
+ y: fromRect.y,
+ width: fromRect.width,
+ height: fromRect.height,
+ },
+ {
+ x: toRect.x,
+ y: toRect.y,
+ width: toRect.width,
+ height: toRect.height,
+ },
+ speed,
+ () => {
+ animations.animationCompleted(el)
+ }
+ )
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/bar/DataLabels.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/bar/DataLabels.js
new file mode 100644
index 0000000..49b76de
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/bar/DataLabels.js
@@ -0,0 +1,662 @@
+import Graphics from '../../../modules/Graphics'
+import DataLabels from '../../../modules/DataLabels'
+
+export default class BarDataLabels {
+ constructor(barCtx) {
+ this.w = barCtx.w
+ this.barCtx = barCtx
+
+ this.totalFormatter =
+ this.w.config.plotOptions.bar.dataLabels.total.formatter
+
+ if (!this.totalFormatter) {
+ this.totalFormatter = this.w.config.dataLabels.formatter
+ }
+ }
+ /** handleBarDataLabels is used to calculate the positions for the data-labels
+ * It also sets the element's data attr for bars and calls drawCalculatedBarDataLabels()
+ * After calculating, it also calls the function to draw data labels
+ * @memberof Bar
+ * @param {object} {barProps} most of the bar properties used throughout the bar
+ * drawing function
+ * @return {object} dataLabels node-element which you can append later
+ **/
+ handleBarDataLabels(opts) {
+ let {
+ x,
+ y,
+ y1,
+ y2,
+ i,
+ j,
+ realIndex,
+ columnGroupIndex,
+ series,
+ barHeight,
+ barWidth,
+ barXPosition,
+ barYPosition,
+ visibleSeries,
+ renderedPath,
+ } = opts
+ let w = this.w
+ let graphics = new Graphics(this.barCtx.ctx)
+
+ let strokeWidth = Array.isArray(this.barCtx.strokeWidth)
+ ? this.barCtx.strokeWidth[realIndex]
+ : this.barCtx.strokeWidth
+
+ let bcx
+ let bcy
+ if (w.globals.isXNumeric && !w.globals.isBarHorizontal) {
+ bcx = x + parseFloat(barWidth * (visibleSeries + 1))
+ bcy = y + parseFloat(barHeight * (visibleSeries + 1)) - strokeWidth
+ } else {
+ bcx = x + parseFloat(barWidth * visibleSeries)
+ bcy = y + parseFloat(barHeight * visibleSeries)
+ }
+
+ let dataLabels = null
+ let totalDataLabels = null
+ let dataLabelsX = x
+ let dataLabelsY = y
+ let dataLabelsPos = {}
+ let dataLabelsConfig = w.config.dataLabels
+ let barDataLabelsConfig = this.barCtx.barOptions.dataLabels
+ let barTotalDataLabelsConfig = this.barCtx.barOptions.dataLabels.total
+
+ if (typeof barYPosition !== 'undefined' && this.barCtx.isRangeBar) {
+ bcy = barYPosition
+ dataLabelsY = barYPosition
+ }
+
+ if (
+ typeof barXPosition !== 'undefined' &&
+ this.barCtx.isVerticalGroupedRangeBar
+ ) {
+ bcx = barXPosition
+ dataLabelsX = barXPosition
+ }
+
+ const offX = dataLabelsConfig.offsetX
+ const offY = dataLabelsConfig.offsetY
+
+ let textRects = {
+ width: 0,
+ height: 0,
+ }
+ if (w.config.dataLabels.enabled) {
+ const yLabel = w.globals.series[i][j]
+
+ textRects = graphics.getTextRects(
+ w.config.dataLabels.formatter
+ ? w.config.dataLabels.formatter(yLabel, {
+ ...w,
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ })
+ : w.globals.yLabelFormatters[0](yLabel),
+ parseFloat(dataLabelsConfig.style.fontSize)
+ )
+ }
+
+ const params = {
+ x,
+ y,
+ i,
+ j,
+ realIndex,
+ columnGroupIndex,
+ renderedPath,
+ bcx,
+ bcy,
+ barHeight,
+ barWidth,
+ textRects,
+ strokeWidth,
+ dataLabelsX,
+ dataLabelsY,
+ dataLabelsConfig,
+ barDataLabelsConfig,
+ barTotalDataLabelsConfig,
+ offX,
+ offY,
+ }
+
+ if (this.barCtx.isHorizontal) {
+ dataLabelsPos = this.calculateBarsDataLabelsPosition(params)
+ } else {
+ dataLabelsPos = this.calculateColumnsDataLabelsPosition(params)
+ }
+
+ renderedPath.attr({
+ cy: dataLabelsPos.bcy,
+ cx: dataLabelsPos.bcx,
+ j,
+ val: w.globals.series[i][j],
+ barHeight,
+ barWidth,
+ })
+
+ dataLabels = this.drawCalculatedDataLabels({
+ x: dataLabelsPos.dataLabelsX,
+ y: dataLabelsPos.dataLabelsY,
+ val: this.barCtx.isRangeBar
+ ? [y1, y2]
+ : w.config.chart.stackType === '100%'
+ ? series[realIndex][j]
+ : w.globals.series[realIndex][j],
+ i: realIndex,
+ j,
+ barWidth,
+ barHeight,
+ textRects,
+ dataLabelsConfig,
+ })
+
+ if (w.config.chart.stacked && barTotalDataLabelsConfig.enabled) {
+ totalDataLabels = this.drawTotalDataLabels({
+ x: dataLabelsPos.totalDataLabelsX,
+ y: dataLabelsPos.totalDataLabelsY,
+ barWidth,
+ barHeight,
+ realIndex,
+ textAnchor: dataLabelsPos.totalDataLabelsAnchor,
+ val: this.getStackedTotalDataLabel({ realIndex, j }),
+ dataLabelsConfig,
+ barTotalDataLabelsConfig,
+ })
+ }
+
+ return {
+ dataLabels,
+ totalDataLabels,
+ }
+ }
+
+ getStackedTotalDataLabel({ realIndex, j }) {
+ const w = this.w
+
+ let val = this.barCtx.stackedSeriesTotals[j]
+ if (this.totalFormatter) {
+ val = this.totalFormatter(val, {
+ ...w,
+ seriesIndex: realIndex,
+ dataPointIndex: j,
+ w,
+ })
+ }
+
+ return val
+ }
+
+ calculateColumnsDataLabelsPosition(opts) {
+ const w = this.w
+ let {
+ i,
+ j,
+ realIndex,
+ columnGroupIndex,
+ y,
+ bcx,
+ barWidth,
+ barHeight,
+ textRects,
+ dataLabelsX,
+ dataLabelsY,
+ dataLabelsConfig,
+ barDataLabelsConfig,
+ barTotalDataLabelsConfig,
+ strokeWidth,
+ offX,
+ offY,
+ } = opts
+
+ let totalDataLabelsY
+ let totalDataLabelsX
+ let totalDataLabelsAnchor = 'middle'
+ let totalDataLabelsBcx = bcx
+ barHeight = Math.abs(barHeight)
+
+ let vertical =
+ w.config.plotOptions.bar.dataLabels.orientation === 'vertical'
+
+ const { zeroEncounters } = this.barCtx.barHelpers.getZeroValueEncounters({
+ i,
+ j,
+ })
+
+ bcx = bcx - strokeWidth / 2
+
+ let dataPointsDividedWidth = w.globals.gridWidth / w.globals.dataPoints
+
+ if (this.barCtx.isVerticalGroupedRangeBar) {
+ dataLabelsX += barWidth / 2
+ } else {
+ if (w.globals.isXNumeric) {
+ dataLabelsX = bcx - barWidth / 2 + offX
+ } else {
+ dataLabelsX = bcx - dataPointsDividedWidth + barWidth / 2 + offX
+ }
+ if (
+ zeroEncounters > 0 &&
+ w.config.plotOptions.bar.hideZeroBarsWhenGrouped
+ ) {
+ dataLabelsX -= barWidth * zeroEncounters
+ }
+ }
+
+ if (vertical) {
+ const offsetDLX = 2
+ dataLabelsX =
+ dataLabelsX + textRects.height / 2 - strokeWidth / 2 - offsetDLX
+ }
+
+ let valIsNegative = w.globals.series[i][j] < 0
+
+ let newY = y
+ if (this.barCtx.isReversed) {
+ newY = y + (valIsNegative ? barHeight : -barHeight)
+ }
+
+ switch (barDataLabelsConfig.position) {
+ case 'center':
+ if (vertical) {
+ if (valIsNegative) {
+ dataLabelsY = newY - barHeight / 2 + offY
+ } else {
+ dataLabelsY = newY + barHeight / 2 - offY
+ }
+ } else {
+ if (valIsNegative) {
+ dataLabelsY = newY - barHeight / 2 + textRects.height / 2 + offY
+ } else {
+ dataLabelsY = newY + barHeight / 2 + textRects.height / 2 - offY
+ }
+ }
+ break
+ case 'bottom':
+ if (vertical) {
+ if (valIsNegative) {
+ dataLabelsY = newY - barHeight + offY
+ } else {
+ dataLabelsY = newY + barHeight - offY
+ }
+ } else {
+ if (valIsNegative) {
+ dataLabelsY =
+ newY - barHeight + textRects.height + strokeWidth + offY
+ } else {
+ dataLabelsY =
+ newY + barHeight - textRects.height / 2 + strokeWidth - offY
+ }
+ }
+ break
+ case 'top':
+ if (vertical) {
+ if (valIsNegative) {
+ dataLabelsY = newY + offY
+ } else {
+ dataLabelsY = newY - offY
+ }
+ } else {
+ if (valIsNegative) {
+ dataLabelsY = newY - textRects.height / 2 - offY
+ } else {
+ dataLabelsY = newY + textRects.height + offY
+ }
+ }
+ break
+ }
+
+ if (
+ this.barCtx.lastActiveBarSerieIndex === realIndex &&
+ barTotalDataLabelsConfig.enabled
+ ) {
+ const ADDITIONAL_OFFY = 18
+
+ const graphics = new Graphics(this.barCtx.ctx)
+ const totalLabeltextRects = graphics.getTextRects(
+ this.getStackedTotalDataLabel({ realIndex, j }),
+ dataLabelsConfig.fontSize
+ )
+
+ if (valIsNegative) {
+ totalDataLabelsY =
+ newY -
+ totalLabeltextRects.height / 2 -
+ offY -
+ barTotalDataLabelsConfig.offsetY +
+ ADDITIONAL_OFFY
+ } else {
+ totalDataLabelsY =
+ newY +
+ totalLabeltextRects.height +
+ offY +
+ barTotalDataLabelsConfig.offsetY -
+ ADDITIONAL_OFFY
+ }
+
+ // width divided into equal parts
+ let xDivision = dataPointsDividedWidth
+
+ totalDataLabelsX =
+ totalDataLabelsBcx +
+ (w.globals.isXNumeric
+ ? (-barWidth * w.globals.barGroups.length) / 2
+ : (w.globals.barGroups.length * barWidth) / 2 -
+ (w.globals.barGroups.length - 1) * barWidth -
+ xDivision) +
+ barTotalDataLabelsConfig.offsetX
+ }
+
+ if (!w.config.chart.stacked) {
+ if (dataLabelsY < 0) {
+ dataLabelsY = 0 + strokeWidth
+ } else if (dataLabelsY + textRects.height / 3 > w.globals.gridHeight) {
+ dataLabelsY = w.globals.gridHeight - strokeWidth
+ }
+ }
+
+ return {
+ bcx,
+ bcy: y,
+ dataLabelsX,
+ dataLabelsY,
+ totalDataLabelsX,
+ totalDataLabelsY,
+ totalDataLabelsAnchor,
+ }
+ }
+
+ calculateBarsDataLabelsPosition(opts) {
+ const w = this.w
+ let {
+ x,
+ i,
+ j,
+ realIndex,
+ bcy,
+ barHeight,
+ barWidth,
+ textRects,
+ dataLabelsX,
+ strokeWidth,
+ dataLabelsConfig,
+ barDataLabelsConfig,
+ barTotalDataLabelsConfig,
+ offX,
+ offY,
+ } = opts
+
+ let dataPointsDividedHeight = w.globals.gridHeight / w.globals.dataPoints
+
+ barWidth = Math.abs(barWidth)
+
+ let dataLabelsY =
+ bcy -
+ (this.barCtx.isRangeBar ? 0 : dataPointsDividedHeight) +
+ barHeight / 2 +
+ textRects.height / 2 +
+ offY -
+ 3
+
+ let totalDataLabelsX
+ let totalDataLabelsY
+ let totalDataLabelsAnchor = 'start'
+
+ let valIsNegative = w.globals.series[i][j] < 0
+
+ let newX = x
+ if (this.barCtx.isReversed) {
+ newX = x + (valIsNegative ? -barWidth : barWidth)
+ totalDataLabelsAnchor = valIsNegative ? 'start' : 'end'
+ }
+
+ switch (barDataLabelsConfig.position) {
+ case 'center':
+ if (valIsNegative) {
+ dataLabelsX = newX + barWidth / 2 - offX
+ } else {
+ dataLabelsX =
+ Math.max(textRects.width / 2, newX - barWidth / 2) + offX
+ }
+ break
+ case 'bottom':
+ if (valIsNegative) {
+ dataLabelsX = newX + barWidth - strokeWidth - offX
+ } else {
+ dataLabelsX = newX - barWidth + strokeWidth + offX
+ }
+ break
+ case 'top':
+ if (valIsNegative) {
+ dataLabelsX = newX - strokeWidth - offX
+ } else {
+ dataLabelsX = newX - strokeWidth + offX
+ }
+ break
+ }
+
+ if (
+ this.barCtx.lastActiveBarSerieIndex === realIndex &&
+ barTotalDataLabelsConfig.enabled
+ ) {
+ const graphics = new Graphics(this.barCtx.ctx)
+ const totalLabeltextRects = graphics.getTextRects(
+ this.getStackedTotalDataLabel({ realIndex, j }),
+ dataLabelsConfig.fontSize
+ )
+ if (valIsNegative) {
+ totalDataLabelsX =
+ newX - strokeWidth - offX - barTotalDataLabelsConfig.offsetX
+
+ totalDataLabelsAnchor = 'end'
+ } else {
+ totalDataLabelsX =
+ newX +
+ offX +
+ barTotalDataLabelsConfig.offsetX +
+ (this.barCtx.isReversed ? -(barWidth + strokeWidth) : strokeWidth)
+ }
+ totalDataLabelsY =
+ dataLabelsY -
+ textRects.height / 2 +
+ totalLabeltextRects.height / 2 +
+ barTotalDataLabelsConfig.offsetY +
+ strokeWidth
+ }
+
+ if (!w.config.chart.stacked) {
+ if (dataLabelsConfig.textAnchor === 'start') {
+ if (dataLabelsX - textRects.width < 0) {
+ dataLabelsX = valIsNegative
+ ? textRects.width + strokeWidth
+ : strokeWidth
+ } else if (dataLabelsX + textRects.width > w.globals.gridWidth) {
+ dataLabelsX = valIsNegative
+ ? w.globals.gridWidth - strokeWidth
+ : w.globals.gridWidth - textRects.width - strokeWidth
+ }
+ } else if (dataLabelsConfig.textAnchor === 'middle') {
+ if (dataLabelsX - textRects.width / 2 < 0) {
+ dataLabelsX = textRects.width / 2 + strokeWidth
+ } else if (dataLabelsX + textRects.width / 2 > w.globals.gridWidth) {
+ dataLabelsX = w.globals.gridWidth - textRects.width / 2 - strokeWidth
+ }
+ } else if (dataLabelsConfig.textAnchor === 'end') {
+ if (dataLabelsX < 1) {
+ dataLabelsX = textRects.width + strokeWidth
+ } else if (dataLabelsX + 1 > w.globals.gridWidth) {
+ dataLabelsX = w.globals.gridWidth - textRects.width - strokeWidth
+ }
+ }
+ }
+
+ return {
+ bcx: x,
+ bcy,
+ dataLabelsX,
+ dataLabelsY,
+ totalDataLabelsX,
+ totalDataLabelsY,
+ totalDataLabelsAnchor,
+ }
+ }
+
+ drawCalculatedDataLabels({
+ x,
+ y,
+ val,
+ i, // = realIndex
+ j,
+ textRects,
+ barHeight,
+ barWidth,
+ dataLabelsConfig,
+ }) {
+ const w = this.w
+ let rotate = 'rotate(0)'
+ if (w.config.plotOptions.bar.dataLabels.orientation === 'vertical')
+ rotate = `rotate(-90, ${x}, ${y})`
+
+ const dataLabels = new DataLabels(this.barCtx.ctx)
+ const graphics = new Graphics(this.barCtx.ctx)
+ const formatter = dataLabelsConfig.formatter
+
+ let elDataLabelsWrap = null
+
+ const isSeriesNotCollapsed =
+ w.globals.collapsedSeriesIndices.indexOf(i) > -1
+
+ if (dataLabelsConfig.enabled && !isSeriesNotCollapsed) {
+ elDataLabelsWrap = graphics.group({
+ class: 'apexcharts-data-labels',
+ transform: rotate,
+ })
+
+ let text = ''
+ if (typeof val !== 'undefined') {
+ text = formatter(val, {
+ ...w,
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ })
+ }
+
+ if (!val && w.config.plotOptions.bar.hideZeroBarsWhenGrouped) {
+ text = ''
+ }
+
+ let valIsNegative = w.globals.series[i][j] < 0
+ let position = w.config.plotOptions.bar.dataLabels.position
+ if (w.config.plotOptions.bar.dataLabels.orientation === 'vertical') {
+ if (position === 'top') {
+ if (valIsNegative) dataLabelsConfig.textAnchor = 'end'
+ else dataLabelsConfig.textAnchor = 'start'
+ }
+ if (position === 'center') {
+ dataLabelsConfig.textAnchor = 'middle'
+ }
+ if (position === 'bottom') {
+ if (valIsNegative) dataLabelsConfig.textAnchor = 'end'
+ else dataLabelsConfig.textAnchor = 'start'
+ }
+ }
+
+ if (
+ this.barCtx.isRangeBar &&
+ this.barCtx.barOptions.dataLabels.hideOverflowingLabels
+ ) {
+ // hide the datalabel if it cannot fit into the rect
+ const txRect = graphics.getTextRects(
+ text,
+ parseFloat(dataLabelsConfig.style.fontSize)
+ )
+ if (barWidth < txRect.width) {
+ text = ''
+ }
+ }
+
+ if (
+ w.config.chart.stacked &&
+ this.barCtx.barOptions.dataLabels.hideOverflowingLabels
+ ) {
+ // if there is not enough space to draw the label in the bar/column rect, check hideOverflowingLabels property to prevent overflowing on wrong rect
+ // Note: This issue is only seen in stacked charts
+ if (this.barCtx.isHorizontal) {
+ if (textRects.width / 1.6 > Math.abs(barWidth)) {
+ text = ''
+ }
+ } else {
+ if (textRects.height / 1.6 > Math.abs(barHeight)) {
+ text = ''
+ }
+ }
+ }
+
+ let modifiedDataLabelsConfig = {
+ ...dataLabelsConfig,
+ }
+ if (this.barCtx.isHorizontal) {
+ if (val < 0) {
+ if (dataLabelsConfig.textAnchor === 'start') {
+ modifiedDataLabelsConfig.textAnchor = 'end'
+ } else if (dataLabelsConfig.textAnchor === 'end') {
+ modifiedDataLabelsConfig.textAnchor = 'start'
+ }
+ }
+ }
+
+ dataLabels.plotDataLabelsText({
+ x,
+ y,
+ text,
+ i,
+ j,
+ parent: elDataLabelsWrap,
+ dataLabelsConfig: modifiedDataLabelsConfig,
+ alwaysDrawDataLabel: true,
+ offsetCorrection: true,
+ })
+ }
+
+ return elDataLabelsWrap
+ }
+
+ drawTotalDataLabels({
+ x,
+ y,
+ val,
+ realIndex,
+ textAnchor,
+ barTotalDataLabelsConfig,
+ }) {
+ const w = this.w
+ const graphics = new Graphics(this.barCtx.ctx)
+
+ let totalDataLabelText
+
+ if (
+ barTotalDataLabelsConfig.enabled &&
+ typeof x !== 'undefined' &&
+ typeof y !== 'undefined' &&
+ this.barCtx.lastActiveBarSerieIndex === realIndex
+ ) {
+ totalDataLabelText = graphics.drawText({
+ x: x,
+ y: y,
+ foreColor: barTotalDataLabelsConfig.style.color,
+ text: val,
+ textAnchor,
+ fontFamily: barTotalDataLabelsConfig.style.fontFamily,
+ fontSize: barTotalDataLabelsConfig.style.fontSize,
+ fontWeight: barTotalDataLabelsConfig.style.fontWeight,
+ })
+ }
+
+ return totalDataLabelText
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/bar/Helpers.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/bar/Helpers.js
new file mode 100644
index 0000000..e216c08
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/bar/Helpers.js
@@ -0,0 +1,806 @@
+import Fill from '../../../modules/Fill'
+import Graphics from '../../../modules/Graphics'
+import Series from '../../../modules/Series'
+import Utils from '../../../utils/Utils'
+
+export default class Helpers {
+ constructor(barCtx) {
+ this.w = barCtx.w
+ this.barCtx = barCtx
+ }
+
+ initVariables(series) {
+ const w = this.w
+ this.barCtx.series = series
+ this.barCtx.totalItems = 0
+ this.barCtx.seriesLen = 0
+ this.barCtx.visibleI = -1 // visible Series
+ this.barCtx.visibleItems = 1 // number of visible bars after user zoomed in/out
+
+ for (let sl = 0; sl < series.length; sl++) {
+ if (series[sl].length > 0) {
+ this.barCtx.seriesLen = this.barCtx.seriesLen + 1
+ this.barCtx.totalItems += series[sl].length
+ }
+ if (w.globals.isXNumeric) {
+ // get max visible items
+ for (let j = 0; j < series[sl].length; j++) {
+ if (
+ w.globals.seriesX[sl][j] > w.globals.minX &&
+ w.globals.seriesX[sl][j] < w.globals.maxX
+ ) {
+ this.barCtx.visibleItems++
+ }
+ }
+ } else {
+ this.barCtx.visibleItems = w.globals.dataPoints
+ }
+ }
+
+ this.arrBorderRadius = this.createBorderRadiusArr(w.globals.series)
+
+ if (this.barCtx.seriesLen === 0) {
+ // A small adjustment when combo charts are used
+ this.barCtx.seriesLen = 1
+ }
+ this.barCtx.zeroSerieses = []
+
+ if (!w.globals.comboCharts) {
+ this.checkZeroSeries({ series })
+ }
+ }
+
+ initialPositions() {
+ let w = this.w
+ let x, y, yDivision, xDivision, barHeight, barWidth, zeroH, zeroW
+
+ let dataPoints = w.globals.dataPoints
+ if (this.barCtx.isRangeBar) {
+ // timeline rangebar chart
+ dataPoints = w.globals.labels.length
+ }
+
+ let seriesLen = this.barCtx.seriesLen
+ if (w.config.plotOptions.bar.rangeBarGroupRows) {
+ seriesLen = 1
+ }
+
+ if (this.barCtx.isHorizontal) {
+ // height divided into equal parts
+ yDivision = w.globals.gridHeight / dataPoints
+ barHeight = yDivision / seriesLen
+
+ if (w.globals.isXNumeric) {
+ yDivision = w.globals.gridHeight / this.barCtx.totalItems
+ barHeight = yDivision / this.barCtx.seriesLen
+ }
+
+ barHeight =
+ (barHeight * parseInt(this.barCtx.barOptions.barHeight, 10)) / 100
+
+ if (String(this.barCtx.barOptions.barHeight).indexOf('%') === -1) {
+ barHeight = parseInt(this.barCtx.barOptions.barHeight, 10)
+ }
+
+ zeroW =
+ this.barCtx.baseLineInvertedY +
+ w.globals.padHorizontal +
+ (this.barCtx.isReversed ? w.globals.gridWidth : 0) -
+ (this.barCtx.isReversed ? this.barCtx.baseLineInvertedY * 2 : 0)
+
+ if (this.barCtx.isFunnel) {
+ zeroW = w.globals.gridWidth / 2
+ }
+ y = (yDivision - barHeight * this.barCtx.seriesLen) / 2
+ } else {
+ // width divided into equal parts
+ xDivision = w.globals.gridWidth / this.barCtx.visibleItems
+ if (w.config.xaxis.convertedCatToNumeric) {
+ xDivision = w.globals.gridWidth / w.globals.dataPoints
+ }
+ barWidth =
+ ((xDivision / seriesLen) *
+ parseInt(this.barCtx.barOptions.columnWidth, 10)) /
+ 100
+
+ if (w.globals.isXNumeric) {
+ // max barwidth should be equal to minXDiff to avoid overlap
+ let xRatio = this.barCtx.xRatio
+
+ if (
+ w.globals.minXDiff &&
+ w.globals.minXDiff !== 0.5 &&
+ w.globals.minXDiff / xRatio > 0
+ ) {
+ xDivision = w.globals.minXDiff / xRatio
+ }
+
+ barWidth =
+ ((xDivision / seriesLen) *
+ parseInt(this.barCtx.barOptions.columnWidth, 10)) /
+ 100
+
+ if (barWidth < 1) {
+ barWidth = 1
+ }
+ }
+ if (String(this.barCtx.barOptions.columnWidth).indexOf('%') === -1) {
+ barWidth = parseInt(this.barCtx.barOptions.columnWidth, 10)
+ }
+
+ zeroH =
+ w.globals.gridHeight -
+ this.barCtx.baseLineY[this.barCtx.translationsIndex] -
+ (this.barCtx.isReversed ? w.globals.gridHeight : 0) +
+ (this.barCtx.isReversed
+ ? this.barCtx.baseLineY[this.barCtx.translationsIndex] * 2
+ : 0)
+
+ x =
+ w.globals.padHorizontal +
+ (xDivision - barWidth * this.barCtx.seriesLen) / 2
+ }
+
+ w.globals.barHeight = barHeight
+ w.globals.barWidth = barWidth
+
+ return {
+ x,
+ y,
+ yDivision,
+ xDivision,
+ barHeight,
+ barWidth,
+ zeroH,
+ zeroW,
+ }
+ }
+
+ initializeStackedPrevVars(ctx) {
+ const w = ctx.w
+ w.globals.seriesGroups.forEach((group) => {
+ if (!ctx[group]) ctx[group] = {}
+
+ ctx[group].prevY = []
+ ctx[group].prevX = []
+ ctx[group].prevYF = []
+ ctx[group].prevXF = []
+ ctx[group].prevYVal = []
+ ctx[group].prevXVal = []
+ })
+ }
+
+ initializeStackedXYVars(ctx) {
+ const w = ctx.w
+
+ w.globals.seriesGroups.forEach((group) => {
+ if (!ctx[group]) ctx[group] = {}
+
+ ctx[group].xArrj = []
+ ctx[group].xArrjF = []
+ ctx[group].xArrjVal = []
+ ctx[group].yArrj = []
+ ctx[group].yArrjF = []
+ ctx[group].yArrjVal = []
+ })
+ }
+
+ getPathFillColor(series, i, j, realIndex) {
+ const w = this.w
+ let fill = this.barCtx.ctx.fill
+
+ let fillColor = null
+ let seriesNumber = this.barCtx.barOptions.distributed ? j : i
+
+ if (this.barCtx.barOptions.colors.ranges.length > 0) {
+ const colorRange = this.barCtx.barOptions.colors.ranges
+ colorRange.map((range) => {
+ if (series[i][j] >= range.from && series[i][j] <= range.to) {
+ fillColor = range.color
+ }
+ })
+ }
+
+ if (w.config.series[i].data[j]?.fillColor) {
+ fillColor = w.config.series[i].data[j].fillColor
+ }
+
+ let pathFill = fill.fillPath({
+ seriesNumber: this.barCtx.barOptions.distributed
+ ? seriesNumber
+ : realIndex,
+ dataPointIndex: j,
+ color: fillColor,
+ value: series[i][j],
+ fillConfig: w.config.series[i].data[j]?.fill,
+ fillType: w.config.series[i].data[j]?.fill?.type
+ ? w.config.series[i].data[j]?.fill.type
+ : Array.isArray(w.config.fill.type)
+ ? w.config.fill.type[realIndex]
+ : w.config.fill.type,
+ })
+
+ return pathFill
+ }
+
+ getStrokeWidth(i, j, realIndex) {
+ let strokeWidth = 0
+ const w = this.w
+
+ if (!this.barCtx.series[i][j]) {
+ this.barCtx.isNullValue = true
+ } else {
+ this.barCtx.isNullValue = false
+ }
+ if (w.config.stroke.show) {
+ if (!this.barCtx.isNullValue) {
+ strokeWidth = Array.isArray(this.barCtx.strokeWidth)
+ ? this.barCtx.strokeWidth[realIndex]
+ : this.barCtx.strokeWidth
+ }
+ }
+ return strokeWidth
+ }
+
+ createBorderRadiusArr(series) {
+ const w = this.w
+
+ const alwaysApplyRadius =
+ !this.w.config.chart.stacked ||
+ w.config.plotOptions.bar.borderRadiusWhenStacked !== 'last' ||
+ w.config.plotOptions.bar.borderRadius <= 0
+
+ const numSeries = series.length
+ const numColumns = series[0].length
+ const output = Array.from({ length: numSeries }, () =>
+ Array(numColumns).fill(alwaysApplyRadius ? 'top' : 'none')
+ )
+
+ if (alwaysApplyRadius) return output
+
+ for (let j = 0; j < numColumns; j++) {
+ let positiveIndices = []
+ let negativeIndices = []
+ let nonZeroCount = 0
+
+ // Collect positive and negative indices
+ for (let i = 0; i < numSeries; i++) {
+ const value = series[i][j]
+ if (value > 0) {
+ positiveIndices.push(i)
+ nonZeroCount++
+ } else if (value < 0) {
+ negativeIndices.push(i)
+ nonZeroCount++
+ }
+ }
+
+ if (positiveIndices.length > 0 && negativeIndices.length === 0) {
+ // Only positive values in this column
+ if (positiveIndices.length === 1) {
+ // Single positive value
+ output[positiveIndices[0]][j] = 'both'
+ } else {
+ // Multiple positive values
+ const firstPositiveIndex = positiveIndices[0]
+ const lastPositiveIndex = positiveIndices[positiveIndices.length - 1]
+ for (let i of positiveIndices) {
+ if (i === firstPositiveIndex) {
+ output[i][j] = 'bottom'
+ } else if (i === lastPositiveIndex) {
+ output[i][j] = 'top'
+ } else {
+ output[i][j] = 'none'
+ }
+ }
+ }
+ } else if (negativeIndices.length > 0 && positiveIndices.length === 0) {
+ // Only negative values in this column
+ if (negativeIndices.length === 1) {
+ // Single negative value
+ output[negativeIndices[0]][j] = 'both'
+ } else {
+ // Multiple negative values
+ const firstNegativeIndex = negativeIndices[0]
+ const lastNegativeIndex = negativeIndices[negativeIndices.length - 1]
+ for (let i of negativeIndices) {
+ if (i === firstNegativeIndex) {
+ output[i][j] = 'bottom'
+ } else if (i === lastNegativeIndex) {
+ output[i][j] = 'top'
+ } else {
+ output[i][j] = 'none'
+ }
+ }
+ }
+ } else if (positiveIndices.length > 0 && negativeIndices.length > 0) {
+ // Mixed positive and negative values
+ // Assign 'top' to the last positive bar
+ const lastPositiveIndex = positiveIndices[positiveIndices.length - 1]
+ for (let i of positiveIndices) {
+ if (i === lastPositiveIndex) {
+ output[i][j] = 'top'
+ } else {
+ output[i][j] = 'none'
+ }
+ }
+ // Assign 'bottom' to the last negative bar (closest to axis)
+ const lastNegativeIndex = negativeIndices[negativeIndices.length - 1]
+ for (let i of negativeIndices) {
+ if (i === lastNegativeIndex) {
+ output[i][j] = 'bottom'
+ } else {
+ output[i][j] = 'none'
+ }
+ }
+ } else if (nonZeroCount === 1) {
+ // Only one non-zero value (either positive or negative)
+ const index = positiveIndices[0] || negativeIndices[0]
+ output[index][j] = 'both'
+ }
+ }
+
+ return output
+ }
+
+ barBackground({ j, i, x1, x2, y1, y2, elSeries }) {
+ const w = this.w
+ const graphics = new Graphics(this.barCtx.ctx)
+
+ const sr = new Series(this.barCtx.ctx)
+ let activeSeriesIndex = sr.getActiveConfigSeriesIndex()
+
+ if (
+ this.barCtx.barOptions.colors.backgroundBarColors.length > 0 &&
+ activeSeriesIndex === i
+ ) {
+ if (j >= this.barCtx.barOptions.colors.backgroundBarColors.length) {
+ j %= this.barCtx.barOptions.colors.backgroundBarColors.length
+ }
+
+ let bcolor = this.barCtx.barOptions.colors.backgroundBarColors[j]
+ let rect = graphics.drawRect(
+ typeof x1 !== 'undefined' ? x1 : 0,
+ typeof y1 !== 'undefined' ? y1 : 0,
+ typeof x2 !== 'undefined' ? x2 : w.globals.gridWidth,
+ typeof y2 !== 'undefined' ? y2 : w.globals.gridHeight,
+ this.barCtx.barOptions.colors.backgroundBarRadius,
+ bcolor,
+ this.barCtx.barOptions.colors.backgroundBarOpacity
+ )
+ elSeries.add(rect)
+ rect.node.classList.add('apexcharts-backgroundBar')
+ }
+ }
+
+ getColumnPaths({
+ barWidth,
+ barXPosition,
+ y1,
+ y2,
+ strokeWidth,
+ isReversed,
+ series,
+ seriesGroup,
+ realIndex,
+ i,
+ j,
+ w,
+ }) {
+ const graphics = new Graphics(this.barCtx.ctx)
+ strokeWidth = Array.isArray(strokeWidth)
+ ? strokeWidth[realIndex]
+ : strokeWidth
+ if (!strokeWidth) strokeWidth = 0
+
+ let bW = barWidth
+ let bXP = barXPosition
+
+ if (w.config.series[realIndex].data[j]?.columnWidthOffset) {
+ bXP =
+ barXPosition - w.config.series[realIndex].data[j].columnWidthOffset / 2
+ bW = barWidth + w.config.series[realIndex].data[j].columnWidthOffset
+ }
+
+ // Center the stroke on the coordinates
+ let strokeCenter = strokeWidth / 2
+
+ const x1 = bXP + strokeCenter
+ const x2 = bXP + bW - strokeCenter
+
+ let direction = (series[i][j] >= 0 ? 1 : -1) * (isReversed ? -1 : 1)
+
+ // append tiny pixels to avoid exponentials (which cause issues in border-radius)
+ y1 += 0.001 - strokeCenter * direction
+ y2 += 0.001 + strokeCenter * direction
+
+ let pathTo = graphics.move(x1, y1)
+ let pathFrom = graphics.move(x1, y1)
+
+ const sl = graphics.line(x2, y1)
+ if (w.globals.previousPaths.length > 0) {
+ pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
+ }
+
+ pathTo =
+ pathTo +
+ graphics.line(x1, y2) +
+ graphics.line(x2, y2) +
+ sl +
+ (w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
+ this.arrBorderRadius[realIndex][j] === 'both'
+ ? ' Z'
+ : ' z')
+
+ // the lines in pathFrom are repeated to equal it to the points of pathTo
+ // this is to avoid weird animation (bug in svg.js)
+ pathFrom =
+ pathFrom +
+ graphics.line(x1, y1) +
+ sl +
+ sl +
+ sl +
+ sl +
+ sl +
+ graphics.line(x1, y1) +
+ (w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
+ this.arrBorderRadius[realIndex][j] === 'both'
+ ? ' Z'
+ : ' z')
+
+ if (this.arrBorderRadius[realIndex][j] !== 'none') {
+ pathTo = graphics.roundPathCorners(
+ pathTo,
+ w.config.plotOptions.bar.borderRadius
+ )
+ }
+
+ if (w.config.chart.stacked) {
+ let _ctx = this.barCtx
+ _ctx = this.barCtx[seriesGroup]
+ _ctx.yArrj.push(y2 - strokeCenter * direction)
+ _ctx.yArrjF.push(Math.abs(y1 - y2 + strokeWidth * direction))
+ _ctx.yArrjVal.push(this.barCtx.series[i][j])
+ }
+
+ return {
+ pathTo,
+ pathFrom,
+ }
+ }
+
+ getBarpaths({
+ barYPosition,
+ barHeight,
+ x1,
+ x2,
+ strokeWidth,
+ isReversed,
+ series,
+ seriesGroup,
+ realIndex,
+ i,
+ j,
+ w,
+ }) {
+ const graphics = new Graphics(this.barCtx.ctx)
+ strokeWidth = Array.isArray(strokeWidth)
+ ? strokeWidth[realIndex]
+ : strokeWidth
+ if (!strokeWidth) strokeWidth = 0
+
+ let bYP = barYPosition
+ let bH = barHeight
+
+ if (w.config.series[realIndex].data[j]?.barHeightOffset) {
+ bYP =
+ barYPosition - w.config.series[realIndex].data[j].barHeightOffset / 2
+ bH = barHeight + w.config.series[realIndex].data[j].barHeightOffset
+ }
+
+ // Center the stroke on the coordinates
+ let strokeCenter = strokeWidth / 2
+
+ const y1 = bYP + strokeCenter
+ const y2 = bYP + bH - strokeCenter
+
+ let direction = (series[i][j] >= 0 ? 1 : -1) * (isReversed ? -1 : 1)
+
+ // append tiny pixels to avoid exponentials (which cause issues in border-radius)
+ x1 += 0.001 + strokeCenter * direction
+ x2 += 0.001 - strokeCenter * direction
+
+ let pathTo = graphics.move(x1, y1)
+ let pathFrom = graphics.move(x1, y1)
+
+ if (w.globals.previousPaths.length > 0) {
+ pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
+ }
+
+ const sl = graphics.line(x1, y2)
+ pathTo =
+ pathTo +
+ graphics.line(x2, y1) +
+ graphics.line(x2, y2) +
+ sl +
+ (w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
+ this.arrBorderRadius[realIndex][j] === 'both'
+ ? ' Z'
+ : ' z')
+
+ pathFrom =
+ pathFrom +
+ graphics.line(x1, y1) +
+ sl +
+ sl +
+ sl +
+ sl +
+ sl +
+ graphics.line(x1, y1) +
+ (w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
+ this.arrBorderRadius[realIndex][j] === 'both'
+ ? ' Z'
+ : ' z')
+
+ if (this.arrBorderRadius[realIndex][j] !== 'none') {
+ pathTo = graphics.roundPathCorners(
+ pathTo,
+ w.config.plotOptions.bar.borderRadius
+ )
+ }
+
+ if (w.config.chart.stacked) {
+ let _ctx = this.barCtx
+ _ctx = this.barCtx[seriesGroup]
+ _ctx.xArrj.push(x2 + strokeCenter * direction)
+ _ctx.xArrjF.push(Math.abs(x1 - x2 - strokeWidth * direction))
+ _ctx.xArrjVal.push(this.barCtx.series[i][j])
+ }
+ return {
+ pathTo,
+ pathFrom,
+ }
+ }
+
+ checkZeroSeries({ series }) {
+ let w = this.w
+ for (let zs = 0; zs < series.length; zs++) {
+ let total = 0
+ for (
+ let zsj = 0;
+ zsj < series[w.globals.maxValsInArrayIndex].length;
+ zsj++
+ ) {
+ total += series[zs][zsj]
+ }
+ if (total === 0) {
+ this.barCtx.zeroSerieses.push(zs)
+ }
+ }
+ }
+
+ getXForValue(value, zeroW, zeroPositionForNull = true) {
+ let xForVal = zeroPositionForNull ? zeroW : null
+ if (typeof value !== 'undefined' && value !== null) {
+ xForVal =
+ zeroW +
+ value / this.barCtx.invertedYRatio -
+ (this.barCtx.isReversed ? value / this.barCtx.invertedYRatio : 0) * 2
+ }
+ return xForVal
+ }
+
+ getYForValue(value, zeroH, translationsIndex, zeroPositionForNull = true) {
+ let yForVal = zeroPositionForNull ? zeroH : null
+ if (typeof value !== 'undefined' && value !== null) {
+ yForVal =
+ zeroH -
+ value / this.barCtx.yRatio[translationsIndex] +
+ (this.barCtx.isReversed
+ ? value / this.barCtx.yRatio[translationsIndex]
+ : 0) *
+ 2
+ }
+ return yForVal
+ }
+
+ getGoalValues(type, zeroW, zeroH, i, j, translationsIndex) {
+ const w = this.w
+
+ let goals = []
+
+ const pushGoal = (value, attrs) => {
+ goals.push({
+ [type]:
+ type === 'x'
+ ? this.getXForValue(value, zeroW, false)
+ : this.getYForValue(value, zeroH, translationsIndex, false),
+ attrs,
+ })
+ }
+ if (
+ w.globals.seriesGoals[i] &&
+ w.globals.seriesGoals[i][j] &&
+ Array.isArray(w.globals.seriesGoals[i][j])
+ ) {
+ w.globals.seriesGoals[i][j].forEach((goal) => {
+ pushGoal(goal.value, goal)
+ })
+ }
+ if (this.barCtx.barOptions.isDumbbell && w.globals.seriesRange.length) {
+ let colors = this.barCtx.barOptions.dumbbellColors
+ ? this.barCtx.barOptions.dumbbellColors
+ : w.globals.colors
+ const commonAttrs = {
+ strokeHeight: type === 'x' ? 0 : w.globals.markers.size[i],
+ strokeWidth: type === 'x' ? w.globals.markers.size[i] : 0,
+ strokeDashArray: 0,
+ strokeLineCap: 'round',
+ strokeColor: Array.isArray(colors[i]) ? colors[i][0] : colors[i],
+ }
+
+ pushGoal(w.globals.seriesRangeStart[i][j], commonAttrs)
+ pushGoal(w.globals.seriesRangeEnd[i][j], {
+ ...commonAttrs,
+ strokeColor: Array.isArray(colors[i]) ? colors[i][1] : colors[i],
+ })
+ }
+ return goals
+ }
+
+ drawGoalLine({
+ barXPosition,
+ barYPosition,
+ goalX,
+ goalY,
+ barWidth,
+ barHeight,
+ }) {
+ let graphics = new Graphics(this.barCtx.ctx)
+ const lineGroup = graphics.group({
+ className: 'apexcharts-bar-goals-groups',
+ })
+
+ lineGroup.node.classList.add('apexcharts-element-hidden')
+ this.barCtx.w.globals.delayedElements.push({
+ el: lineGroup.node,
+ })
+
+ lineGroup.attr(
+ 'clip-path',
+ `url(#gridRectMarkerMask${this.barCtx.w.globals.cuid})`
+ )
+
+ let line = null
+ if (this.barCtx.isHorizontal) {
+ if (Array.isArray(goalX)) {
+ goalX.forEach((goal) => {
+ // Need a tiny margin of 1 each side so goals don't disappear at extremeties
+ if (goal.x >= -1 && goal.x <= graphics.w.globals.gridWidth + 1) {
+ let sHeight =
+ typeof goal.attrs.strokeHeight !== 'undefined'
+ ? goal.attrs.strokeHeight
+ : barHeight / 2
+ let y = barYPosition + sHeight + barHeight / 2
+
+ line = graphics.drawLine(
+ goal.x,
+ y - sHeight * 2,
+ goal.x,
+ y,
+ goal.attrs.strokeColor ? goal.attrs.strokeColor : undefined,
+ goal.attrs.strokeDashArray,
+ goal.attrs.strokeWidth ? goal.attrs.strokeWidth : 2,
+ goal.attrs.strokeLineCap
+ )
+ lineGroup.add(line)
+ }
+ })
+ }
+ } else {
+ if (Array.isArray(goalY)) {
+ goalY.forEach((goal) => {
+ // Need a tiny margin of 1 each side so goals don't disappear at extremeties
+ if (goal.y >= -1 && goal.y <= graphics.w.globals.gridHeight + 1) {
+ let sWidth =
+ typeof goal.attrs.strokeWidth !== 'undefined'
+ ? goal.attrs.strokeWidth
+ : barWidth / 2
+ let x = barXPosition + sWidth + barWidth / 2
+
+ line = graphics.drawLine(
+ x - sWidth * 2,
+ goal.y,
+ x,
+ goal.y,
+ goal.attrs.strokeColor ? goal.attrs.strokeColor : undefined,
+ goal.attrs.strokeDashArray,
+ goal.attrs.strokeHeight ? goal.attrs.strokeHeight : 2,
+ goal.attrs.strokeLineCap
+ )
+ lineGroup.add(line)
+ }
+ })
+ }
+ }
+
+ return lineGroup
+ }
+
+ drawBarShadow({ prevPaths, currPaths, color }) {
+ const w = this.w
+ const { x: prevX2, x1: prevX1, barYPosition: prevY1 } = prevPaths
+ const { x: currX2, x1: currX1, barYPosition: currY1 } = currPaths
+
+ const prevY2 = prevY1 + currPaths.barHeight
+
+ const graphics = new Graphics(this.barCtx.ctx)
+ const utils = new Utils()
+
+ const shadowPath =
+ graphics.move(prevX1, prevY2) +
+ graphics.line(prevX2, prevY2) +
+ graphics.line(currX2, currY1) +
+ graphics.line(currX1, currY1) +
+ graphics.line(prevX1, prevY2) +
+ (w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
+ this.arrBorderRadius[realIndex][j] === 'both'
+ ? ' Z'
+ : ' z')
+
+ return graphics.drawPath({
+ d: shadowPath,
+ fill: utils.shadeColor(0.5, Utils.rgb2hex(color)),
+ stroke: 'none',
+ strokeWidth: 0,
+ fillOpacity: 1,
+ classes: 'apexcharts-bar-shadows',
+ })
+ }
+
+ getZeroValueEncounters({ i, j }) {
+ const w = this.w
+
+ let nonZeroColumns = 0
+ let zeroEncounters = 0
+ let seriesIndices = w.config.plotOptions.bar.horizontal
+ ? w.globals.series.map((_, _i) => _i)
+ : w.globals.columnSeries?.i.map((_i) => _i) || []
+
+ seriesIndices.forEach((_si) => {
+ let val = w.globals.seriesPercent[_si][j]
+ if (val) {
+ nonZeroColumns++
+ }
+ if (_si < i && val === 0) {
+ zeroEncounters++
+ }
+ })
+
+ return {
+ nonZeroColumns,
+ zeroEncounters,
+ }
+ }
+
+ getGroupIndex(seriesIndex) {
+ const w = this.w
+ // groupIndex is the index of group buckets (group1, group2, ...)
+ let groupIndex = w.globals.seriesGroups.findIndex(
+ (group) =>
+ // w.config.series[i].name may be undefined, so use
+ // w.globals.seriesNames[i], which has default names for those
+ // series. w.globals.seriesGroups[] uses the same default naming.
+ group.indexOf(w.globals.seriesNames[seriesIndex]) > -1
+ )
+ // We need the column groups to be indexable as 0,1,2,... for their
+ // positioning relative to each other.
+ let cGI = this.barCtx.columnGroupIndices
+ let columnGroupIndex = cGI.indexOf(groupIndex)
+ if (columnGroupIndex < 0) {
+ cGI.push(groupIndex)
+ columnGroupIndex = cGI.length - 1
+ }
+ return { groupIndex, columnGroupIndex }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/circle/Helpers.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/circle/Helpers.js
new file mode 100644
index 0000000..522a353
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/circle/Helpers.js
@@ -0,0 +1,30 @@
+import Graphics from '../../../modules/Graphics'
+
+export default class CircularChartsHelpers {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ drawYAxisTexts(x, y, i, text) {
+ const w = this.w
+
+ const yaxisConfig = w.config.yaxis[0]
+ const formatter = w.globals.yLabelFormatters[0]
+
+ const graphics = new Graphics(this.ctx)
+ const yaxisLabel = graphics.drawText({
+ x: x + yaxisConfig.labels.offsetX,
+ y: y + yaxisConfig.labels.offsetY,
+ text: formatter(text, i),
+ textAnchor: 'middle',
+ fontSize: yaxisConfig.labels.style.fontSize,
+ fontFamily: yaxisConfig.labels.style.fontFamily,
+ foreColor: Array.isArray(yaxisConfig.labels.style.colors)
+ ? yaxisConfig.labels.style.colors[i]
+ : yaxisConfig.labels.style.colors
+ })
+
+ return yaxisLabel
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/line/Helpers.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/line/Helpers.js
new file mode 100644
index 0000000..43f517a
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/line/Helpers.js
@@ -0,0 +1,151 @@
+import CoreUtils from '../../../modules/CoreUtils'
+import Utils from '../../../utils/Utils'
+
+export default class Helpers {
+ constructor(lineCtx) {
+ this.w = lineCtx.w
+ this.lineCtx = lineCtx
+ }
+
+ sameValueSeriesFix(i, series) {
+ const w = this.w
+
+ if (
+ w.config.fill.type === 'gradient' ||
+ w.config.fill.type[i] === 'gradient'
+ ) {
+ const coreUtils = new CoreUtils(this.lineCtx.ctx, w)
+
+ // applied only to LINE chart
+ // a small adjustment to allow gradient line to draw correctly for all same values
+ /* #fix https://github.com/apexcharts/apexcharts.js/issues/358 */
+ if (coreUtils.seriesHaveSameValues(i)) {
+ let gSeries = series[i].slice()
+ gSeries[gSeries.length - 1] = gSeries[gSeries.length - 1] + 0.000001
+ series[i] = gSeries
+ }
+ }
+ return series
+ }
+
+ calculatePoints({ series, realIndex, x, y, i, j, prevY }) {
+ let w = this.w
+
+ let ptX = []
+ let ptY = []
+
+ if (j === 0) {
+ let xPT1st =
+ this.lineCtx.categoryAxisCorrection + w.config.markers.offsetX
+ // the first point for line series
+ // we need to check whether it's not a time series, because a time series may
+ // start from the middle of the x axis
+ if (w.globals.isXNumeric) {
+ xPT1st =
+ (w.globals.seriesX[realIndex][0] - w.globals.minX) /
+ this.lineCtx.xRatio +
+ w.config.markers.offsetX
+ }
+
+ // push 2 points for the first data values
+ ptX.push(xPT1st)
+ ptY.push(
+ Utils.isNumber(series[i][0]) ? prevY + w.config.markers.offsetY : null
+ )
+ ptX.push(x + w.config.markers.offsetX)
+ ptY.push(
+ Utils.isNumber(series[i][j + 1]) ? y + w.config.markers.offsetY : null
+ )
+ } else {
+ ptX.push(x + w.config.markers.offsetX)
+ ptY.push(
+ Utils.isNumber(series[i][j + 1]) ? y + w.config.markers.offsetY : null
+ )
+ }
+
+ let pointsPos = {
+ x: ptX,
+ y: ptY,
+ }
+
+ return pointsPos
+ }
+
+ checkPreviousPaths({ pathFromLine, pathFromArea, realIndex }) {
+ let w = this.w
+
+ for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
+ let gpp = w.globals.previousPaths[pp]
+
+ if (
+ (gpp.type === 'line' || gpp.type === 'area') &&
+ gpp.paths.length > 0 &&
+ parseInt(gpp.realIndex, 10) === parseInt(realIndex, 10)
+ ) {
+ if (gpp.type === 'line') {
+ this.lineCtx.appendPathFrom = false
+ pathFromLine = w.globals.previousPaths[pp].paths[0].d
+ } else if (gpp.type === 'area') {
+ this.lineCtx.appendPathFrom = false
+ pathFromArea = w.globals.previousPaths[pp].paths[0].d
+
+ if (w.config.stroke.show && w.globals.previousPaths[pp].paths[1]) {
+ pathFromLine = w.globals.previousPaths[pp].paths[1].d
+ }
+ }
+ }
+ }
+
+ return {
+ pathFromLine,
+ pathFromArea,
+ }
+ }
+
+ determineFirstPrevY({ i, realIndex, series, prevY, lineYPosition, translationsIndex }) {
+ let w = this.w
+ let stackSeries =
+ (w.config.chart.stacked && !w.globals.comboCharts) ||
+ (w.config.chart.stacked &&
+ w.globals.comboCharts &&
+ (!this.w.config.chart.stackOnlyBar ||
+ this.w.config.series[realIndex]?.type === 'bar'
+ || this.w.config.series[realIndex]?.type === 'column'))
+
+ if (typeof series[i]?.[0] !== 'undefined') {
+ if (stackSeries) {
+ if (i > 0) {
+ // 1st y value of previous series
+ lineYPosition = this.lineCtx.prevSeriesY[i - 1][0]
+ } else {
+ // the first series will not have prevY values
+ lineYPosition = this.lineCtx.zeroY
+ }
+ } else {
+ lineYPosition = this.lineCtx.zeroY
+ }
+ prevY =
+ lineYPosition -
+ series[i][0] / this.lineCtx.yRatio[translationsIndex] +
+ (this.lineCtx.isReversed
+ ? series[i][0] / this.lineCtx.yRatio[translationsIndex] : 0) * 2
+ } else {
+ // the first value in the current series is null
+ if (stackSeries && i > 0 && typeof series[i][0] === 'undefined') {
+ // check for undefined value (undefined value will occur when we clear the series while user clicks on legend to hide serieses)
+ for (let s = i - 1; s >= 0; s--) {
+ // for loop to get to 1st previous value until we get it
+ if (series[s][0] !== null && typeof series[s][0] !== 'undefined') {
+ lineYPosition = this.lineCtx.prevSeriesY[s][0]
+ prevY = lineYPosition
+ break
+ }
+ }
+ }
+ }
+ return {
+ prevY,
+ lineYPosition,
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/treemap/Helpers.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/treemap/Helpers.js
new file mode 100644
index 0000000..56af126
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/charts/common/treemap/Helpers.js
@@ -0,0 +1,200 @@
+import Utils from '../../../utils/Utils'
+import Graphics from '../../../modules/Graphics'
+import DataLabels from '../../../modules/DataLabels'
+
+export default class TreemapHelpers {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ checkColorRange() {
+ const w = this.w
+
+ let negRange = false
+ let chartOpts = w.config.plotOptions[w.config.chart.type]
+
+ if (chartOpts.colorScale.ranges.length > 0) {
+ chartOpts.colorScale.ranges.map((range, index) => {
+ if (range.from <= 0) {
+ negRange = true
+ }
+ })
+ }
+ return negRange
+ }
+
+ getShadeColor(chartType, i, j, negRange) {
+ const w = this.w
+
+ let colorShadePercent = 1
+ let shadeIntensity = w.config.plotOptions[chartType].shadeIntensity
+
+ const colorProps = this.determineColor(chartType, i, j)
+
+ if (w.globals.hasNegs || negRange) {
+ if (w.config.plotOptions[chartType].reverseNegativeShade) {
+ if (colorProps.percent < 0) {
+ colorShadePercent =
+ (colorProps.percent / 100) * (shadeIntensity * 1.25)
+ } else {
+ colorShadePercent =
+ (1 - colorProps.percent / 100) * (shadeIntensity * 1.25)
+ }
+ } else {
+ if (colorProps.percent <= 0) {
+ colorShadePercent =
+ 1 - (1 + colorProps.percent / 100) * shadeIntensity
+ } else {
+ colorShadePercent = (1 - colorProps.percent / 100) * shadeIntensity
+ }
+ }
+ } else {
+ colorShadePercent = 1 - colorProps.percent / 100
+ if (chartType === 'treemap') {
+ colorShadePercent =
+ (1 - colorProps.percent / 100) * (shadeIntensity * 1.25)
+ }
+ }
+
+ let color = colorProps.color
+ let utils = new Utils()
+
+ if (w.config.plotOptions[chartType].enableShades) {
+ // The shadeColor function may return either an RGB or a hex color value
+ // However, hexToRgba requires the input to be in hex format
+ // The ternary operator checks if the color is in RGB format, and if so, converts it to hex
+ if (this.w.config.theme.mode === 'dark') {
+ const shadeColor = utils.shadeColor(
+ colorShadePercent * -1,
+ colorProps.color
+ )
+ color = Utils.hexToRgba(
+ Utils.isColorHex(shadeColor) ? shadeColor : Utils.rgb2hex(shadeColor),
+ w.config.fill.opacity
+ )
+ } else {
+ const shadeColor = utils.shadeColor(colorShadePercent, colorProps.color)
+ color = Utils.hexToRgba(
+ Utils.isColorHex(shadeColor) ? shadeColor : Utils.rgb2hex(shadeColor),
+ w.config.fill.opacity
+ )
+ }
+ }
+
+ return { color, colorProps }
+ }
+
+ determineColor(chartType, i, j) {
+ const w = this.w
+
+ let val = w.globals.series[i][j]
+
+ let chartOpts = w.config.plotOptions[chartType]
+
+ let seriesNumber = chartOpts.colorScale.inverse ? j : i
+
+ if (chartOpts.distributed && w.config.chart.type === 'treemap') {
+ seriesNumber = j
+ }
+
+ let color = w.globals.colors[seriesNumber]
+ let foreColor = null
+ let min = Math.min(...w.globals.series[i])
+ let max = Math.max(...w.globals.series[i])
+
+ if (!chartOpts.distributed && chartType === 'heatmap') {
+ min = w.globals.minY
+ max = w.globals.maxY
+ }
+
+ if (typeof chartOpts.colorScale.min !== 'undefined') {
+ min =
+ chartOpts.colorScale.min < w.globals.minY
+ ? chartOpts.colorScale.min
+ : w.globals.minY
+ max =
+ chartOpts.colorScale.max > w.globals.maxY
+ ? chartOpts.colorScale.max
+ : w.globals.maxY
+ }
+
+ let total = Math.abs(max) + Math.abs(min)
+
+ let percent = (100 * val) / (total === 0 ? total - 0.000001 : total)
+
+ if (chartOpts.colorScale.ranges.length > 0) {
+ const colorRange = chartOpts.colorScale.ranges
+ colorRange.map((range, index) => {
+ if (val >= range.from && val <= range.to) {
+ color = range.color
+ foreColor = range.foreColor ? range.foreColor : null
+ min = range.from
+ max = range.to
+ let rTotal = Math.abs(max) + Math.abs(min)
+ percent = (100 * val) / (rTotal === 0 ? rTotal - 0.000001 : rTotal)
+ }
+ })
+ }
+
+ return {
+ color,
+ foreColor,
+ percent,
+ }
+ }
+
+ calculateDataLabels({ text, x, y, i, j, colorProps, fontSize }) {
+ let w = this.w
+ let dataLabelsConfig = w.config.dataLabels
+
+ const graphics = new Graphics(this.ctx)
+
+ let dataLabels = new DataLabels(this.ctx)
+
+ let elDataLabelsWrap = null
+
+ if (dataLabelsConfig.enabled) {
+ elDataLabelsWrap = graphics.group({
+ class: 'apexcharts-data-labels',
+ })
+
+ const offX = dataLabelsConfig.offsetX
+ const offY = dataLabelsConfig.offsetY
+
+ let dataLabelsX = x + offX
+ let dataLabelsY =
+ y + parseFloat(dataLabelsConfig.style.fontSize) / 3 + offY
+
+ dataLabels.plotDataLabelsText({
+ x: dataLabelsX,
+ y: dataLabelsY,
+ text,
+ i,
+ j,
+ color: colorProps.foreColor,
+ parent: elDataLabelsWrap,
+ fontSize,
+ dataLabelsConfig,
+ })
+ }
+
+ return elDataLabelsWrap
+ }
+
+ addListeners(elRect) {
+ const graphics = new Graphics(this.ctx)
+ elRect.node.addEventListener(
+ 'mouseenter',
+ graphics.pathMouseEnter.bind(this, elRect)
+ )
+ elRect.node.addEventListener(
+ 'mouseleave',
+ graphics.pathMouseLeave.bind(this, elRect)
+ )
+ elRect.node.addEventListener(
+ 'mousedown',
+ graphics.pathMouseDown.bind(this, elRect)
+ )
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/libs/Treemap-squared.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/libs/Treemap-squared.js
new file mode 100644
index 0000000..a9ae841
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/libs/Treemap-squared.js
@@ -0,0 +1,290 @@
+/*
+ * treemap-squarify.js - open source implementation of squarified treemaps
+ *
+ * Treemap Squared 0.5 - Treemap Charting library
+ *
+ * https://github.com/imranghory/treemap-squared/
+ *
+ * Copyright (c) 2012 Imran Ghory (imranghory@gmail.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ *
+ *
+ * Implementation of the squarify treemap algorithm described in:
+ *
+ * Bruls, Mark; Huizing, Kees; van Wijk, Jarke J. (2000), "Squarified treemaps"
+ * in de Leeuw, W.; van Liere, R., Data Visualization 2000:
+ * Proc. Joint Eurographics and IEEE TCVG Symp. on Visualization, Springer-Verlag, pp. 33–42.
+ *
+ * Paper is available online at: http://www.win.tue.nl/~vanwijk/stm.pdf
+ *
+ * The code in this file is completeley decoupled from the drawing code so it should be trivial
+ * to port it to any other vector drawing library. Given an array of datapoints this library returns
+ * an array of cartesian coordinates that represent the rectangles that make up the treemap.
+ *
+ * The library also supports multidimensional data (nested treemaps) and performs normalization on the data.
+ *
+ * See the README file for more details.
+ */
+
+window.TreemapSquared = {}
+;(function() {
+ 'use strict'
+ window.TreemapSquared.generate = (function() {
+ function Container(xoffset, yoffset, width, height) {
+ this.xoffset = xoffset // offset from the the top left hand corner
+ this.yoffset = yoffset // ditto
+ this.height = height
+ this.width = width
+
+ this.shortestEdge = function() {
+ return Math.min(this.height, this.width)
+ }
+
+ // getCoordinates - for a row of boxes which we've placed
+ // return an array of their cartesian coordinates
+ this.getCoordinates = function(row) {
+ let coordinates = []
+ let subxoffset = this.xoffset,
+ subyoffset = this.yoffset //our offset within the container
+ let areawidth = sumArray(row) / this.height
+ let areaheight = sumArray(row) / this.width
+ let i
+
+ if (this.width >= this.height) {
+ for (i = 0; i < row.length; i++) {
+ coordinates.push([
+ subxoffset,
+ subyoffset,
+ subxoffset + areawidth,
+ subyoffset + row[i] / areawidth
+ ])
+ subyoffset = subyoffset + row[i] / areawidth
+ }
+ } else {
+ for (i = 0; i < row.length; i++) {
+ coordinates.push([
+ subxoffset,
+ subyoffset,
+ subxoffset + row[i] / areaheight,
+ subyoffset + areaheight
+ ])
+ subxoffset = subxoffset + row[i] / areaheight
+ }
+ }
+ return coordinates
+ }
+
+ // cutArea - once we've placed some boxes into an row we then need to identify the remaining area,
+ // this function takes the area of the boxes we've placed and calculates the location and
+ // dimensions of the remaining space and returns a container box defined by the remaining area
+ this.cutArea = function(area) {
+ let newcontainer
+
+ if (this.width >= this.height) {
+ let areawidth = area / this.height
+ let newwidth = this.width - areawidth
+ newcontainer = new Container(
+ this.xoffset + areawidth,
+ this.yoffset,
+ newwidth,
+ this.height
+ )
+ } else {
+ let areaheight = area / this.width
+ let newheight = this.height - areaheight
+ newcontainer = new Container(
+ this.xoffset,
+ this.yoffset + areaheight,
+ this.width,
+ newheight
+ )
+ }
+ return newcontainer
+ }
+ }
+
+ // normalize - the Bruls algorithm assumes we're passing in areas that nicely fit into our
+ // container box, this method takes our raw data and normalizes the data values into
+ // area values so that this assumption is valid.
+ function normalize(data, area) {
+ let normalizeddata = []
+ let sum = sumArray(data)
+ let multiplier = area / sum
+ let i
+
+ for (i = 0; i < data.length; i++) {
+ normalizeddata[i] = data[i] * multiplier
+ }
+ return normalizeddata
+ }
+
+ // treemapMultidimensional - takes multidimensional data (aka [[23,11],[11,32]] - nested array)
+ // and recursively calls itself using treemapSingledimensional
+ // to create a patchwork of treemaps and merge them
+ function treemapMultidimensional(data, width, height, xoffset, yoffset) {
+ xoffset = typeof xoffset === 'undefined' ? 0 : xoffset
+ yoffset = typeof yoffset === 'undefined' ? 0 : yoffset
+
+ let mergeddata = []
+ let mergedtreemap
+ let results = []
+ let i
+
+ if (isArray(data[0])) {
+ // if we've got more dimensions of depth
+ for (i = 0; i < data.length; i++) {
+ mergeddata[i] = sumMultidimensionalArray(data[i])
+ }
+ mergedtreemap = treemapSingledimensional(
+ mergeddata,
+ width,
+ height,
+ xoffset,
+ yoffset
+ )
+
+ for (i = 0; i < data.length; i++) {
+ results.push(
+ treemapMultidimensional(
+ data[i],
+ mergedtreemap[i][2] - mergedtreemap[i][0],
+ mergedtreemap[i][3] - mergedtreemap[i][1],
+ mergedtreemap[i][0],
+ mergedtreemap[i][1]
+ )
+ )
+ }
+ } else {
+ results = treemapSingledimensional(
+ data,
+ width,
+ height,
+ xoffset,
+ yoffset
+ )
+ }
+ return results
+ }
+
+ // treemapSingledimensional - simple wrapper around squarify
+ function treemapSingledimensional(data, width, height, xoffset, yoffset) {
+ xoffset = typeof xoffset === 'undefined' ? 0 : xoffset
+ yoffset = typeof yoffset === 'undefined' ? 0 : yoffset
+
+ let rawtreemap = squarify(
+ normalize(data, width * height),
+ [],
+ new Container(xoffset, yoffset, width, height),
+ []
+ )
+ return flattenTreemap(rawtreemap)
+ }
+
+ // flattenTreemap - squarify implementation returns an array of arrays of coordinates
+ // because we have a new array everytime we switch to building a new row
+ // this converts it into an array of coordinates.
+ function flattenTreemap(rawtreemap) {
+ let flattreemap = []
+ let i, j
+
+ for (i = 0; i < rawtreemap.length; i++) {
+ for (j = 0; j < rawtreemap[i].length; j++) {
+ flattreemap.push(rawtreemap[i][j])
+ }
+ }
+ return flattreemap
+ }
+
+ // squarify - as per the Bruls paper
+ // plus coordinates stack and containers so we get
+ // usable data out of it
+ function squarify(data, currentrow, container, stack) {
+ let length
+ let nextdatapoint
+ let newcontainer
+
+ if (data.length === 0) {
+ stack.push(container.getCoordinates(currentrow))
+ return
+ }
+
+ length = container.shortestEdge()
+ nextdatapoint = data[0]
+
+ if (improvesRatio(currentrow, nextdatapoint, length)) {
+ currentrow.push(nextdatapoint)
+ squarify(data.slice(1), currentrow, container, stack)
+ } else {
+ newcontainer = container.cutArea(sumArray(currentrow), stack)
+ stack.push(container.getCoordinates(currentrow))
+ squarify(data, [], newcontainer, stack)
+ }
+ return stack
+ }
+
+ // improveRatio - implements the worse calculation and comparision as given in Bruls
+ // (note the error in the original paper; fixed here)
+ function improvesRatio(currentrow, nextnode, length) {
+ let newrow
+
+ if (currentrow.length === 0) {
+ return true
+ }
+
+ newrow = currentrow.slice()
+ newrow.push(nextnode)
+
+ let currentratio = calculateRatio(currentrow, length)
+ let newratio = calculateRatio(newrow, length)
+
+ // the pseudocode in the Bruls paper has the direction of the comparison
+ // wrong, this is the correct one.
+ return currentratio >= newratio
+ }
+
+ // calculateRatio - calculates the maximum width to height ratio of the
+ // boxes in this row
+ function calculateRatio(row, length) {
+ let min = Math.min.apply(Math, row)
+ let max = Math.max.apply(Math, row)
+ let sum = sumArray(row)
+ return Math.max(
+ (Math.pow(length, 2) * max) / Math.pow(sum, 2),
+ Math.pow(sum, 2) / (Math.pow(length, 2) * min)
+ )
+ }
+
+ // isArray - checks if arr is an array
+ function isArray(arr) {
+ return arr && arr.constructor === Array
+ }
+
+ // sumArray - sums a single dimensional array
+ function sumArray(arr) {
+ let sum = 0
+ let i
+
+ for (i = 0; i < arr.length; i++) {
+ sum += arr[i]
+ }
+ return sum
+ }
+
+ // sumMultidimensionalArray - sums the values in a nested array (aka [[0,1],[[2,3]]])
+ function sumMultidimensionalArray(arr) {
+ let i,
+ total = 0
+
+ if (isArray(arr[0])) {
+ for (i = 0; i < arr.length; i++) {
+ total += sumMultidimensionalArray(arr[i])
+ }
+ } else {
+ total = sumArray(arr)
+ }
+ return total
+ }
+
+ return treemapMultidimensional
+ })()
+})()
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/libs/monotone-cubic.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/libs/monotone-cubic.js
new file mode 100644
index 0000000..9298565
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/libs/monotone-cubic.js
@@ -0,0 +1,186 @@
+/**
+ *
+ * @yr/monotone-cubic-spline (https://github.com/YR/monotone-cubic-spline)
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 yr.no
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/**
+ * Generate tangents for 'points'
+ * @param {Array} points
+ * @returns {Array}
+ */
+export const tangents = (points) => {
+ const m = finiteDifferences(points)
+ const n = points.length - 1
+
+ const ε = 1e-6
+
+ const tgts = []
+ let a, b, d, s
+
+ for (let i = 0; i < n; i++) {
+ d = slope(points[i], points[i + 1])
+
+ if (Math.abs(d) < ε) {
+ m[i] = m[i + 1] = 0
+ } else {
+ a = m[i] / d
+ b = m[i + 1] / d
+ s = a * a + b * b
+ if (s > 9) {
+ s = (d * 3) / Math.sqrt(s)
+ m[i] = s * a
+ m[i + 1] = s * b
+ }
+ }
+ }
+
+ for (let i = 0; i <= n; i++) {
+ s =
+ (points[Math.min(n, i + 1)][0] - points[Math.max(0, i - 1)][0]) /
+ (6 * (1 + m[i] * m[i]))
+ tgts.push([s || 0, m[i] * s || 0])
+ }
+
+ return tgts
+}
+
+/**
+ * Convert 'points' to svg path
+ * @param {Array} points
+ * @returns {String}
+ */
+export const svgPath = (points) => {
+ let p = ''
+
+ for (let i = 0; i < points.length; i++) {
+ const point = points[i]
+ const n = point.length
+
+ if (n > 4) {
+ p += `C${point[0]}, ${point[1]}`
+ p += `, ${point[2]}, ${point[3]}`
+ p += `, ${point[4]}, ${point[5]}`
+ } else if (n > 2) {
+ p += `S${point[0]}, ${point[1]}`
+ p += `, ${point[2]}, ${point[3]}`
+ }
+ }
+
+ return p
+}
+
+export const spline = {
+ /**
+ * Convert 'points' to bezier
+ * @param {Array} points
+ * @returns {Array}
+ */
+ points(points) {
+ const tgts = tangents(points)
+
+ const p = points[1]
+ const p0 = points[0]
+ const pts = []
+ const t = tgts[1]
+ const t0 = tgts[0]
+
+ // Add starting 'M' and 'C' points
+ pts.push(p0, [
+ p0[0] + t0[0],
+ p0[1] + t0[1],
+ p[0] - t[0],
+ p[1] - t[1],
+ p[0],
+ p[1],
+ ])
+
+ // Add 'S' points
+ for (let i = 2, n = tgts.length; i < n; i++) {
+ const p = points[i]
+ const t = tgts[i]
+
+ pts.push([p[0] - t[0], p[1] - t[1], p[0], p[1]])
+ }
+
+ return pts
+ },
+
+ /**
+ * Slice out a segment of 'points'
+ * @param {Array} points
+ * @param {Number} start
+ * @param {Number} end
+ * @returns {Array}
+ */
+ slice(points, start, end) {
+ const pts = points.slice(start, end)
+
+ if (start) {
+ // Add additional 'C' points
+ if (end - start > 1 && pts[1].length < 6) {
+ const n = pts[0].length
+
+ pts[1] = [
+ pts[0][n - 2] * 2 - pts[0][n - 4],
+ pts[0][n - 1] * 2 - pts[0][n - 3],
+ ].concat(pts[1])
+ }
+ // Remove control points for 'M'
+ pts[0] = pts[0].slice(-2)
+ }
+
+ return pts
+ },
+}
+
+/**
+ * Compute slope from point 'p0' to 'p1'
+ * @param {Array} p0
+ * @param {Array} p1
+ * @returns {Number}
+ */
+function slope(p0, p1) {
+ return (p1[1] - p0[1]) / (p1[0] - p0[0])
+}
+
+/**
+ * Compute three-point differences for 'points'
+ * @param {Array} points
+ * @returns {Array}
+ */
+function finiteDifferences(points) {
+ const m = []
+ let p0 = points[0]
+ let p1 = points[1]
+ let d = (m[0] = slope(p0, p1))
+ let i = 1
+
+ for (let n = points.length - 1; i < n; i++) {
+ p0 = p1
+ p1 = points[i + 1]
+ m[i] = (d + (d = slope(p0, p1))) * 0.5
+ }
+ m[i] = d
+
+ return m
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ar.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ar.json
new file mode 100644
index 0000000..f13eab3
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ar.json
@@ -0,0 +1,63 @@
+{
+"name": "ar",
+"options": {
+"months": [
+"يناير",
+"فبراير",
+"مارس",
+"أبريل",
+"مايو",
+"يونيو",
+"يوليو",
+"أغسطس",
+"سبتمبر",
+"أكتوبر",
+"نوفمبر",
+"ديسمبر"
+],
+"shortMonths": [
+"يناير",
+"فبراير",
+"مارس",
+"أبريل",
+"مايو",
+"يونيو",
+"يوليو",
+"أغسطس",
+"سبتمبر",
+"أكتوبر",
+"نوفمبر",
+"ديسمبر"
+],
+"days": [
+"الأحد",
+"الإثنين",
+"الثلاثاء",
+"الأربعاء",
+"الخميس",
+"الجمعة",
+"السبت"
+],
+"shortDays": [
+"أحد",
+"إثنين",
+"ثلاثاء",
+"أربعاء",
+"خميس",
+"جمعة",
+"سبت"
+],
+"toolbar": {
+"exportToSVG": "تحميل بصيغة SVG",
+"exportToPNG": "تحميل بصيغة PNG",
+"exportToCSV": "تحميل بصيغة CSV",
+"menu": "القائمة",
+"selection": "تحديد",
+"selectionZoom": "تكبير التحديد",
+"zoomIn": "تكبير",
+"zoomOut": "تصغير",
+"pan": "تحريك",
+"reset": "إعادة التعيين"
+}
+}
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/be-cyrl.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/be-cyrl.json
new file mode 100644
index 0000000..89805d3
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/be-cyrl.json
@@ -0,0 +1,55 @@
+{
+ "name": "be-cyrl",
+ "options": {
+ "months": [
+ "Студзень",
+ "Люты",
+ "Сакавік",
+ "Красавік",
+ "Травень",
+ "Чэрвень",
+ "Ліпень",
+ "Жнівень",
+ "Верасень",
+ "Кастрычнік",
+ "Лістапад",
+ "Сьнежань"
+ ],
+ "shortMonths": [
+ "Сту",
+ "Лют",
+ "Сак",
+ "Кра",
+ "Тра",
+ "Чэр",
+ "Ліп",
+ "Жні",
+ "Вер",
+ "Кас",
+ "Ліс",
+ "Сьн"
+ ],
+ "days": [
+ "Нядзеля",
+ "Панядзелак",
+ "Аўторак",
+ "Серада",
+ "Чацьвер",
+ "Пятніца",
+ "Субота"
+ ],
+ "shortDays": ["Нд", "Пн", "Аў", "Ср", "Чц", "Пт", "Сб"],
+ "toolbar": {
+ "exportToSVG": "Спампаваць SVG",
+ "exportToPNG": "Спампаваць PNG",
+ "exportToCSV": "Спампаваць CSV",
+ "menu": "Мэню",
+ "selection": "Вылучэньне",
+ "selectionZoom": "Вылучэньне з маштабаваньнем",
+ "zoomIn": "Наблізіць",
+ "zoomOut": "Аддаліць",
+ "pan": "Ссоўваньне",
+ "reset": "Скінуць маштабаваньне"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/be-latn.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/be-latn.json
new file mode 100644
index 0000000..864b47c
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/be-latn.json
@@ -0,0 +1,55 @@
+{
+ "name": "be-latn",
+ "options": {
+ "months": [
+ "Studzień",
+ "Luty",
+ "Sakavik",
+ "Krasavik",
+ "Travień",
+ "Červień",
+ "Lipień",
+ "Žnivień",
+ "Vierasień",
+ "Kastryčnik",
+ "Listapad",
+ "Śniežań"
+ ],
+ "shortMonths": [
+ "Stu",
+ "Lut",
+ "Sak",
+ "Kra",
+ "Tra",
+ "Čer",
+ "Lip",
+ "Žni",
+ "Vie",
+ "Kas",
+ "Lis",
+ "Śni"
+ ],
+ "days": [
+ "Niadziela",
+ "Paniadziełak",
+ "Aŭtorak",
+ "Sierada",
+ "Čaćvier",
+ "Piatnica",
+ "Subota"
+ ],
+ "shortDays": ["Nd", "Pn", "Aŭ", "Sr", "Čć", "Pt", "Sb"],
+ "toolbar": {
+ "exportToSVG": "Spampavać SVG",
+ "exportToPNG": "Spampavać PNG",
+ "exportToCSV": "Spampavać CSV",
+ "menu": "Meniu",
+ "selection": "Vyłučeńnie",
+ "selectionZoom": "Vyłučeńnie z maštabavańniem",
+ "zoomIn": "Nablizić",
+ "zoomOut": "Addalić",
+ "pan": "Ssoŭvańnie",
+ "reset": "Skinuć maštabavańnie"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ca.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ca.json
new file mode 100644
index 0000000..cef7d1a
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ca.json
@@ -0,0 +1,55 @@
+{
+ "name": "ca",
+ "options": {
+ "months": [
+ "Gener",
+ "Febrer",
+ "Març",
+ "Abril",
+ "Maig",
+ "Juny",
+ "Juliol",
+ "Agost",
+ "Setembre",
+ "Octubre",
+ "Novembre",
+ "Desembre"
+ ],
+ "shortMonths": [
+ "Gen.",
+ "Febr.",
+ "Març",
+ "Abr.",
+ "Maig",
+ "Juny",
+ "Jul.",
+ "Ag.",
+ "Set.",
+ "Oct.",
+ "Nov.",
+ "Des."
+ ],
+ "days": [
+ "Diumenge",
+ "Dilluns",
+ "Dimarts",
+ "Dimecres",
+ "Dijous",
+ "Divendres",
+ "Dissabte"
+ ],
+ "shortDays": ["Dg", "Dl", "Dt", "Dc", "Dj", "Dv", "Ds"],
+ "toolbar": {
+ "exportToSVG": "Descarregar SVG",
+ "exportToPNG": "Descarregar PNG",
+ "exportToCSV": "Descarregar CSV",
+ "menu": "Menú",
+ "selection": "Seleccionar",
+ "selectionZoom": "Seleccionar Zoom",
+ "zoomIn": "Augmentar",
+ "zoomOut": "Disminuir",
+ "pan": "Navegació",
+ "reset": "Reiniciar Zoom"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/cs.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/cs.json
new file mode 100644
index 0000000..b8d9d40
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/cs.json
@@ -0,0 +1,55 @@
+{
+ "name": "cs",
+ "options": {
+ "months": [
+ "Leden",
+ "Únor",
+ "Březen",
+ "Duben",
+ "Květen",
+ "Červen",
+ "Červenec",
+ "Srpen",
+ "Září",
+ "Říjen",
+ "Listopad",
+ "Prosinec"
+ ],
+ "shortMonths": [
+ "Led",
+ "Úno",
+ "Bře",
+ "Dub",
+ "Kvě",
+ "Čvn",
+ "Čvc",
+ "Srp",
+ "Zář",
+ "Říj",
+ "Lis",
+ "Pro"
+ ],
+ "days": [
+ "Neděle",
+ "Pondělí",
+ "Úterý",
+ "Středa",
+ "Čtvrtek",
+ "Pátek",
+ "Sobota"
+ ],
+ "shortDays": ["Ne", "Po", "Út", "St", "Čt", "Pá", "So"],
+ "toolbar": {
+ "exportToSVG": "Stáhnout SVG",
+ "exportToPNG": "Stáhnout PNG",
+ "exportToCSV": "Stáhnout CSV",
+ "menu": "Menu",
+ "selection": "Vybrat",
+ "selectionZoom": "Zoom: Vybrat",
+ "zoomIn": "Zoom: Přiblížit",
+ "zoomOut": "Zoom: Oddálit",
+ "pan": "Přesouvat",
+ "reset": "Resetovat"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/da.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/da.json
new file mode 100644
index 0000000..e6861c0
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/da.json
@@ -0,0 +1,55 @@
+{
+ "name": "da",
+ "options": {
+ "months": [
+ "januar",
+ "februar",
+ "marts",
+ "april",
+ "maj",
+ "juni",
+ "juli",
+ "august",
+ "september",
+ "oktober",
+ "november",
+ "december"
+ ],
+ "shortMonths": [
+ "jan",
+ "feb",
+ "mar",
+ "apr",
+ "maj",
+ "jun",
+ "jul",
+ "aug",
+ "sep",
+ "okt",
+ "nov",
+ "dec"
+ ],
+ "days": [
+ "Søndag",
+ "Mandag",
+ "Tirsdag",
+ "Onsdag",
+ "Torsdag",
+ "Fredag",
+ "Lørdag"
+ ],
+ "shortDays": ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør"],
+ "toolbar": {
+ "exportToSVG": "Download SVG",
+ "exportToPNG": "Download PNG",
+ "exportToCSV": "Download CSV",
+ "menu": "Menu",
+ "selection": "Valg",
+ "selectionZoom": "Zoom til valg",
+ "zoomIn": "Zoom ind",
+ "zoomOut": "Zoom ud",
+ "pan": "Panorér",
+ "reset": "Nulstil zoom"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/de.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/de.json
new file mode 100644
index 0000000..af625e3
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/de.json
@@ -0,0 +1,55 @@
+{
+ "name": "de",
+ "options": {
+ "months": [
+ "Januar",
+ "Februar",
+ "März",
+ "April",
+ "Mai",
+ "Juni",
+ "Juli",
+ "August",
+ "September",
+ "Oktober",
+ "November",
+ "Dezember"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mär",
+ "Apr",
+ "Mai",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dez"
+ ],
+ "days": [
+ "Sonntag",
+ "Montag",
+ "Dienstag",
+ "Mittwoch",
+ "Donnerstag",
+ "Freitag",
+ "Samstag"
+ ],
+ "shortDays": ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
+ "toolbar": {
+ "exportToSVG": "SVG speichern",
+ "exportToPNG": "PNG speichern",
+ "exportToCSV": "CSV speichern",
+ "menu": "Menü",
+ "selection": "Auswahl",
+ "selectionZoom": "Auswahl vergrößern",
+ "zoomIn": "Vergrößern",
+ "zoomOut": "Verkleinern",
+ "pan": "Verschieben",
+ "reset": "Zoom zurücksetzen"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/el.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/el.json
new file mode 100644
index 0000000..e547e54
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/el.json
@@ -0,0 +1,55 @@
+{
+ "name": "el",
+ "options": {
+ "months": [
+ "Ιανουάριος",
+ "Φεβρουάριος",
+ "Μάρτιος",
+ "Απρίλιος",
+ "Μάιος",
+ "Ιούνιος",
+ "Ιούλιος",
+ "Αύγουστος",
+ "Σεπτέμβριος",
+ "Οκτώβριος",
+ "Νοέμβριος",
+ "Δεκέμβριος"
+ ],
+ "shortMonths": [
+ "Ιαν",
+ "Φευ",
+ "Μαρ",
+ "Απρ",
+ "Μάι",
+ "Ιουν",
+ "Ιουλ",
+ "Αυγ",
+ "Σεπ",
+ "Οκτ",
+ "Νοε",
+ "Δεκ"
+ ],
+ "days": [
+ "Κυριακή",
+ "Δευτέρα",
+ "Τρίτη",
+ "Τετάρτη",
+ "Πέμπτη",
+ "Παρασκευή",
+ "Σάββατο"
+ ],
+ "shortDays": ["Κυρ", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ"],
+ "toolbar": {
+ "exportToSVG": "Λήψη SVG",
+ "exportToPNG": "Λήψη PNG",
+ "exportToCSV": "Λήψη CSV",
+ "menu": "Menu",
+ "selection": "Επιλογή",
+ "selectionZoom": "Μεγένθυση βάση επιλογής",
+ "zoomIn": "Μεγένθυνση",
+ "zoomOut": "Σμίκρυνση",
+ "pan": "Μετατόπιση",
+ "reset": "Επαναφορά μεγένθυνσης"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/en.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/en.json
new file mode 100644
index 0000000..7b12481
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/en.json
@@ -0,0 +1,55 @@
+{
+ "name": "en",
+ "options": {
+ "months": [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec"
+ ],
+ "days": [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday"
+ ],
+ "shortDays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
+ "toolbar": {
+ "exportToSVG": "Download SVG",
+ "exportToPNG": "Download PNG",
+ "exportToCSV": "Download CSV",
+ "menu": "Menu",
+ "selection": "Selection",
+ "selectionZoom": "Selection Zoom",
+ "zoomIn": "Zoom In",
+ "zoomOut": "Zoom Out",
+ "pan": "Panning",
+ "reset": "Reset Zoom"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/es.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/es.json
new file mode 100644
index 0000000..8c465f6
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/es.json
@@ -0,0 +1,55 @@
+{
+ "name": "es",
+ "options": {
+ "months": [
+ "Enero",
+ "Febrero",
+ "Marzo",
+ "Abril",
+ "Mayo",
+ "Junio",
+ "Julio",
+ "Agosto",
+ "Septiembre",
+ "Octubre",
+ "Noviembre",
+ "Diciembre"
+ ],
+ "shortMonths": [
+ "Ene",
+ "Feb",
+ "Mar",
+ "Abr",
+ "May",
+ "Jun",
+ "Jul",
+ "Ago",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dic"
+ ],
+ "days": [
+ "Domingo",
+ "Lunes",
+ "Martes",
+ "Miércoles",
+ "Jueves",
+ "Viernes",
+ "Sábado"
+ ],
+ "shortDays": ["Dom", "Lun", "Mar", "Mie", "Jue", "Vie", "Sab"],
+ "toolbar": {
+ "exportToSVG": "Descargar SVG",
+ "exportToPNG": "Descargar PNG",
+ "exportToCSV": "Descargar CSV",
+ "menu": "Menu",
+ "selection": "Seleccionar",
+ "selectionZoom": "Seleccionar Zoom",
+ "zoomIn": "Aumentar",
+ "zoomOut": "Disminuir",
+ "pan": "Navegación",
+ "reset": "Reiniciar Zoom"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/et.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/et.json
new file mode 100644
index 0000000..5aa5248
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/et.json
@@ -0,0 +1,63 @@
+{
+ "name": "et",
+ "options": {
+ "months": [
+ "jaanuar",
+ "veebruar",
+ "märts",
+ "aprill",
+ "mai",
+ "juuni",
+ "juuli",
+ "august",
+ "september",
+ "oktoober",
+ "november",
+ "detsember"
+ ],
+ "shortMonths": [
+ "jaan",
+ "veebr",
+ "märts",
+ "apr",
+ "mai",
+ "juuni",
+ "juuli",
+ "aug",
+ "sept",
+ "okt",
+ "nov",
+ "dets"
+ ],
+ "days": [
+ "pühapäev",
+ "esmaspäev",
+ "teisipäev",
+ "kolmapäev",
+ "neljapäev",
+ "reede",
+ "laupäev"
+ ],
+ "shortDays": [
+ "P",
+ "E",
+ "T",
+ "K",
+ "N",
+ "R",
+ "L"
+ ],
+ "toolbar": {
+ "exportToSVG": "Lae alla SVG",
+ "exportToPNG": "Lae alla PNG",
+ "exportToCSV": "Lae alla CSV",
+ "menu": "Menüü",
+ "selection": "Valik",
+ "selectionZoom": "Valiku suum",
+ "zoomIn": "Suurenda",
+ "zoomOut": "Vähenda",
+ "pan": "Panoraamimine",
+ "reset": "Lähtesta suum"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/fa.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/fa.json
new file mode 100644
index 0000000..a4c38f7
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/fa.json
@@ -0,0 +1,55 @@
+{
+ "name": "fa",
+ "options": {
+ "months": [
+ "فروردین",
+ "اردیبهشت",
+ "خرداد",
+ "تیر",
+ "مرداد",
+ "شهریور",
+ "مهر",
+ "آبان",
+ "آذر",
+ "دی",
+ "بهمن",
+ "اسفند"
+ ],
+ "shortMonths": [
+ "فرو",
+ "ارد",
+ "خرد",
+ "تیر",
+ "مرد",
+ "شهر",
+ "مهر",
+ "آبا",
+ "آذر",
+ "دی",
+ "بهمـ",
+ "اسفـ"
+ ],
+ "days": [
+ "یکشنبه",
+ "دوشنبه",
+ "سه شنبه",
+ "چهارشنبه",
+ "پنجشنبه",
+ "جمعه",
+ "شنبه"
+ ],
+ "shortDays": ["ی", "د", "س", "چ", "پ", "ج", "ش"],
+ "toolbar": {
+ "exportToSVG": "دانلود SVG",
+ "exportToPNG": "دانلود PNG",
+ "exportToCSV": "دانلود CSV",
+ "menu": "منو",
+ "selection": "انتخاب",
+ "selectionZoom": "بزرگنمایی انتخابی",
+ "zoomIn": "بزرگنمایی",
+ "zoomOut": "کوچکنمایی",
+ "pan": "پیمایش",
+ "reset": "بازنشانی بزرگنمایی"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/fi.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/fi.json
new file mode 100644
index 0000000..73df095
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/fi.json
@@ -0,0 +1,55 @@
+{
+ "name": "fi",
+ "options": {
+ "months": [
+ "Tammikuu",
+ "Helmikuu",
+ "Maaliskuu",
+ "Huhtikuu",
+ "Toukokuu",
+ "Kesäkuu",
+ "Heinäkuu",
+ "Elokuu",
+ "Syyskuu",
+ "Lokakuu",
+ "Marraskuu",
+ "Joulukuu"
+ ],
+ "shortMonths": [
+ "Tammi",
+ "Helmi",
+ "Maalis",
+ "Huhti",
+ "Touko",
+ "Kesä",
+ "Heinä",
+ "Elo",
+ "Syys",
+ "Loka",
+ "Marras",
+ "Joulu"
+ ],
+ "days": [
+ "Sunnuntai",
+ "Maanantai",
+ "Tiistai",
+ "Keskiviikko",
+ "Torstai",
+ "Perjantai",
+ "Lauantai"
+ ],
+ "shortDays": ["Su", "Ma", "Ti", "Ke", "To", "Pe", "La"],
+ "toolbar": {
+ "exportToSVG": "Lataa SVG",
+ "exportToPNG": "Lataa PNG",
+ "exportToCSV": "Lataa CSV",
+ "menu": "Valikko",
+ "selection": "Valinta",
+ "selectionZoom": "Valinnan zoomaus",
+ "zoomIn": "Lähennä",
+ "zoomOut": "Loitonna",
+ "pan": "Panoroi",
+ "reset": "Nollaa zoomaus"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/fr.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/fr.json
new file mode 100644
index 0000000..959ce0b
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/fr.json
@@ -0,0 +1,55 @@
+{
+ "name": "fr",
+ "options": {
+ "months": [
+ "janvier",
+ "février",
+ "mars",
+ "avril",
+ "mai",
+ "juin",
+ "juillet",
+ "août",
+ "septembre",
+ "octobre",
+ "novembre",
+ "décembre"
+ ],
+ "shortMonths": [
+ "janv.",
+ "févr.",
+ "mars",
+ "avr.",
+ "mai",
+ "juin",
+ "juill.",
+ "août",
+ "sept.",
+ "oct.",
+ "nov.",
+ "déc."
+ ],
+ "days": [
+ "dimanche",
+ "lundi",
+ "mardi",
+ "mercredi",
+ "jeudi",
+ "vendredi",
+ "samedi"
+ ],
+ "shortDays": ["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."],
+ "toolbar": {
+ "exportToSVG": "Télécharger au format SVG",
+ "exportToPNG": "Télécharger au format PNG",
+ "exportToCSV": "Télécharger au format CSV",
+ "menu": "Menu",
+ "selection": "Sélection",
+ "selectionZoom": "Sélection et zoom",
+ "zoomIn": "Zoomer",
+ "zoomOut": "Dézoomer",
+ "pan": "Navigation",
+ "reset": "Réinitialiser le zoom"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/he.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/he.json
new file mode 100644
index 0000000..bafff3e
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/he.json
@@ -0,0 +1,55 @@
+{
+ "name": "he",
+ "options": {
+ "months": [
+ "ינואר",
+ "פברואר",
+ "מרץ",
+ "אפריל",
+ "מאי",
+ "יוני",
+ "יולי",
+ "אוגוסט",
+ "ספטמבר",
+ "אוקטובר",
+ "נובמבר",
+ "דצמבר"
+ ],
+ "shortMonths": [
+ "ינו׳",
+ "פבר׳",
+ "מרץ",
+ "אפר׳",
+ "מאי",
+ "יוני",
+ "יולי",
+ "אוג׳",
+ "ספט׳",
+ "אוק׳",
+ "נוב׳",
+ "דצמ׳"
+ ],
+ "days": [
+ "ראשון",
+ "שני",
+ "שלישי",
+ "רביעי",
+ "חמישי",
+ "שישי",
+ "שבת"
+ ],
+ "shortDays": ["א׳", "ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳"],
+ "toolbar": {
+ "exportToSVG": "הורד SVG",
+ "exportToPNG": "הורד PNG",
+ "exportToCSV": "הורד CSV",
+ "menu": "תפריט",
+ "selection": "בחירה",
+ "selectionZoom": "זום בחירה",
+ "zoomIn": "הגדלה",
+ "zoomOut": "הקטנה",
+ "pan": "הזזה",
+ "reset": "איפוס תצוגה"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hi.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hi.json
new file mode 100644
index 0000000..2191342
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hi.json
@@ -0,0 +1,55 @@
+{
+ "name": "hi",
+ "options": {
+ "months": [
+ "जनवरी",
+ "फ़रवरी",
+ "मार्च",
+ "अप्रैल",
+ "मई",
+ "जून",
+ "जुलाई",
+ "अगस्त",
+ "सितंबर",
+ "अक्टूबर",
+ "नवंबर",
+ "दिसंबर"
+ ],
+ "shortMonths": [
+ "जनवरी",
+ "फ़रवरी",
+ "मार्च",
+ "अप्रैल",
+ "मई",
+ "जून",
+ "जुलाई",
+ "अगस्त",
+ "सितंबर",
+ "अक्टूबर",
+ "नवंबर",
+ "दिसंबर"
+ ],
+ "days": [
+ "रविवार",
+ "सोमवार",
+ "मंगलवार",
+ "बुधवार",
+ "गुरुवार",
+ "शुक्रवार",
+ "शनिवार"
+ ],
+ "shortDays": ["रवि", "सोम", "मंगल", "बुध", "गुरु", "शुक्र", "शनि"],
+ "toolbar": {
+ "exportToSVG": "निर्यात SVG",
+ "exportToPNG": "निर्यात PNG",
+ "exportToCSV": "निर्यात CSV",
+ "menu": "सूची",
+ "selection": "चयन",
+ "selectionZoom": "ज़ूम करना",
+ "zoomIn": "ज़ूम इन",
+ "zoomOut": "ज़ूम आउट",
+ "pan": "पैनिंग",
+ "reset": "फिर से कायम करना"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hr.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hr.json
new file mode 100644
index 0000000..52ab2fc
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hr.json
@@ -0,0 +1,55 @@
+{
+ "name": "hr",
+ "options": {
+ "months": [
+ "Siječanj",
+ "Veljača",
+ "Ožujak",
+ "Travanj",
+ "Svibanj",
+ "Lipanj",
+ "Srpanj",
+ "Kolovoz",
+ "Rujan",
+ "Listopad",
+ "Studeni",
+ "Prosinac"
+ ],
+ "shortMonths": [
+ "Sij",
+ "Velj",
+ "Ožu",
+ "Tra",
+ "Svi",
+ "Lip",
+ "Srp",
+ "Kol",
+ "Ruj",
+ "Lis",
+ "Stu",
+ "Pro"
+ ],
+ "days": [
+ "Nedjelja",
+ "Ponedjeljak",
+ "Utorak",
+ "Srijeda",
+ "Četvrtak",
+ "Petak",
+ "Subota"
+ ],
+ "shortDays": ["Ned", "Pon", "Uto", "Sri", "Čet", "Pet", "Sub"],
+ "toolbar": {
+ "exportToSVG": "Preuzmi SVG",
+ "exportToPNG": "Preuzmi PNG",
+ "exportToCSV": "Preuzmi CSV",
+ "menu": "Izbornik",
+ "selection": "Odabir",
+ "selectionZoom": "Odabirno povećanje",
+ "zoomIn": "Uvećajte prikaz",
+ "zoomOut": "Umanjite prikaz",
+ "pan": "Pomicanje",
+ "reset": "Povratak na zadani prikaz"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hu.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hu.json
new file mode 100644
index 0000000..04142a0
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hu.json
@@ -0,0 +1,64 @@
+{
+ "name": "hu",
+ "options": {
+ "months": [
+ "január",
+ "február",
+ "március",
+ "április",
+ "május",
+ "június",
+ "július",
+ "augusztus",
+ "szeptember",
+ "október",
+ "november",
+ "december"
+ ],
+ "shortMonths": [
+ "jan",
+ "feb",
+ "mar",
+ "ápr",
+ "máj",
+ "jún",
+ "júl",
+ "aug",
+ "szept",
+ "okt",
+ "nov",
+ "dec"
+ ],
+ "days": [
+ "hétfő",
+ "kedd",
+ "szerda",
+ "csütörtök",
+ "péntek",
+ "szombat",
+ "vasárnap"
+ ],
+ "shortDays": [
+ "H",
+ "K",
+ "Sze",
+ "Cs",
+ "P",
+ "Szo",
+ "V"
+ ],
+ "toolbar": {
+ "exportToSVG": "Exportálás SVG-be",
+ "exportToPNG": "Exportálás PNG-be",
+ "exportToCSV": "Exportálás CSV-be",
+ "menu": "Fő ajánlat",
+ "download": "SVG letöltése",
+ "selection": "Kiválasztás",
+ "selectionZoom": "Nagyító kiválasztása",
+ "zoomIn": "Nagyítás",
+ "zoomOut": "Kicsinyítés",
+ "pan": "Képcsúsztatás",
+ "reset": "Nagyító visszaállítása"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hy.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hy.json
new file mode 100644
index 0000000..cdbe469
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/hy.json
@@ -0,0 +1,55 @@
+{
+ "name": "hy",
+ "options": {
+ "months": [
+ "Հունվար",
+ "Փետրվար",
+ "Մարտ",
+ "Ապրիլ",
+ "Մայիս",
+ "Հունիս",
+ "Հուլիս",
+ "Օգոստոս",
+ "Սեպտեմբեր",
+ "Հոկտեմբեր",
+ "Նոյեմբեր",
+ "Դեկտեմբեր"
+ ],
+ "shortMonths": [
+ "Հնվ",
+ "Փտվ",
+ "Մրտ",
+ "Ապր",
+ "Մյս",
+ "Հնս",
+ "Հլիս",
+ "Օգս",
+ "Սեպ",
+ "Հոկ",
+ "Նոյ",
+ "Դեկ"
+ ],
+ "days": [
+ "Կիրակի",
+ "Երկուշաբթի",
+ "Երեքշաբթի",
+ "Չորեքշաբթի",
+ "Հինգշաբթի",
+ "Ուրբաթ",
+ "Շաբաթ"
+ ],
+ "shortDays": ["Կիր", "Երկ", "Երք", "Չրք", "Հնգ", "Ուրբ", "Շբթ"],
+ "toolbar": {
+ "exportToSVG": "Բեռնել SVG",
+ "exportToPNG": "Բեռնել PNG",
+ "exportToCSV": "Բեռնել CSV",
+ "menu": "Մենյու",
+ "selection": "Ընտրված",
+ "selectionZoom": "Ընտրված հատվածի խոշորացում",
+ "zoomIn": "Խոշորացնել",
+ "zoomOut": "Մանրացնել",
+ "pan": "Տեղափոխում",
+ "reset": "Բերել սկզբնական վիճակի"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/id.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/id.json
new file mode 100644
index 0000000..52a34b6
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/id.json
@@ -0,0 +1,47 @@
+{
+ "name": "id",
+ "options": {
+ "months": [
+ "Januari",
+ "Februari",
+ "Maret",
+ "April",
+ "Mei",
+ "Juni",
+ "Juli",
+ "Agustus",
+ "September",
+ "Oktober",
+ "November",
+ "Desember"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Mei",
+ "Jun",
+ "Jul",
+ "Agu",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Des"
+ ],
+ "days": ["Minggu", "Senin", "Selasa", "Rabu", "kamis", "Jumat", "Sabtu"],
+ "shortDays": ["Min", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab"],
+ "toolbar": {
+ "exportToSVG": "Unduh SVG",
+ "exportToPNG": "Unduh PNG",
+ "exportToCSV": "Unduh CSV",
+ "menu": "Menu",
+ "selection": "Pilihan",
+ "selectionZoom": "Perbesar Pilihan",
+ "zoomIn": "Perbesar",
+ "zoomOut": "Perkecil",
+ "pan": "Geser",
+ "reset": "Atur Ulang Zoom"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/it.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/it.json
new file mode 100644
index 0000000..7facfea
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/it.json
@@ -0,0 +1,55 @@
+{
+ "name": "it",
+ "options": {
+ "months": [
+ "Gennaio",
+ "Febbraio",
+ "Marzo",
+ "Aprile",
+ "Maggio",
+ "Giugno",
+ "Luglio",
+ "Agosto",
+ "Settembre",
+ "Ottobre",
+ "Novembre",
+ "Dicembre"
+ ],
+ "shortMonths": [
+ "Gen",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Mag",
+ "Giu",
+ "Lug",
+ "Ago",
+ "Set",
+ "Ott",
+ "Nov",
+ "Dic"
+ ],
+ "days": [
+ "Domenica",
+ "Lunedì",
+ "Martedì",
+ "Mercoledì",
+ "Giovedì",
+ "Venerdì",
+ "Sabato"
+ ],
+ "shortDays": ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"],
+ "toolbar": {
+ "exportToSVG": "Scarica SVG",
+ "exportToPNG": "Scarica PNG",
+ "exportToCSV": "Scarica CSV",
+ "menu": "Menu",
+ "selection": "Selezione",
+ "selectionZoom": "Seleziona Zoom",
+ "zoomIn": "Zoom In",
+ "zoomOut": "Zoom Out",
+ "pan": "Sposta",
+ "reset": "Reimposta Zoom"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ja.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ja.json
new file mode 100644
index 0000000..2b3af52
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ja.json
@@ -0,0 +1,55 @@
+{
+ "name": "ja",
+ "options": {
+ "months": [
+ "1月",
+ "2月",
+ "3月",
+ "4月",
+ "5月",
+ "6月",
+ "7月",
+ "8月",
+ "9月",
+ "10月",
+ "11月",
+ "12月"
+ ],
+ "shortMonths": [
+ "1月",
+ "2月",
+ "3月",
+ "4月",
+ "5月",
+ "6月",
+ "7月",
+ "8月",
+ "9月",
+ "10月",
+ "11月",
+ "12月"
+ ],
+ "days": [
+ "日曜日",
+ "月曜日",
+ "火曜日",
+ "水曜日",
+ "木曜日",
+ "金曜日",
+ "土曜日"
+ ],
+ "shortDays": ["日", "月", "火", "水", "木", "金", "土"],
+ "toolbar": {
+ "exportToSVG": "SVGダウンロード",
+ "exportToPNG": "PNGダウンロード",
+ "exportToCSV": "CSVダウンロード",
+ "menu": "メニュー",
+ "selection": "選択",
+ "selectionZoom": "選択ズーム",
+ "zoomIn": "拡大",
+ "zoomOut": "縮小",
+ "pan": "パン",
+ "reset": "ズームリセット"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ka.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ka.json
new file mode 100644
index 0000000..b3c8a0f
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ka.json
@@ -0,0 +1,55 @@
+{
+ "name": "ka",
+ "options": {
+ "months": [
+ "იანვარი",
+ "თებერვალი",
+ "მარტი",
+ "აპრილი",
+ "მაისი",
+ "ივნისი",
+ "ივლისი",
+ "აგვისტო",
+ "სექტემბერი",
+ "ოქტომბერი",
+ "ნოემბერი",
+ "დეკემბერი"
+ ],
+ "shortMonths": [
+ "იან",
+ "თებ",
+ "მარ",
+ "აპრ",
+ "მაი",
+ "ივნ",
+ "ივლ",
+ "აგვ",
+ "სექ",
+ "ოქტ",
+ "ნოე",
+ "დეკ"
+ ],
+ "days": [
+ "კვირა",
+ "ორშაბათი",
+ "სამშაბათი",
+ "ოთხშაბათი",
+ "ხუთშაბათი",
+ "პარასკევი",
+ "შაბათი"
+ ],
+ "shortDays": ["კვი", "ორშ", "სამ", "ოთხ", "ხუთ", "პარ", "შაბ"],
+ "toolbar": {
+ "exportToSVG": "გადმოქაჩე SVG",
+ "exportToPNG": "გადმოქაჩე PNG",
+ "exportToCSV": "გადმოქაჩე CSV",
+ "menu": "მენიუ",
+ "selection": "არჩევა",
+ "selectionZoom": "არჩეულის გადიდება",
+ "zoomIn": "გადიდება",
+ "zoomOut": "დაპატარაება",
+ "pan": "გადაჩოჩება",
+ "reset": "გადიდების გაუქმება"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ko.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ko.json
new file mode 100644
index 0000000..181196d
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ko.json
@@ -0,0 +1,55 @@
+{
+ "name": "ko",
+ "options": {
+ "months": [
+ "1월",
+ "2월",
+ "3월",
+ "4월",
+ "5월",
+ "6월",
+ "7월",
+ "8월",
+ "9월",
+ "10월",
+ "11월",
+ "12월"
+ ],
+ "shortMonths": [
+ "1월",
+ "2월",
+ "3월",
+ "4월",
+ "5월",
+ "6월",
+ "7월",
+ "8월",
+ "9월",
+ "10월",
+ "11월",
+ "12월"
+ ],
+ "days": [
+ "일요일",
+ "월요일",
+ "화요일",
+ "수요일",
+ "목요일",
+ "금요일",
+ "토요일"
+ ],
+ "shortDays": ["일", "월", "화", "수", "목", "금", "토"],
+ "toolbar": {
+ "exportToSVG": "SVG 다운로드",
+ "exportToPNG": "PNG 다운로드",
+ "exportToCSV": "CSV 다운로드",
+ "menu": "메뉴",
+ "selection": "선택",
+ "selectionZoom": "선택영역 확대",
+ "zoomIn": "확대",
+ "zoomOut": "축소",
+ "pan": "패닝",
+ "reset": "원래대로"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/lt.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/lt.json
new file mode 100644
index 0000000..4ed1520
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/lt.json
@@ -0,0 +1,55 @@
+{
+ "name": "lt",
+ "options": {
+ "months": [
+ "Sausis",
+ "Vasaris",
+ "Kovas",
+ "Balandis",
+ "Gegužė",
+ "Birželis",
+ "Liepa",
+ "Rugpjūtis",
+ "Rugsėjis",
+ "Spalis",
+ "Lapkritis",
+ "Gruodis"
+ ],
+ "shortMonths": [
+ "Sau",
+ "Vas",
+ "Kov",
+ "Bal",
+ "Geg",
+ "Bir",
+ "Lie",
+ "Rgp",
+ "Rgs",
+ "Spl",
+ "Lap",
+ "Grd"
+ ],
+ "days": [
+ "Sekmadienis",
+ "Pirmadienis",
+ "Antradienis",
+ "Trečiadienis",
+ "Ketvirtadienis",
+ "Penktadienis",
+ "Šeštadienis"
+ ],
+ "shortDays": ["Sk", "Per", "An", "Tr", "Kt", "Pn", "Št"],
+ "toolbar": {
+ "exportToSVG": "Atsisiųsti SVG",
+ "exportToPNG": "Atsisiųsti PNG",
+ "exportToCSV": "Atsisiųsti CSV",
+ "menu": "Menu",
+ "selection": "Pasirinkimas",
+ "selectionZoom": "Zoom: Pasirinkimas",
+ "zoomIn": "Zoom: Priartinti",
+ "zoomOut": "Zoom: Atitolinti",
+ "pan": "Perkėlimas",
+ "reset": "Atstatyti"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/lv.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/lv.json
new file mode 100644
index 0000000..8a845dd
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/lv.json
@@ -0,0 +1,64 @@
+{
+ "name": "lv",
+ "options": {
+ "months": [
+ "janvāris",
+ "februāris",
+ "marts",
+ "aprīlis",
+ "maijs",
+ "jūnijs",
+ "jūlijs",
+ "augusts",
+ "septembris",
+ "oktobris",
+ "novembris",
+ "decembris"
+ ],
+ "shortMonths": [
+ "janv",
+ "febr",
+ "marts",
+ "apr",
+ "maijs",
+ "jūn",
+ "jūl",
+ "aug",
+ "sept",
+ "okt",
+ "nov",
+ "dec"
+ ],
+ "days": [
+ "svētdiena",
+ "pirmdiena",
+ "otrdiena",
+ "trešdiena",
+ "ceturtdiena",
+ "piektdiena",
+ "sestdiena"
+ ],
+ "shortDays": [
+ "Sv",
+ "P",
+ "O",
+ "T",
+ "C",
+ "P",
+ "S"
+ ],
+ "toolbar": {
+ "exportToSVG": "Lejuplādēt SVG",
+ "exportToPNG": "Lejuplādēt PNG",
+ "exportToCSV": "Lejuplādēt CSV",
+ "menu": "Izvēlne",
+ "selection": "Atlase",
+ "selectionZoom": "Pietuvināt atlasi",
+ "zoomIn": "Pietuvināt",
+ "zoomOut": "Attālināt",
+ "pan": "Pārvietoties diagrammā",
+ "reset": "Atiestatīt pietuvinājumu"
+ }
+ }
+}
+
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ms.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ms.json
new file mode 100644
index 0000000..eef8ca2
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ms.json
@@ -0,0 +1,63 @@
+{
+ "name": "ms",
+ "options": {
+ "months": [
+ "Januari",
+ "Februari",
+ "Mac",
+ "April",
+ "Mei",
+ "Jun",
+ "Julai",
+ "Ogos",
+ "September",
+ "Oktober",
+ "November",
+ "Disember"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mac",
+ "Apr",
+ "Mei",
+ "Jun",
+ "Jul",
+ "Ogos",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dis"
+ ],
+ "days": [
+ "Ahad",
+ "Isnin",
+ "Selasa",
+ "Rabu",
+ "Khamis",
+ "Jumaat",
+ "Sabtu"
+ ],
+ "shortDays": [
+ "Ahd",
+ "Isn",
+ "Sel",
+ "Rab",
+ "Kha",
+ "Jum",
+ "Sab"
+ ],
+ "toolbar": {
+ "exportToSVG": "Muat turun SVG",
+ "exportToPNG": "Muat turun PNG",
+ "exportToCSV": "Muat turun CSV",
+ "menu": "Menu",
+ "selection": "Pilihan",
+ "selectionZoom": "Zum Pilihan",
+ "zoomIn": "Zoom Masuk",
+ "zoomOut": "Zoom Keluar",
+ "pan": "Pemusingan",
+ "reset": "Tetapkan Semula Zum"
+ }
+ }
+}
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/nb.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/nb.json
new file mode 100644
index 0000000..3339d2c
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/nb.json
@@ -0,0 +1,55 @@
+{
+ "name": "nb",
+ "options": {
+ "months": [
+ "Januar",
+ "Februar",
+ "Mars",
+ "April",
+ "Mai",
+ "Juni",
+ "Juli",
+ "August",
+ "September",
+ "Oktober",
+ "November",
+ "Desember"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Mai",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Des"
+ ],
+ "days": [
+ "Søndag",
+ "Mandag",
+ "Tirsdag",
+ "Onsdag",
+ "Torsdag",
+ "Fredag",
+ "Lørdag"
+ ],
+ "shortDays": ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø"],
+ "toolbar": {
+ "exportToSVG": "Last ned SVG",
+ "exportToPNG": "Last ned PNG",
+ "exportToCSV": "Last ned CSV",
+ "menu": "Menu",
+ "selection": "Velg",
+ "selectionZoom": "Zoom: Velg",
+ "zoomIn": "Zoome inn",
+ "zoomOut": "Zoome ut",
+ "pan": "Skyving",
+ "reset": "Start på nytt"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/nl.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/nl.json
new file mode 100644
index 0000000..0c2126a
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/nl.json
@@ -0,0 +1,55 @@
+{
+ "name": "nl",
+ "options": {
+ "months": [
+ "Januari",
+ "Februari",
+ "Maart",
+ "April",
+ "Mei",
+ "Juni",
+ "Juli",
+ "Augustus",
+ "September",
+ "Oktober",
+ "November",
+ "December"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mrt",
+ "Apr",
+ "Mei",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dec"
+ ],
+ "days": [
+ "Zondag",
+ "Maandag",
+ "Dinsdag",
+ "Woensdag",
+ "Donderdag",
+ "Vrijdag",
+ "Zaterdag"
+ ],
+ "shortDays": ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za"],
+ "toolbar": {
+ "exportToSVG": "Download SVG",
+ "exportToPNG": "Download PNG",
+ "exportToCSV": "Download CSV",
+ "menu": "Menu",
+ "selection": "Selectie",
+ "selectionZoom": "Zoom selectie",
+ "zoomIn": "Zoom in",
+ "zoomOut": "Zoom out",
+ "pan": "Verplaatsen",
+ "reset": "Standaardwaarden"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/pl.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/pl.json
new file mode 100644
index 0000000..3df3c16
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/pl.json
@@ -0,0 +1,55 @@
+{
+ "name": "pl",
+ "options": {
+ "months": [
+ "Styczeń",
+ "Luty",
+ "Marzec",
+ "Kwiecień",
+ "Maj",
+ "Czerwiec",
+ "Lipiec",
+ "Sierpień",
+ "Wrzesień",
+ "Październik",
+ "Listopad",
+ "Grudzień"
+ ],
+ "shortMonths": [
+ "Sty",
+ "Lut",
+ "Mar",
+ "Kwi",
+ "Maj",
+ "Cze",
+ "Lip",
+ "Sie",
+ "Wrz",
+ "Paź",
+ "Lis",
+ "Gru"
+ ],
+ "days": [
+ "Niedziela",
+ "Poniedziałek",
+ "Wtorek",
+ "Środa",
+ "Czwartek",
+ "Piątek",
+ "Sobota"
+ ],
+ "shortDays": ["Nd", "Pn", "Wt", "Śr", "Cz", "Pt", "Sb"],
+ "toolbar": {
+ "exportToSVG": "Pobierz SVG",
+ "exportToPNG": "Pobierz PNG",
+ "exportToCSV": "Pobierz CSV",
+ "menu": "Menu",
+ "selection": "Wybieranie",
+ "selectionZoom": "Zoom: Wybieranie",
+ "zoomIn": "Zoom: Przybliż",
+ "zoomOut": "Zoom: Oddal",
+ "pan": "Przesuwanie",
+ "reset": "Resetuj"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/pt-br.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/pt-br.json
new file mode 100644
index 0000000..a2932fc
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/pt-br.json
@@ -0,0 +1,55 @@
+{
+ "name": "pt-br",
+ "options": {
+ "months": [
+ "Janeiro",
+ "Fevereiro",
+ "Março",
+ "Abril",
+ "Maio",
+ "Junho",
+ "Julho",
+ "Agosto",
+ "Setembro",
+ "Outubro",
+ "Novembro",
+ "Dezembro"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Fev",
+ "Mar",
+ "Abr",
+ "Mai",
+ "Jun",
+ "Jul",
+ "Ago",
+ "Set",
+ "Out",
+ "Nov",
+ "Dez"
+ ],
+ "days": [
+ "Domingo",
+ "Segunda",
+ "Terça",
+ "Quarta",
+ "Quinta",
+ "Sexta",
+ "Sábado"
+ ],
+ "shortDays": ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"],
+ "toolbar": {
+ "exportToSVG": "Baixar SVG",
+ "exportToPNG": "Baixar PNG",
+ "exportToCSV": "Baixar CSV",
+ "menu": "Menu",
+ "selection": "Selecionar",
+ "selectionZoom": "Selecionar Zoom",
+ "zoomIn": "Aumentar",
+ "zoomOut": "Diminuir",
+ "pan": "Navegação",
+ "reset": "Reiniciar Zoom"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/pt.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/pt.json
new file mode 100644
index 0000000..c76cee6
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/pt.json
@@ -0,0 +1,55 @@
+{
+ "name": "pt",
+ "options": {
+ "months": [
+ "Janeiro",
+ "Fevereiro",
+ "Março",
+ "Abril",
+ "Maio",
+ "Junho",
+ "Julho",
+ "Agosto",
+ "Setembro",
+ "Outubro",
+ "Novembro",
+ "Dezembro"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Fev",
+ "Mar",
+ "Abr",
+ "Mai",
+ "Jun",
+ "Jul",
+ "Ag",
+ "Set",
+ "Out",
+ "Nov",
+ "Dez"
+ ],
+ "days": [
+ "Domingo",
+ "Segunda-feira",
+ "Terça-feira",
+ "Quarta-feira",
+ "Quinta-feira",
+ "Sexta-feira",
+ "Sábado"
+ ],
+ "shortDays": ["Do", "Se", "Te", "Qa", "Qi", "Sx", "Sa"],
+ "toolbar": {
+ "exportToSVG": "Transferir SVG",
+ "exportToPNG": "Transferir PNG",
+ "exportToCSV": "Transferir CSV",
+ "menu": "Menu",
+ "selection": "Selecionar",
+ "selectionZoom": "Zoom: Selecionar",
+ "zoomIn": "Zoom: Aumentar",
+ "zoomOut": "Zoom: Diminuir",
+ "pan": "Deslocamento",
+ "reset": "Redefinir"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/rs.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/rs.json
new file mode 100644
index 0000000..c4fff61
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/rs.json
@@ -0,0 +1,55 @@
+{
+ "name": "rs",
+ "options": {
+ "months": [
+ "Januar",
+ "Februar",
+ "Mart",
+ "April",
+ "Maj",
+ "Jun",
+ "Jul",
+ "Avgust",
+ "Septembar",
+ "Oktobar",
+ "Novembar",
+ "Decembar"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Maj",
+ "Jun",
+ "Jul",
+ "Avg",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dec"
+ ],
+ "days": [
+ "Nedelja",
+ "Ponedeljak",
+ "Utorak",
+ "Sreda",
+ "Četvrtak",
+ "Petak",
+ "Subota"
+ ],
+ "shortDays": ["Ned", "Pon", "Uto", "Sre", "Čet", "Pet", "Sub"],
+ "toolbar": {
+ "exportToSVG": "Preuzmi SVG",
+ "exportToPNG": "Preuzmi PNG",
+ "exportToCSV": "Preuzmi CSV",
+ "menu": "Meni",
+ "selection": "Odabir",
+ "selectionZoom": "Odabirno povećanje",
+ "zoomIn": "Uvećajte prikaz",
+ "zoomOut": "Umanjite prikaz",
+ "pan": "Pomeranje",
+ "reset": "Resetuj prikaz"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ru.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ru.json
new file mode 100644
index 0000000..55f3a0c
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ru.json
@@ -0,0 +1,55 @@
+{
+ "name": "ru",
+ "options": {
+ "months": [
+ "Январь",
+ "Февраль",
+ "Март",
+ "Апрель",
+ "Май",
+ "Июнь",
+ "Июль",
+ "Август",
+ "Сентябрь",
+ "Октябрь",
+ "Ноябрь",
+ "Декабрь"
+ ],
+ "shortMonths": [
+ "Янв",
+ "Фев",
+ "Мар",
+ "Апр",
+ "Май",
+ "Июн",
+ "Июл",
+ "Авг",
+ "Сен",
+ "Окт",
+ "Ноя",
+ "Дек"
+ ],
+ "days": [
+ "Воскресенье",
+ "Понедельник",
+ "Вторник",
+ "Среда",
+ "Четверг",
+ "Пятница",
+ "Суббота"
+ ],
+ "shortDays": ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
+ "toolbar": {
+ "exportToSVG": "Сохранить SVG",
+ "exportToPNG": "Сохранить PNG",
+ "exportToCSV": "Сохранить CSV",
+ "menu": "Меню",
+ "selection": "Выбор",
+ "selectionZoom": "Выбор с увеличением",
+ "zoomIn": "Увеличить",
+ "zoomOut": "Уменьшить",
+ "pan": "Перемещение",
+ "reset": "Сбросить увеличение"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/se.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/se.json
new file mode 100644
index 0000000..e9409e5
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/se.json
@@ -0,0 +1,55 @@
+{
+ "name": "se",
+ "options": {
+ "months": [
+ "Januari",
+ "Februari",
+ "Mars",
+ "April",
+ "Maj",
+ "Juni",
+ "Juli",
+ "Augusti",
+ "September",
+ "Oktober",
+ "November",
+ "December"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Maj",
+ "Juni",
+ "Juli",
+ "Aug",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dec"
+ ],
+ "days": [
+ "Söndag",
+ "Måndag",
+ "Tisdag",
+ "Onsdag",
+ "Torsdag",
+ "Fredag",
+ "Lördag"
+ ],
+ "shortDays": ["Sön", "Mån", "Tis", "Ons", "Tor", "Fre", "Lör"],
+ "toolbar": {
+ "exportToSVG": "Ladda SVG",
+ "exportToPNG": "Ladda PNG",
+ "exportToCSV": "Ladda CSV",
+ "menu": "Meny",
+ "selection": "Selektion",
+ "selectionZoom": "Val av zoom",
+ "zoomIn": "Zooma in",
+ "zoomOut": "Zooma ut",
+ "pan": "Panorering",
+ "reset": "Återställ zoomning"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/sk.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/sk.json
new file mode 100644
index 0000000..03e69aa
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/sk.json
@@ -0,0 +1,55 @@
+{
+ "name": "sk",
+ "options": {
+ "months": [
+ "Január",
+ "Február",
+ "Marec",
+ "Apríl",
+ "Máj",
+ "Jún",
+ "Júl",
+ "August",
+ "September",
+ "Október",
+ "November",
+ "December"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Máj",
+ "Jún",
+ "Júl",
+ "Aug",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dec"
+ ],
+ "days": [
+ "Nedeľa",
+ "Pondelok",
+ "Utorok",
+ "Streda",
+ "Štvrtok",
+ "Piatok",
+ "Sobota"
+ ],
+ "shortDays": ["Ne", "Po", "Ut", "St", "Št", "Pi", "So"],
+ "toolbar": {
+ "exportToSVG": "Stiahnuť SVG",
+ "exportToPNG": "Stiahnuť PNG",
+ "exportToCSV": "Stiahnuť CSV",
+ "menu": "Menu",
+ "selection": "Vyberanie",
+ "selectionZoom": "Zoom: Vyberanie",
+ "zoomIn": "Zoom: Priblížiť",
+ "zoomOut": "Zoom: Vzdialiť",
+ "pan": "Presúvanie",
+ "reset": "Resetovať"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/sl.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/sl.json
new file mode 100644
index 0000000..793ff56
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/sl.json
@@ -0,0 +1,55 @@
+{
+ "name": "sl",
+ "options": {
+ "months": [
+ "Januar",
+ "Februar",
+ "Marec",
+ "April",
+ "Maj",
+ "Junij",
+ "Julij",
+ "Avgust",
+ "Septemer",
+ "Oktober",
+ "November",
+ "December"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "Maj",
+ "Jun",
+ "Jul",
+ "Avg",
+ "Sep",
+ "Okt",
+ "Nov",
+ "Dec"
+ ],
+ "days": [
+ "Nedelja",
+ "Ponedeljek",
+ "Torek",
+ "Sreda",
+ "Četrtek",
+ "Petek",
+ "Sobota"
+ ],
+ "shortDays": ["Ne", "Po", "To", "Sr", "Če", "Pe", "So"],
+ "toolbar": {
+ "exportToSVG": "Prenesi SVG",
+ "exportToPNG": "Prenesi PNG",
+ "exportToCSV": "Prenesi CSV",
+ "menu": "Menu",
+ "selection": "Izbiranje",
+ "selectionZoom": "Zoom: Izbira",
+ "zoomIn": "Zoom: Približaj",
+ "zoomOut": "Zoom: Oddalji",
+ "pan": "Pomikanje",
+ "reset": "Resetiraj"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/sq.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/sq.json
new file mode 100644
index 0000000..a478591
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/sq.json
@@ -0,0 +1,55 @@
+{
+ "name": "sq",
+ "options": {
+ "months": [
+ "Janar",
+ "Shkurt",
+ "Mars",
+ "Prill",
+ "Maj",
+ "Qershor",
+ "Korrik",
+ "Gusht",
+ "Shtator",
+ "Tetor",
+ "Nëntor",
+ "Dhjetor"
+ ],
+ "shortMonths": [
+ "Jan",
+ "Shk",
+ "Mar",
+ "Pr",
+ "Maj",
+ "Qer",
+ "Korr",
+ "Gush",
+ "Sht",
+ "Tet",
+ "Nën",
+ "Dhj"
+ ],
+ "days": [
+ "e Dielë",
+ "e Hënë",
+ "e Martë",
+ "e Mërkurë",
+ "e Enjte",
+ "e Premte",
+ "e Shtunë"
+ ],
+ "shortDays": ["Die", "Hën", "Mar", "Mër", "Enj", "Pre", "Sht"],
+ "toolbar": {
+ "exportToSVG": "Shkarko SVG",
+ "exportToPNG": "Shkarko PNG",
+ "exportToCSV": "Shkarko CSV",
+ "menu": "Menu",
+ "selection": "Seleksiono",
+ "selectionZoom": "Seleksiono Zmadhim",
+ "zoomIn": "Zmadho",
+ "zoomOut": "Zvogëlo",
+ "pan": "Spostoje",
+ "reset": "Rikthe dimensionin"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/th.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/th.json
new file mode 100644
index 0000000..2b3b109
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/th.json
@@ -0,0 +1,55 @@
+{
+ "name": "th",
+ "options": {
+ "months": [
+ "มกราคม",
+ "กุมภาพันธ์",
+ "มีนาคม",
+ "เมษายน",
+ "พฤษภาคม",
+ "มิถุนายน",
+ "กรกฎาคม",
+ "สิงหาคม",
+ "กันยายน",
+ "ตุลาคม",
+ "พฤศจิกายน",
+ "ธันวาคม"
+ ],
+ "shortMonths": [
+ "ม.ค.",
+ "ก.พ.",
+ "มี.ค.",
+ "เม.ย.",
+ "พ.ค.",
+ "มิ.ย.",
+ "ก.ค.",
+ "ส.ค.",
+ "ก.ย.",
+ "ต.ค.",
+ "พ.ย.",
+ "ธ.ค."
+ ],
+ "days": [
+ "อาทิตย์",
+ "จันทร์",
+ "อังคาร",
+ "พุธ",
+ "พฤหัสบดี",
+ "ศุกร์",
+ "เสาร์"
+ ],
+ "shortDays": ["อา", "จ", "อ", "พ", "พฤ", "ศ", "ส"],
+ "toolbar": {
+ "exportToSVG": "ดาวน์โหลด SVG",
+ "exportToPNG": "ดาวน์โหลด PNG",
+ "exportToCSV": "ดาวน์โหลด CSV",
+ "menu": "เมนู",
+ "selection": "เลือก",
+ "selectionZoom": "เลือกจุดที่จะซูม",
+ "zoomIn": "ซูมเข้า",
+ "zoomOut": "ซูมออก",
+ "pan": "ปรากฎว่า",
+ "reset": "รีเซ็ตการซูม"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/tr.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/tr.json
new file mode 100644
index 0000000..dda01e8
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/tr.json
@@ -0,0 +1,55 @@
+{
+ "name": "tr",
+ "options": {
+ "months": [
+ "Ocak",
+ "Şubat",
+ "Mart",
+ "Nisan",
+ "Mayıs",
+ "Haziran",
+ "Temmuz",
+ "Ağustos",
+ "Eylül",
+ "Ekim",
+ "Kasım",
+ "Aralık"
+ ],
+ "shortMonths": [
+ "Oca",
+ "Şub",
+ "Mar",
+ "Nis",
+ "May",
+ "Haz",
+ "Tem",
+ "Ağu",
+ "Eyl",
+ "Eki",
+ "Kas",
+ "Ara"
+ ],
+ "days": [
+ "Pazar",
+ "Pazartesi",
+ "Salı",
+ "Çarşamba",
+ "Perşembe",
+ "Cuma",
+ "Cumartesi"
+ ],
+ "shortDays": ["Paz", "Pzt", "Sal", "Çar", "Per", "Cum", "Cmt"],
+ "toolbar": {
+ "exportToSVG": "SVG İndir",
+ "exportToPNG": "PNG İndir",
+ "exportToCSV": "CSV İndir",
+ "menu": "Menü",
+ "selection": "Seçim",
+ "selectionZoom": "Seçim Yakınlaştır",
+ "zoomIn": "Yakınlaştır",
+ "zoomOut": "Uzaklaştır",
+ "pan": "Kaydır",
+ "reset": "Yakınlaştırmayı Sıfırla"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ua.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ua.json
new file mode 100644
index 0000000..d6f81de
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/ua.json
@@ -0,0 +1,55 @@
+{
+ "name": "ua",
+ "options": {
+ "months": [
+ "Січень",
+ "Лютий",
+ "Березень",
+ "Квітень",
+ "Травень",
+ "Червень",
+ "Липень",
+ "Серпень",
+ "Вересень",
+ "Жовтень",
+ "Листопад",
+ "Грудень"
+ ],
+ "shortMonths": [
+ "Січ",
+ "Лют",
+ "Бер",
+ "Кві",
+ "Тра",
+ "Чер",
+ "Лип",
+ "Сер",
+ "Вер",
+ "Жов",
+ "Лис",
+ "Гру"
+ ],
+ "days": [
+ "Неділя",
+ "Понеділок",
+ "Вівторок",
+ "Середа",
+ "Четвер",
+ "П'ятниця",
+ "Субота"
+ ],
+ "shortDays": ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
+ "toolbar": {
+ "exportToSVG": "Зберегти SVG",
+ "exportToPNG": "Зберегти PNG",
+ "exportToCSV": "Зберегти CSV",
+ "menu": "Меню",
+ "selection": "Вибір",
+ "selectionZoom": "Вибір із збільшенням",
+ "zoomIn": "Збільшити",
+ "zoomOut": "Зменшити",
+ "pan": "Переміщення",
+ "reset": "Скинути збільшення"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/vi.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/vi.json
new file mode 100644
index 0000000..9323583
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/vi.json
@@ -0,0 +1,63 @@
+{
+ "name": "vi",
+ "options": {
+ "months": [
+ "Tháng 01",
+ "Tháng 02",
+ "Tháng 03",
+ "Tháng 04",
+ "Tháng 05",
+ "Tháng 06",
+ "Tháng 07",
+ "Tháng 08",
+ "Tháng 09",
+ "Tháng 10",
+ "Tháng 11",
+ "Tháng 12"
+ ],
+ "shortMonths": [
+ "Th01",
+ "Th02",
+ "Th03",
+ "Th04",
+ "Th05",
+ "Th06",
+ "Th07",
+ "Th08",
+ "Th09",
+ "Th10",
+ "Th11",
+ "Th12"
+ ],
+ "days": [
+ "Chủ nhật",
+ "Thứ hai",
+ "Thứ ba",
+ "Thứ Tư",
+ "Thứ năm",
+ "Thứ sáu",
+ "Thứ bảy"
+ ],
+ "shortDays": [
+ "CN",
+ "T2",
+ "T3",
+ "T4",
+ "T5",
+ "T6",
+ "T7"
+ ],
+ "toolbar": {
+ "exportToSVG": "Tải xuống SVG",
+ "exportToPNG": "Tải xuống PNG",
+ "exportToCSV": "Tải xuống CSV",
+ "menu": "Tuỳ chọn",
+ "selection": "Vùng chọn",
+ "selectionZoom": "Vùng chọn phóng to",
+ "zoomIn": "Phóng to",
+ "zoomOut": "Thu nhỏ",
+ "pan": "Di chuyển",
+ "reset": "Đặt lại thu phóng"
+ }
+ }
+}
\ No newline at end of file
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/zh-cn.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/zh-cn.json
new file mode 100644
index 0000000..8944659
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/zh-cn.json
@@ -0,0 +1,55 @@
+{
+ "name": "zh-cn",
+ "options": {
+ "months": [
+ "一月",
+ "二月",
+ "三月",
+ "四月",
+ "五月",
+ "六月",
+ "七月",
+ "八月",
+ "九月",
+ "十月",
+ "十一月",
+ "十二月"
+ ],
+ "shortMonths": [
+ "一月",
+ "二月",
+ "三月",
+ "四月",
+ "五月",
+ "六月",
+ "七月",
+ "八月",
+ "九月",
+ "十月",
+ "十一月",
+ "十二月"
+ ],
+ "days": [
+ "星期天",
+ "星期一",
+ "星期二",
+ "星期三",
+ "星期四",
+ "星期五",
+ "星期六"
+ ],
+ "shortDays": ["周日", "周一", "周二", "周三", "周四", "周五", "周六"],
+ "toolbar": {
+ "exportToSVG": "下载 SVG",
+ "exportToPNG": "下载 PNG",
+ "exportToCSV": "下载 CSV",
+ "menu": "菜单",
+ "selection": "选择",
+ "selectionZoom": "选择缩放",
+ "zoomIn": "放大",
+ "zoomOut": "缩小",
+ "pan": "平移",
+ "reset": "重置缩放"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/zh-tw.json b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/zh-tw.json
new file mode 100644
index 0000000..2444b46
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/locales/zh-tw.json
@@ -0,0 +1,55 @@
+{
+ "name": "zh-tw",
+ "options": {
+ "months": [
+ "一月",
+ "二月",
+ "三月",
+ "四月",
+ "五月",
+ "六月",
+ "七月",
+ "八月",
+ "九月",
+ "十月",
+ "十一月",
+ "十二月"
+ ],
+ "shortMonths": [
+ "一月",
+ "二月",
+ "三月",
+ "四月",
+ "五月",
+ "六月",
+ "七月",
+ "八月",
+ "九月",
+ "十月",
+ "十一月",
+ "十二月"
+ ],
+ "days": [
+ "星期日",
+ "星期一",
+ "星期二",
+ "星期三",
+ "星期四",
+ "星期五",
+ "星期六"
+ ],
+ "shortDays": ["週日", "週一", "週二", "週三", "週四", "週五", "週六"],
+ "toolbar": {
+ "exportToSVG": "下載 SVG",
+ "exportToPNG": "下載 PNG",
+ "exportToCSV": "下載 CSV",
+ "menu": "選單",
+ "selection": "選擇",
+ "selectionZoom": "選擇縮放",
+ "zoomIn": "放大",
+ "zoomOut": "縮小",
+ "pan": "平移",
+ "reset": "重置縮放"
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Animations.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Animations.js
new file mode 100644
index 0000000..bee9d71
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Animations.js
@@ -0,0 +1,237 @@
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts Animation Class.
+ *
+ * @module Animations
+ **/
+
+export default class Animations {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.setEasingFunctions()
+ }
+
+ setEasingFunctions() {
+ let easing
+
+ if (this.w.globals.easing) return
+
+ const userDefinedEasing = this.w.config.chart.animations.easing
+
+ switch (userDefinedEasing) {
+ case 'linear': {
+ easing = '-'
+ break
+ }
+ case 'easein': {
+ easing = '<'
+ break
+ }
+ case 'easeout': {
+ easing = '>'
+ break
+ }
+ case 'easeinout': {
+ easing = '<>'
+ break
+ }
+ case 'swing': {
+ easing = (pos) => {
+ let s = 1.70158
+ let ret = (pos -= 1) * pos * ((s + 1) * pos + s) + 1
+ return ret
+ }
+ break
+ }
+ case 'bounce': {
+ easing = (pos) => {
+ let ret = ''
+ if (pos < 1 / 2.75) {
+ ret = 7.5625 * pos * pos
+ } else if (pos < 2 / 2.75) {
+ ret = 7.5625 * (pos -= 1.5 / 2.75) * pos + 0.75
+ } else if (pos < 2.5 / 2.75) {
+ ret = 7.5625 * (pos -= 2.25 / 2.75) * pos + 0.9375
+ } else {
+ ret = 7.5625 * (pos -= 2.625 / 2.75) * pos + 0.984375
+ }
+ return ret
+ }
+ break
+ }
+ case 'elastic': {
+ easing = (pos) => {
+ if (pos === !!pos) return pos
+ return (
+ Math.pow(2, -10 * pos) *
+ Math.sin(((pos - 0.075) * (2 * Math.PI)) / 0.3) +
+ 1
+ )
+ }
+ break
+ }
+
+ default: {
+ easing = '<>'
+ }
+ }
+
+ this.w.globals.easing = easing
+ }
+
+ animateLine(el, from, to, speed) {
+ el.attr(from).animate(speed).attr(to)
+ }
+
+ /*
+ ** Animate radius of a circle element
+ */
+ animateMarker(el, speed, easing, cb) {
+ el.attr({
+ opacity: 0,
+ })
+ .animate(speed, easing)
+ .attr({
+ opacity: 1,
+ })
+ .afterAll(() => {
+ cb()
+ })
+ }
+
+ /*
+ ** Animate rect properties
+ */
+ animateRect(el, from, to, speed, fn) {
+ el.attr(from)
+ .animate(speed)
+ .attr(to)
+ .afterAll(() => fn())
+ }
+
+ animatePathsGradually(params) {
+ let { el, realIndex, j, fill, pathFrom, pathTo, speed, delay } = params
+
+ let me = this
+ let w = this.w
+
+ let delayFactor = 0
+
+ if (w.config.chart.animations.animateGradually.enabled) {
+ delayFactor = w.config.chart.animations.animateGradually.delay
+ }
+
+ if (
+ w.config.chart.animations.dynamicAnimation.enabled &&
+ w.globals.dataChanged &&
+ w.config.chart.type !== 'bar'
+ ) {
+ // disabled due to this bug - https://github.com/apexcharts/vue-apexcharts/issues/75
+ delayFactor = 0
+ }
+ me.morphSVG(
+ el,
+ realIndex,
+ j,
+ w.config.chart.type === 'line' && !w.globals.comboCharts
+ ? 'stroke'
+ : fill,
+ pathFrom,
+ pathTo,
+ speed,
+ delay * delayFactor
+ )
+ }
+
+ showDelayedElements() {
+ this.w.globals.delayedElements.forEach((d) => {
+ const ele = d.el
+ ele.classList.remove('apexcharts-element-hidden')
+ ele.classList.add('apexcharts-hidden-element-shown')
+ })
+ }
+
+ animationCompleted(el) {
+ const w = this.w
+ if (w.globals.animationEnded) return
+
+ w.globals.animationEnded = true
+ this.showDelayedElements()
+
+ if (typeof w.config.chart.events.animationEnd === 'function') {
+ w.config.chart.events.animationEnd(this.ctx, { el, w })
+ }
+ }
+
+ // SVG.js animation for morphing one path to another
+ morphSVG(el, realIndex, j, fill, pathFrom, pathTo, speed, delay) {
+ let w = this.w
+
+ if (!pathFrom) {
+ pathFrom = el.attr('pathFrom')
+ }
+
+ if (!pathTo) {
+ pathTo = el.attr('pathTo')
+ }
+
+ const disableAnimationForCorrupPath = (path) => {
+ if (w.config.chart.type === 'radar') {
+ // radar chart drops the path to bottom and hence a corrup path looks ugly
+ // therefore, disable animation for such a case
+ speed = 1
+ }
+ return `M 0 ${w.globals.gridHeight}`
+ }
+
+ if (
+ !pathFrom ||
+ pathFrom.indexOf('undefined') > -1 ||
+ pathFrom.indexOf('NaN') > -1
+ ) {
+ pathFrom = disableAnimationForCorrupPath()
+ }
+
+ if (
+ !pathTo ||
+ pathTo.indexOf('undefined') > -1 ||
+ pathTo.indexOf('NaN') > -1
+ ) {
+ pathTo = disableAnimationForCorrupPath()
+ }
+ if (!w.globals.shouldAnimate) {
+ speed = 1
+ }
+
+ el.plot(pathFrom)
+ .animate(1, w.globals.easing, delay)
+ .plot(pathFrom)
+ .animate(speed, w.globals.easing, delay)
+ .plot(pathTo)
+ .afterAll(() => {
+ // a flag to indicate that the original mount function can return true now as animation finished here
+
+ if (Utils.isNumber(j)) {
+ if (
+ j === w.globals.series[w.globals.maxValsInArrayIndex].length - 2 &&
+ w.globals.shouldAnimate
+ ) {
+ this.animationCompleted(el)
+ }
+ } else if (fill !== 'none' && w.globals.shouldAnimate) {
+ if (
+ (!w.globals.comboCharts &&
+ realIndex === w.globals.series.length - 1) ||
+ w.globals.comboCharts
+ ) {
+ this.animationCompleted(el)
+ }
+ }
+
+ this.showDelayedElements()
+ })
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Base.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Base.js
new file mode 100644
index 0000000..87cdfb1
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Base.js
@@ -0,0 +1,25 @@
+import Config from './settings/Config'
+import Globals from './settings/Globals'
+
+/**
+ * ApexCharts Base Class for extending user options with pre-defined ApexCharts config.
+ *
+ * @module Base
+ **/
+export default class Base {
+ constructor(opts) {
+ this.opts = opts
+ }
+
+ init() {
+ const config = new Config(this.opts).init({ responsiveOverride: false })
+ const globals = new Globals().init(config)
+
+ const w = {
+ config,
+ globals
+ }
+
+ return w
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Core.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Core.js
new file mode 100644
index 0000000..a8add42
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Core.js
@@ -0,0 +1,603 @@
+import Bar from '../charts/Bar'
+import BarStacked from '../charts/BarStacked'
+import BoxCandleStick from '../charts/BoxCandleStick'
+import CoreUtils from './CoreUtils'
+import Crosshairs from './Crosshairs'
+import HeatMap from '../charts/HeatMap'
+import Globals from '../modules/settings/Globals'
+import Pie from '../charts/Pie'
+import Radar from '../charts/Radar'
+import Radial from '../charts/Radial'
+import RangeBar from '../charts/RangeBar'
+import Legend from './legend/Legend'
+import Line from '../charts/Line'
+import Treemap from '../charts/Treemap'
+import Graphics from './Graphics'
+import Range from './Range'
+import Utils from '../utils/Utils'
+import TimeScale from './TimeScale'
+
+/**
+ * ApexCharts Core Class responsible for major calculations and creating elements.
+ *
+ * @module Core
+ **/
+
+export default class Core {
+ constructor(el, ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ this.el = el
+ }
+
+ setupElements() {
+ const { globals: gl, config: cnf } = this.w
+
+ const ct = cnf.chart.type
+ const axisChartsArrTypes = [
+ 'line',
+ 'area',
+ 'bar',
+ 'rangeBar',
+ 'rangeArea',
+ 'candlestick',
+ 'boxPlot',
+ 'scatter',
+ 'bubble',
+ 'radar',
+ 'heatmap',
+ 'treemap',
+ ]
+
+ const xyChartsArrTypes = [
+ 'line',
+ 'area',
+ 'bar',
+ 'rangeBar',
+ 'rangeArea',
+ 'candlestick',
+ 'boxPlot',
+ 'scatter',
+ 'bubble',
+ ]
+
+ gl.axisCharts = axisChartsArrTypes.includes(ct)
+ gl.xyCharts = xyChartsArrTypes.includes(ct)
+
+ gl.isBarHorizontal =
+ ['bar', 'rangeBar', 'boxPlot'].includes(ct) &&
+ cnf.plotOptions.bar.horizontal
+
+ gl.chartClass = `.apexcharts${gl.chartID}`
+ gl.dom.baseEl = this.el
+
+ gl.dom.elWrap = document.createElement('div')
+ Graphics.setAttrs(gl.dom.elWrap, {
+ id: gl.chartClass.substring(1),
+ class: `apexcharts-canvas ${gl.chartClass.substring(1)}`,
+ })
+ this.el.appendChild(gl.dom.elWrap)
+
+ gl.dom.Paper = new window.SVG.Doc(gl.dom.elWrap)
+ gl.dom.Paper.attr({
+ class: 'apexcharts-svg',
+ 'xmlns:data': 'ApexChartsNS',
+ transform: `translate(${cnf.chart.offsetX}, ${cnf.chart.offsetY})`,
+ })
+
+ gl.dom.Paper.node.style.background =
+ cnf.theme.mode === 'dark' && !cnf.chart.background
+ ? '#424242'
+ : cnf.theme.mode === 'light' && !cnf.chart.background
+ ? '#fff'
+ : cnf.chart.background
+
+ this.setSVGDimensions()
+
+ gl.dom.elLegendForeign = document.createElementNS(gl.SVGNS, 'foreignObject')
+ Graphics.setAttrs(gl.dom.elLegendForeign, {
+ x: 0,
+ y: 0,
+ width: gl.svgWidth,
+ height: gl.svgHeight,
+ })
+
+ gl.dom.elLegendWrap = document.createElement('div')
+ gl.dom.elLegendWrap.classList.add('apexcharts-legend')
+
+ gl.dom.elLegendWrap.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')
+ gl.dom.elLegendForeign.appendChild(gl.dom.elLegendWrap)
+
+ gl.dom.Paper.node.appendChild(gl.dom.elLegendForeign)
+
+ gl.dom.elGraphical = gl.dom.Paper.group().attr({
+ class: 'apexcharts-inner apexcharts-graphical',
+ })
+
+ gl.dom.elDefs = gl.dom.Paper.defs()
+ gl.dom.Paper.add(gl.dom.elGraphical)
+ gl.dom.elGraphical.add(gl.dom.elDefs)
+ }
+
+ plotChartType(ser, xyRatios) {
+ const { w, ctx } = this
+ const { config: cnf, globals: gl } = w
+
+ const seriesTypes = {
+ line: { series: [], i: [] },
+ area: { series: [], i: [] },
+ scatter: { series: [], i: [] },
+ bubble: { series: [], i: [] },
+ column: { series: [], i: [] },
+ candlestick: { series: [], i: [] },
+ boxPlot: { series: [], i: [] },
+ rangeBar: { series: [], i: [] },
+ rangeArea: { series: [], seriesRangeEnd: [], i: [] },
+ }
+
+ const chartType = cnf.chart.type || 'line'
+ let nonComboType = null
+ let comboCount = 0
+
+ gl.series.forEach((serie, st) => {
+ const seriesType = ser[st].type || chartType
+ if (seriesTypes[seriesType]) {
+ if (seriesType === 'rangeArea') {
+ seriesTypes[seriesType].series.push(gl.seriesRangeStart[st])
+ seriesTypes[seriesType].seriesRangeEnd.push(gl.seriesRangeEnd[st])
+ } else {
+ seriesTypes[seriesType].series.push(serie)
+ }
+ seriesTypes[seriesType].i.push(st)
+
+ if (seriesType === 'column' || seriesType === 'bar')
+ w.globals.columnSeries = seriesTypes.column
+ } else if (
+ [
+ 'heatmap',
+ 'treemap',
+ 'pie',
+ 'donut',
+ 'polarArea',
+ 'radialBar',
+ 'radar',
+ ].includes(seriesType)
+ ) {
+ nonComboType = seriesType
+ } else if (seriesType === 'bar') {
+ seriesTypes['column'].series.push(serie)
+ seriesTypes['column'].i.push(st)
+ } else {
+ console.warn(
+ `You have specified an unrecognized series type (${seriesType}).`
+ )
+ }
+ if (chartType !== seriesType && seriesType !== 'scatter') comboCount++
+ })
+
+ if (comboCount > 0) {
+ if (nonComboType) {
+ console.warn(
+ `Chart or series type ${nonComboType} cannot appear with other chart or series types.`
+ )
+ }
+ if (
+ seriesTypes.column.series.length > 0 &&
+ cnf.plotOptions.bar.horizontal
+ ) {
+ comboCount -= seriesTypes.column.series.length
+ seriesTypes.column = { series: [], i: [] }
+ w.globals.columnSeries = { series: [], i: [] }
+ console.warn(
+ 'Horizontal bars are not supported in a mixed/combo chart. Please turn off `plotOptions.bar.horizontal`'
+ )
+ }
+ }
+ gl.comboCharts ||= comboCount > 0
+
+ const line = new Line(ctx, xyRatios)
+ const boxCandlestick = new BoxCandleStick(ctx, xyRatios)
+ ctx.pie = new Pie(ctx)
+ const radialBar = new Radial(ctx)
+ ctx.rangeBar = new RangeBar(ctx, xyRatios)
+ const radar = new Radar(ctx)
+ let elGraph = []
+
+ if (gl.comboCharts) {
+ const coreUtils = new CoreUtils(ctx)
+ if (seriesTypes.area.series.length > 0) {
+ elGraph.push(
+ ...coreUtils.drawSeriesByGroup(
+ seriesTypes.area,
+ gl.areaGroups,
+ 'area',
+ line
+ )
+ )
+ }
+ if (seriesTypes.column.series.length > 0) {
+ if (cnf.chart.stacked) {
+ const barStacked = new BarStacked(ctx, xyRatios)
+ elGraph.push(
+ barStacked.draw(seriesTypes.column.series, seriesTypes.column.i)
+ )
+ } else {
+ ctx.bar = new Bar(ctx, xyRatios)
+ elGraph.push(
+ ctx.bar.draw(seriesTypes.column.series, seriesTypes.column.i)
+ )
+ }
+ }
+ if (seriesTypes.rangeArea.series.length > 0) {
+ elGraph.push(
+ line.draw(
+ seriesTypes.rangeArea.series,
+ 'rangeArea',
+ seriesTypes.rangeArea.i,
+ seriesTypes.rangeArea.seriesRangeEnd
+ )
+ )
+ }
+ if (seriesTypes.line.series.length > 0) {
+ elGraph.push(
+ ...coreUtils.drawSeriesByGroup(
+ seriesTypes.line,
+ gl.lineGroups,
+ 'line',
+ line
+ )
+ )
+ }
+ if (seriesTypes.candlestick.series.length > 0) {
+ elGraph.push(
+ boxCandlestick.draw(
+ seriesTypes.candlestick.series,
+ 'candlestick',
+ seriesTypes.candlestick.i
+ )
+ )
+ }
+ if (seriesTypes.boxPlot.series.length > 0) {
+ elGraph.push(
+ boxCandlestick.draw(
+ seriesTypes.boxPlot.series,
+ 'boxPlot',
+ seriesTypes.boxPlot.i
+ )
+ )
+ }
+ if (seriesTypes.rangeBar.series.length > 0) {
+ elGraph.push(
+ ctx.rangeBar.draw(seriesTypes.rangeBar.series, seriesTypes.rangeBar.i)
+ )
+ }
+ if (seriesTypes.scatter.series.length > 0) {
+ const scatterLine = new Line(ctx, xyRatios, true)
+ elGraph.push(
+ scatterLine.draw(
+ seriesTypes.scatter.series,
+ 'scatter',
+ seriesTypes.scatter.i
+ )
+ )
+ }
+ if (seriesTypes.bubble.series.length > 0) {
+ const bubbleLine = new Line(ctx, xyRatios, true)
+ elGraph.push(
+ bubbleLine.draw(
+ seriesTypes.bubble.series,
+ 'bubble',
+ seriesTypes.bubble.i
+ )
+ )
+ }
+ } else {
+ switch (cnf.chart.type) {
+ case 'line':
+ elGraph = line.draw(gl.series, 'line')
+ break
+ case 'area':
+ elGraph = line.draw(gl.series, 'area')
+ break
+ case 'bar':
+ if (cnf.chart.stacked) {
+ const barStacked = new BarStacked(ctx, xyRatios)
+ elGraph = barStacked.draw(gl.series)
+ } else {
+ ctx.bar = new Bar(ctx, xyRatios)
+ elGraph = ctx.bar.draw(gl.series)
+ }
+ break
+ case 'candlestick':
+ const candleStick = new BoxCandleStick(ctx, xyRatios)
+ elGraph = candleStick.draw(gl.series, 'candlestick')
+ break
+ case 'boxPlot':
+ const boxPlot = new BoxCandleStick(ctx, xyRatios)
+ elGraph = boxPlot.draw(gl.series, cnf.chart.type)
+ break
+ case 'rangeBar':
+ elGraph = ctx.rangeBar.draw(gl.series)
+ break
+ case 'rangeArea':
+ elGraph = line.draw(
+ gl.seriesRangeStart,
+ 'rangeArea',
+ undefined,
+ gl.seriesRangeEnd
+ )
+ break
+ case 'heatmap':
+ const heatmap = new HeatMap(ctx, xyRatios)
+ elGraph = heatmap.draw(gl.series)
+ break
+ case 'treemap':
+ const treemap = new Treemap(ctx, xyRatios)
+ elGraph = treemap.draw(gl.series)
+ break
+ case 'pie':
+ case 'donut':
+ case 'polarArea':
+ elGraph = ctx.pie.draw(gl.series)
+ break
+ case 'radialBar':
+ elGraph = radialBar.draw(gl.series)
+ break
+ case 'radar':
+ elGraph = radar.draw(gl.series)
+ break
+ default:
+ elGraph = line.draw(gl.series)
+ }
+ }
+
+ return elGraph
+ }
+
+ setSVGDimensions() {
+ const { globals: gl, config: cnf } = this.w
+
+ cnf.chart.width = cnf.chart.width || '100%'
+ cnf.chart.height = cnf.chart.height || 'auto'
+
+ gl.svgWidth = cnf.chart.width
+ gl.svgHeight = cnf.chart.height
+
+ let elDim = Utils.getDimensions(this.el)
+ const widthUnit = cnf.chart.width
+ .toString()
+ .split(/[0-9]+/g)
+ .pop()
+
+ if (widthUnit === '%') {
+ if (Utils.isNumber(elDim[0])) {
+ if (elDim[0].width === 0) {
+ elDim = Utils.getDimensions(this.el.parentNode)
+ }
+ gl.svgWidth = (elDim[0] * parseInt(cnf.chart.width, 10)) / 100
+ }
+ } else if (widthUnit === 'px' || widthUnit === '') {
+ gl.svgWidth = parseInt(cnf.chart.width, 10)
+ }
+
+ const heightUnit = String(cnf.chart.height)
+ .toString()
+ .split(/[0-9]+/g)
+ .pop()
+ if (gl.svgHeight !== 'auto' && gl.svgHeight !== '') {
+ if (heightUnit === '%') {
+ const elParentDim = Utils.getDimensions(this.el.parentNode)
+ gl.svgHeight = (elParentDim[1] * parseInt(cnf.chart.height, 10)) / 100
+ } else {
+ gl.svgHeight = parseInt(cnf.chart.height, 10)
+ }
+ } else {
+ gl.svgHeight = gl.axisCharts ? gl.svgWidth / 1.61 : gl.svgWidth / 1.2
+ }
+
+ gl.svgWidth = Math.max(gl.svgWidth, 0)
+ gl.svgHeight = Math.max(gl.svgHeight, 0)
+
+ Graphics.setAttrs(gl.dom.Paper.node, {
+ width: gl.svgWidth,
+ height: gl.svgHeight,
+ })
+
+ if (heightUnit !== '%') {
+ const offsetY = cnf.chart.sparkline.enabled
+ ? 0
+ : gl.axisCharts
+ ? cnf.chart.parentHeightOffset
+ : 0
+ gl.dom.Paper.node.parentNode.parentNode.style.minHeight = `${
+ gl.svgHeight + offsetY
+ }px`
+ }
+
+ gl.dom.elWrap.style.width = `${gl.svgWidth}px`
+ gl.dom.elWrap.style.height = `${gl.svgHeight}px`
+ }
+
+ shiftGraphPosition() {
+ const { globals: gl } = this.w
+ const { translateY: tY, translateX: tX } = gl
+
+ Graphics.setAttrs(gl.dom.elGraphical.node, {
+ transform: `translate(${tX}, ${tY})`,
+ })
+ }
+
+ resizeNonAxisCharts() {
+ const { w } = this
+ const { globals: gl } = w
+
+ let legendHeight = 0
+ let offY = w.config.chart.sparkline.enabled ? 1 : 15
+ offY += w.config.grid.padding.bottom
+
+ if (
+ ['top', 'bottom'].includes(w.config.legend.position) &&
+ w.config.legend.show &&
+ !w.config.legend.floating
+ ) {
+ legendHeight =
+ new Legend(this.ctx).legendHelpers.getLegendDimensions().clwh + 7
+ }
+
+ const el = w.globals.dom.baseEl.querySelector(
+ '.apexcharts-radialbar, .apexcharts-pie'
+ )
+ let chartInnerDimensions = w.globals.radialSize * 2.05
+
+ if (
+ el &&
+ !w.config.chart.sparkline.enabled &&
+ w.config.plotOptions.radialBar.startAngle !== 0
+ ) {
+ const elRadialRect = Utils.getBoundingClientRect(el)
+ chartInnerDimensions = elRadialRect.bottom
+ const maxHeight = elRadialRect.bottom - elRadialRect.top
+ chartInnerDimensions = Math.max(w.globals.radialSize * 2.05, maxHeight)
+ }
+
+ const newHeight = Math.ceil(
+ chartInnerDimensions + gl.translateY + legendHeight + offY
+ )
+
+ if (gl.dom.elLegendForeign) {
+ gl.dom.elLegendForeign.setAttribute('height', newHeight)
+ }
+
+ if (w.config.chart.height && String(w.config.chart.height).includes('%'))
+ return
+
+ gl.dom.elWrap.style.height = `${newHeight}px`
+ Graphics.setAttrs(gl.dom.Paper.node, { height: newHeight })
+ gl.dom.Paper.node.parentNode.parentNode.style.minHeight = `${newHeight}px`
+ }
+
+ coreCalculations() {
+ new Range(this.ctx).init()
+ }
+
+ resetGlobals() {
+ const resetxyValues = () => this.w.config.series.map(() => [])
+ const globalObj = new Globals()
+
+ const { globals: gl } = this.w
+ globalObj.initGlobalVars(gl)
+ gl.seriesXvalues = resetxyValues()
+ gl.seriesYvalues = resetxyValues()
+ }
+
+ isMultipleY() {
+ if (Array.isArray(this.w.config.yaxis) && this.w.config.yaxis.length > 1) {
+ this.w.globals.isMultipleYAxis = true
+ return true
+ }
+ return false
+ }
+
+ xySettings() {
+ const { w } = this
+ let xyRatios = null
+
+ if (w.globals.axisCharts) {
+ if (w.config.xaxis.crosshairs.position === 'back') {
+ new Crosshairs(this.ctx).drawXCrosshairs()
+ }
+ if (w.config.yaxis[0].crosshairs.position === 'back') {
+ new Crosshairs(this.ctx).drawYCrosshairs()
+ }
+
+ if (
+ w.config.xaxis.type === 'datetime' &&
+ w.config.xaxis.labels.formatter === undefined
+ ) {
+ this.ctx.timeScale = new TimeScale(this.ctx)
+ let formattedTimeScale = []
+ if (
+ isFinite(w.globals.minX) &&
+ isFinite(w.globals.maxX) &&
+ !w.globals.isBarHorizontal
+ ) {
+ formattedTimeScale = this.ctx.timeScale.calculateTimeScaleTicks(
+ w.globals.minX,
+ w.globals.maxX
+ )
+ } else if (w.globals.isBarHorizontal) {
+ formattedTimeScale = this.ctx.timeScale.calculateTimeScaleTicks(
+ w.globals.minY,
+ w.globals.maxY
+ )
+ }
+ this.ctx.timeScale.recalcDimensionsBasedOnFormat(formattedTimeScale)
+ }
+
+ const coreUtils = new CoreUtils(this.ctx)
+ xyRatios = coreUtils.getCalculatedRatios()
+ }
+ return xyRatios
+ }
+
+ updateSourceChart(targetChart) {
+ this.ctx.w.globals.selection = undefined
+ this.ctx.updateHelpers._updateOptions(
+ {
+ chart: {
+ selection: {
+ xaxis: {
+ min: targetChart.w.globals.minX,
+ max: targetChart.w.globals.maxX,
+ },
+ },
+ },
+ },
+ false,
+ false
+ )
+ }
+
+ setupBrushHandler() {
+ const { w } = this
+
+ if (!w.config.chart.brush.enabled) return
+
+ if (typeof w.config.chart.events.selection !== 'function') {
+ const targets = Array.isArray(w.config.chart.brush.targets)
+ ? w.config.chart.brush.targets
+ : [w.config.chart.brush.target]
+ targets.forEach((target) => {
+ const targetChart = ApexCharts.getChartByID(target)
+ targetChart.w.globals.brushSource = this.ctx
+
+ if (typeof targetChart.w.config.chart.events.zoomed !== 'function') {
+ targetChart.w.config.chart.events.zoomed = () =>
+ this.updateSourceChart(targetChart)
+ }
+ if (typeof targetChart.w.config.chart.events.scrolled !== 'function') {
+ targetChart.w.config.chart.events.scrolled = () =>
+ this.updateSourceChart(targetChart)
+ }
+ })
+
+ w.config.chart.events.selection = (chart, e) => {
+ targets.forEach((target) => {
+ const targetChart = ApexCharts.getChartByID(target)
+ targetChart.ctx.updateHelpers._updateOptions(
+ {
+ xaxis: {
+ min: e.xaxis.min,
+ max: e.xaxis.max,
+ },
+ },
+ false,
+ false,
+ false,
+ false
+ )
+ })
+ }
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/CoreUtils.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/CoreUtils.js
new file mode 100644
index 0000000..eb075bc
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/CoreUtils.js
@@ -0,0 +1,642 @@
+/*
+ ** Util functions which are dependent on ApexCharts instance
+ */
+
+class CoreUtils {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ static checkComboSeries(series, chartType) {
+ let comboCharts = false
+ let comboBarCount = 0
+ let comboCount = 0
+
+ if (chartType === undefined) {
+ chartType = 'line'
+ }
+
+ // Check if user specified a type in series that may make us a combo chart.
+ // The default type for chart is "line" and the default for series is the
+ // chart type, therefore, if the types of all series match the chart type,
+ // this should not be considered a combo chart.
+ if (series.length && typeof series[0].type !== 'undefined') {
+ series.forEach((s) => {
+ if (
+ s.type === 'bar' ||
+ s.type === 'column' ||
+ s.type === 'candlestick' ||
+ s.type === 'boxPlot'
+ ) {
+ comboBarCount++
+ }
+ if (typeof s.type !== 'undefined' && s.type !== chartType) {
+ comboCount++
+ }
+ })
+ }
+ if (comboCount > 0) {
+ comboCharts = true
+ }
+
+ return {
+ comboBarCount,
+ comboCharts,
+ }
+ }
+
+ /**
+ * @memberof CoreUtils
+ * returns the sum of all individual values in a multiple stacked series
+ * Eg. w.globals.series = [[32,33,43,12], [2,3,5,1]]
+ * @return [34,36,48,13]
+ **/
+ getStackedSeriesTotals(excludedSeriesIndices = []) {
+ const w = this.w
+ let total = []
+
+ if (w.globals.series.length === 0) return total
+
+ for (
+ let i = 0;
+ i < w.globals.series[w.globals.maxValsInArrayIndex].length;
+ i++
+ ) {
+ let t = 0
+ for (let j = 0; j < w.globals.series.length; j++) {
+ if (
+ typeof w.globals.series[j][i] !== 'undefined' &&
+ excludedSeriesIndices.indexOf(j) === -1
+ ) {
+ t += w.globals.series[j][i]
+ }
+ }
+ total.push(t)
+ }
+ return total
+ }
+
+ // get total of the all values inside all series
+ getSeriesTotalByIndex(index = null) {
+ if (index === null) {
+ // non-plot chart types - pie / donut / circle
+ return this.w.config.series.reduce((acc, cur) => acc + cur, 0)
+ } else {
+ // axis charts - supporting multiple series
+ return this.w.globals.series[index].reduce((acc, cur) => acc + cur, 0)
+ }
+ }
+
+ /**
+ * @memberof CoreUtils
+ * returns the sum of values in a multiple stacked grouped charts
+ * Eg. w.globals.series = [[32,33,43,12], [2,3,5,1], [43, 23, 34, 22]]
+ * series 1 and 2 are in a group, while series 3 is in another group
+ * @return [[34, 36, 48, 12], [43, 23, 34, 22]]
+ **/
+ getStackedSeriesTotalsByGroups() {
+ const w = this.w
+ let total = []
+
+ w.globals.seriesGroups.forEach((sg) => {
+ let includedIndexes = []
+ w.config.series.forEach((s, si) => {
+ if (sg.indexOf(w.globals.seriesNames[si]) > -1) {
+ includedIndexes.push(si)
+ }
+ })
+
+ const excludedIndices = w.globals.series
+ .map((_, fi) => (includedIndexes.indexOf(fi) === -1 ? fi : -1))
+ .filter((f) => f !== -1)
+
+ total.push(this.getStackedSeriesTotals(excludedIndices))
+ })
+ return total
+ }
+
+ setSeriesYAxisMappings() {
+ const gl = this.w.globals
+ const cnf = this.w.config
+
+ // The old config method to map multiple series to a y axis is to
+ // include one yaxis config per series but set each yaxis seriesName to the
+ // same series name. This relies on indexing equivalence to map series to
+ // an axis: series[n] => yaxis[n]. This needs to be retained for compatibility.
+ // But we introduce an alternative that explicitly configures yaxis elements
+ // with the series that will be referenced to them (seriesName: []). This
+ // only requires including the yaxis elements that will be seen on the chart.
+ // Old way:
+ // ya: s
+ // 0: 0
+ // 1: 1
+ // 2: 1
+ // 3: 1
+ // 4: 1
+ // Axes 0..4 are all scaled and all will be rendered unless the axes are
+ // show: false. If the chart is stacked, it's assumed that series 1..4 are
+ // the contributing series. This is not particularly intuitive.
+ // New way:
+ // ya: s
+ // 0: [0]
+ // 1: [1,2,3,4]
+ // If the chart is stacked, it can be assumed that any axis with multiple
+ // series is stacked.
+ //
+ // If this is an old chart and we are being backward compatible, it will be
+ // expected that each series is associated with it's corresponding yaxis
+ // through their indices, one-to-one.
+ // If yaxis.seriesName matches series.name, we have indices yi and si.
+ // A name match where yi != si is interpretted as yaxis[yi] and yaxis[si]
+ // will both be scaled to fit the combined series[si] and series[yi].
+ // Consider series named: S0,S1,S2 and yaxes A0,A1,A2.
+ //
+ // Example 1: A0 and A1 scaled the same.
+ // A0.seriesName: S0
+ // A1.seriesName: S0
+ // A2.seriesName: S2
+ // Then A1 <-> A0
+ //
+ // Example 2: A0, A1 and A2 all scaled the same.
+ // A0.seriesName: S2
+ // A1.seriesName: S0
+ // A2.seriesName: S1
+ // A0 <-> A2, A1 <-> A0, A2 <-> A1 --->>> A0 <-> A1 <-> A2
+
+ let axisSeriesMap = []
+ let seriesYAxisReverseMap = []
+ let unassignedSeriesIndices = []
+ let seriesNameArrayStyle =
+ gl.series.length > cnf.yaxis.length ||
+ cnf.yaxis.some((a) => Array.isArray(a.seriesName))
+
+ cnf.series.forEach((s, i) => {
+ unassignedSeriesIndices.push(i)
+ seriesYAxisReverseMap.push(null)
+ })
+ cnf.yaxis.forEach((yaxe, yi) => {
+ axisSeriesMap[yi] = []
+ })
+
+ let unassignedYAxisIndices = []
+
+ // here, we loop through the yaxis array and find the item which has "seriesName" property
+ cnf.yaxis.forEach((yaxe, yi) => {
+ let assigned = false
+ // Allow seriesName to be either a string (for backward compatibility),
+ // in which case, handle multiple yaxes referencing the same series.
+ // or an array of strings so that a yaxis can reference multiple series.
+ // Feature request #4237
+ if (yaxe.seriesName) {
+ let seriesNames = []
+ if (Array.isArray(yaxe.seriesName)) {
+ seriesNames = yaxe.seriesName
+ } else {
+ seriesNames.push(yaxe.seriesName)
+ }
+ seriesNames.forEach((name) => {
+ cnf.series.forEach((s, si) => {
+ if (s.name === name) {
+ let remove = si
+ if (yi === si || seriesNameArrayStyle) {
+ // New style, don't allow series to be double referenced
+ if (
+ !seriesNameArrayStyle ||
+ unassignedSeriesIndices.indexOf(si) > -1
+ ) {
+ axisSeriesMap[yi].push([yi, si])
+ } else {
+ console.warn(
+ "Series '" +
+ s.name +
+ "' referenced more than once in what looks like the new style." +
+ ' That is, when using either seriesName: [],' +
+ ' or when there are more series than yaxes.'
+ )
+ }
+ } else {
+ // The series index refers to the target yaxis and the current
+ // yaxis index refers to the actual referenced series.
+ axisSeriesMap[si].push([si, yi])
+ remove = yi
+ }
+ assigned = true
+ remove = unassignedSeriesIndices.indexOf(remove)
+ if (remove !== -1) {
+ unassignedSeriesIndices.splice(remove, 1)
+ }
+ }
+ })
+ })
+ }
+ if (!assigned) {
+ unassignedYAxisIndices.push(yi)
+ }
+ })
+ axisSeriesMap = axisSeriesMap.map((yaxe, yi) => {
+ let ra = []
+ yaxe.forEach((sa) => {
+ seriesYAxisReverseMap[sa[1]] = sa[0]
+ ra.push(sa[1])
+ })
+ return ra
+ })
+
+ // All series referenced directly by yaxes have been assigned to those axes.
+ // Any series so far unassigned will be assigned to any yaxes that have yet
+ // to reference series directly, one-for-one in order of appearance, with
+ // all left-over series assigned to either the last unassigned yaxis, or the
+ // last yaxis if all have assigned series. This captures the
+ // default single and multiaxis config options which simply includes zero,
+ // one or as many yaxes as there are series but do not reference them by name.
+ let lastUnassignedYAxis = cnf.yaxis.length - 1
+ for (let i = 0; i < unassignedYAxisIndices.length; i++) {
+ lastUnassignedYAxis = unassignedYAxisIndices[i]
+ axisSeriesMap[lastUnassignedYAxis] = []
+ if (unassignedSeriesIndices) {
+ let si = unassignedSeriesIndices[0]
+ unassignedSeriesIndices.shift()
+ axisSeriesMap[lastUnassignedYAxis].push(si)
+ seriesYAxisReverseMap[si] = lastUnassignedYAxis
+ } else {
+ break
+ }
+ }
+
+ unassignedSeriesIndices.forEach((i) => {
+ axisSeriesMap[lastUnassignedYAxis].push(i)
+ seriesYAxisReverseMap[i] = lastUnassignedYAxis
+ })
+
+ // For the old-style seriesName-as-string-only, leave the zero-length yaxis
+ // array elements in for compatibility so that series.length == yaxes.length
+ // for multi axis charts.
+ gl.seriesYAxisMap = axisSeriesMap.map((x) => x)
+ gl.seriesYAxisReverseMap = seriesYAxisReverseMap.map((x) => x)
+ // Set default series group names
+ gl.seriesYAxisMap.forEach((axisSeries, ai) => {
+ axisSeries.forEach((si) => {
+ // series may be bare until loaded in realtime
+ if (cnf.series[si] && cnf.series[si].group === undefined) {
+ // A series with no group defined will be named after the axis that
+ // referenced it and thus form a group automatically.
+ cnf.series[si].group = 'apexcharts-axis-'.concat(ai.toString())
+ }
+ })
+ })
+ }
+
+ isSeriesNull(index = null) {
+ let r = []
+ if (index === null) {
+ // non-plot chart types - pie / donut / circle
+ r = this.w.config.series.filter((d) => d !== null)
+ } else {
+ // axis charts - supporting multiple series
+ r = this.w.config.series[index].data.filter((d) => d !== null)
+ }
+
+ return r.length === 0
+ }
+
+ seriesHaveSameValues(index) {
+ return this.w.globals.series[index].every((val, i, arr) => val === arr[0])
+ }
+
+ getCategoryLabels(labels) {
+ const w = this.w
+ let catLabels = labels.slice()
+ if (w.config.xaxis.convertedCatToNumeric) {
+ catLabels = labels.map((i, li) => {
+ return w.config.xaxis.labels.formatter(i - w.globals.minX + 1)
+ })
+ }
+ return catLabels
+ }
+ // maxValsInArrayIndex is the index of series[] which has the largest number of items
+ getLargestSeries() {
+ const w = this.w
+ w.globals.maxValsInArrayIndex = w.globals.series
+ .map((a) => a.length)
+ .indexOf(
+ Math.max.apply(
+ Math,
+ w.globals.series.map((a) => a.length)
+ )
+ )
+ }
+
+ getLargestMarkerSize() {
+ const w = this.w
+ let size = 0
+
+ w.globals.markers.size.forEach((m) => {
+ size = Math.max(size, m)
+ })
+
+ if (w.config.markers.discrete && w.config.markers.discrete.length) {
+ w.config.markers.discrete.forEach((m) => {
+ size = Math.max(size, m.size)
+ })
+ }
+
+ if (size > 0) {
+ if (w.config.markers.hover.size > 0) {
+ size = w.config.markers.hover.size
+ } else {
+ size += w.config.markers.hover.sizeOffset
+ }
+ }
+
+ w.globals.markers.largestSize = size
+
+ return size
+ }
+
+ /**
+ * @memberof Core
+ * returns the sum of all values in a series
+ * Eg. w.globals.series = [[32,33,43,12], [2,3,5,1]]
+ * @return [120, 11]
+ **/
+ getSeriesTotals() {
+ const w = this.w
+
+ w.globals.seriesTotals = w.globals.series.map((ser, index) => {
+ let total = 0
+
+ if (Array.isArray(ser)) {
+ for (let j = 0; j < ser.length; j++) {
+ total += ser[j]
+ }
+ } else {
+ // for pie/donuts/gauges
+ total += ser
+ }
+
+ return total
+ })
+ }
+
+ getSeriesTotalsXRange(minX, maxX) {
+ const w = this.w
+
+ const seriesTotalsXRange = w.globals.series.map((ser, index) => {
+ let total = 0
+
+ for (let j = 0; j < ser.length; j++) {
+ if (
+ w.globals.seriesX[index][j] > minX &&
+ w.globals.seriesX[index][j] < maxX
+ ) {
+ total += ser[j]
+ }
+ }
+
+ return total
+ })
+
+ return seriesTotalsXRange
+ }
+
+ /**
+ * @memberof CoreUtils
+ * returns the percentage value of all individual values which can be used in a 100% stacked series
+ * Eg. w.globals.series = [[32, 33, 43, 12], [2, 3, 5, 1]]
+ * @return [[94.11, 91.66, 89.58, 92.30], [5.88, 8.33, 10.41, 7.7]]
+ **/
+ getPercentSeries() {
+ const w = this.w
+
+ w.globals.seriesPercent = w.globals.series.map((ser, index) => {
+ let seriesPercent = []
+ if (Array.isArray(ser)) {
+ for (let j = 0; j < ser.length; j++) {
+ let total = w.globals.stackedSeriesTotals[j]
+ let percent = 0
+ if (total) {
+ percent = (100 * ser[j]) / total
+ }
+ seriesPercent.push(percent)
+ }
+ } else {
+ const total = w.globals.seriesTotals.reduce((acc, val) => acc + val, 0)
+ let percent = (100 * ser) / total
+ seriesPercent.push(percent)
+ }
+
+ return seriesPercent
+ })
+ }
+
+ getCalculatedRatios() {
+ let w = this.w
+ let gl = w.globals
+
+ let yRatio = []
+ let invertedYRatio = 0
+ let xRatio = 0
+ let invertedXRatio = 0
+ let zRatio = 0
+ let baseLineY = []
+ let baseLineInvertedY = 0.1
+ let baseLineX = 0
+
+ gl.yRange = []
+ if (gl.isMultipleYAxis) {
+ for (let i = 0; i < gl.minYArr.length; i++) {
+ gl.yRange.push(Math.abs(gl.minYArr[i] - gl.maxYArr[i]))
+ baseLineY.push(0)
+ }
+ } else {
+ gl.yRange.push(Math.abs(gl.minY - gl.maxY))
+ }
+ gl.xRange = Math.abs(gl.maxX - gl.minX)
+ gl.zRange = Math.abs(gl.maxZ - gl.minZ)
+
+ // multiple y axis
+ for (let i = 0; i < gl.yRange.length; i++) {
+ yRatio.push(gl.yRange[i] / gl.gridHeight)
+ }
+
+ xRatio = gl.xRange / gl.gridWidth
+
+ invertedYRatio = gl.yRange / gl.gridWidth
+ invertedXRatio = gl.xRange / gl.gridHeight
+ zRatio = (gl.zRange / gl.gridHeight) * 16
+
+ if (!zRatio) {
+ zRatio = 1
+ }
+
+ if (gl.minY !== Number.MIN_VALUE && Math.abs(gl.minY) !== 0) {
+ // Negative numbers present in series
+ gl.hasNegs = true
+ }
+
+ // Check we have a map as series may still to be added/updated.
+ if (w.globals.seriesYAxisReverseMap.length > 0) {
+ let scaleBaseLineYScale = (y, i) => {
+ let yAxis = w.config.yaxis[w.globals.seriesYAxisReverseMap[i]]
+ let sign = y < 0 ? -1 : 1
+ y = Math.abs(y)
+ if (yAxis.logarithmic) {
+ y = this.getBaseLog(yAxis.logBase, y)
+ }
+ return (-sign * y) / yRatio[i]
+ }
+ if (gl.isMultipleYAxis) {
+ baseLineY = []
+ // baseline variables is the 0 of the yaxis which will be needed when there are negatives
+ for (let i = 0; i < yRatio.length; i++) {
+ baseLineY.push(scaleBaseLineYScale(gl.minYArr[i], i))
+ }
+ } else {
+ baseLineY = []
+ baseLineY.push(scaleBaseLineYScale(gl.minY, 0))
+
+ if (gl.minY !== Number.MIN_VALUE && Math.abs(gl.minY) !== 0) {
+ baseLineInvertedY = -gl.minY / invertedYRatio // this is for bar chart
+ baseLineX = gl.minX / xRatio
+ }
+ }
+ } else {
+ baseLineY = []
+ baseLineY.push(0)
+ baseLineInvertedY = 0
+ baseLineX = 0
+ }
+
+ return {
+ yRatio,
+ invertedYRatio,
+ zRatio,
+ xRatio,
+ invertedXRatio,
+ baseLineInvertedY,
+ baseLineY,
+ baseLineX,
+ }
+ }
+
+ getLogSeries(series) {
+ const w = this.w
+
+ w.globals.seriesLog = series.map((s, i) => {
+ let yAxisIndex = w.globals.seriesYAxisReverseMap[i]
+ if (
+ w.config.yaxis[yAxisIndex] &&
+ w.config.yaxis[yAxisIndex].logarithmic
+ ) {
+ return s.map((d) => {
+ if (d === null) return null
+ return this.getLogVal(w.config.yaxis[yAxisIndex].logBase, d, i)
+ })
+ } else {
+ return s
+ }
+ })
+
+ return w.globals.invalidLogScale ? series : w.globals.seriesLog
+ }
+ getBaseLog(base, value) {
+ return Math.log(value) / Math.log(base)
+ }
+ getLogVal(b, d, seriesIndex) {
+ if (d <= 0) {
+ return 0 // Should be Number.NEGATIVE_INFINITY
+ }
+ const w = this.w
+ const min_log_val =
+ w.globals.minYArr[seriesIndex] === 0
+ ? -1 // make sure we dont calculate log of 0
+ : this.getBaseLog(b, w.globals.minYArr[seriesIndex])
+ const max_log_val =
+ w.globals.maxYArr[seriesIndex] === 0
+ ? 0 // make sure we dont calculate log of 0
+ : this.getBaseLog(b, w.globals.maxYArr[seriesIndex])
+ const number_of_height_levels = max_log_val - min_log_val
+ if (d < 1) return d / number_of_height_levels
+ const log_height_value = this.getBaseLog(b, d) - min_log_val
+ return log_height_value / number_of_height_levels
+ }
+
+ getLogYRatios(yRatio) {
+ const w = this.w
+ const gl = this.w.globals
+
+ gl.yLogRatio = yRatio.slice()
+
+ gl.logYRange = gl.yRange.map((_, i) => {
+ let yAxisIndex = w.globals.seriesYAxisReverseMap[i]
+ if (
+ w.config.yaxis[yAxisIndex] &&
+ this.w.config.yaxis[yAxisIndex].logarithmic
+ ) {
+ let maxY = -Number.MAX_VALUE
+ let minY = Number.MIN_VALUE
+ let range = 1
+ gl.seriesLog.forEach((s, si) => {
+ s.forEach((v) => {
+ if (w.config.yaxis[si] && w.config.yaxis[si].logarithmic) {
+ maxY = Math.max(v, maxY)
+ minY = Math.min(v, minY)
+ }
+ })
+ })
+
+ range = Math.pow(gl.yRange[i], Math.abs(minY - maxY) / gl.yRange[i])
+
+ gl.yLogRatio[i] = range / gl.gridHeight
+ return range
+ }
+ })
+
+ return gl.invalidLogScale ? yRatio.slice() : gl.yLogRatio
+ }
+
+ // Some config objects can be array - and we need to extend them correctly
+ static extendArrayProps(configInstance, options, w) {
+ if (options?.yaxis) {
+ options = configInstance.extendYAxis(options, w)
+ }
+ if (options?.annotations) {
+ if (options.annotations.yaxis) {
+ options = configInstance.extendYAxisAnnotations(options)
+ }
+ if (options?.annotations?.xaxis) {
+ options = configInstance.extendXAxisAnnotations(options)
+ }
+ if (options?.annotations?.points) {
+ options = configInstance.extendPointAnnotations(options)
+ }
+ }
+
+ return options
+ }
+
+ // Series of the same group and type can be stacked together distinct from
+ // other series of the same type on the same axis.
+ drawSeriesByGroup(typeSeries, typeGroups, type, chartClass) {
+ let w = this.w
+ let graph = []
+ if (typeSeries.series.length > 0) {
+ // draw each group separately
+ typeGroups.forEach((gn) => {
+ let gs = []
+ let gi = []
+ typeSeries.i.forEach((i, ii) => {
+ if (w.config.series[i].group === gn) {
+ gs.push(typeSeries.series[ii])
+ gi.push(i)
+ }
+ })
+ gs.length > 0 && graph.push(chartClass.draw(gs, type, gi))
+ })
+ }
+ return graph
+ }
+}
+
+export default CoreUtils
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Crosshairs.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Crosshairs.js
new file mode 100644
index 0000000..3f77823
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Crosshairs.js
@@ -0,0 +1,138 @@
+import Graphics from './Graphics'
+import Filters from './Filters'
+import Utils from '../utils/Utils'
+
+class Crosshairs {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ drawXCrosshairs() {
+ const w = this.w
+
+ let graphics = new Graphics(this.ctx)
+ let filters = new Filters(this.ctx)
+
+ let crosshairGradient = w.config.xaxis.crosshairs.fill.gradient
+ let crosshairShadow = w.config.xaxis.crosshairs.dropShadow
+
+ let fillType = w.config.xaxis.crosshairs.fill.type
+ let gradientFrom = crosshairGradient.colorFrom
+ let gradientTo = crosshairGradient.colorTo
+ let opacityFrom = crosshairGradient.opacityFrom
+ let opacityTo = crosshairGradient.opacityTo
+ let stops = crosshairGradient.stops
+
+ let shadow = 'none'
+ let dropShadow = crosshairShadow.enabled
+ let shadowLeft = crosshairShadow.left
+ let shadowTop = crosshairShadow.top
+ let shadowBlur = crosshairShadow.blur
+ let shadowColor = crosshairShadow.color
+ let shadowOpacity = crosshairShadow.opacity
+
+ let xcrosshairsFill = w.config.xaxis.crosshairs.fill.color
+
+ if (w.config.xaxis.crosshairs.show) {
+ if (fillType === 'gradient') {
+ xcrosshairsFill = graphics.drawGradient(
+ 'vertical',
+ gradientFrom,
+ gradientTo,
+ opacityFrom,
+ opacityTo,
+ null,
+ stops,
+ null
+ )
+ }
+
+ let xcrosshairs = graphics.drawRect()
+ if (w.config.xaxis.crosshairs.width === 1) {
+ // to prevent drawing 2 lines, convert rect to line
+ xcrosshairs = graphics.drawLine()
+ }
+
+ let gridHeight = w.globals.gridHeight
+ if (!Utils.isNumber(gridHeight) || gridHeight < 0) {
+ gridHeight = 0
+ }
+ let crosshairsWidth = w.config.xaxis.crosshairs.width
+ if (!Utils.isNumber(crosshairsWidth) || crosshairsWidth < 0) {
+ crosshairsWidth = 0
+ }
+
+ xcrosshairs.attr({
+ class: 'apexcharts-xcrosshairs',
+ x: 0,
+ y: 0,
+ y2: gridHeight,
+ width: crosshairsWidth,
+ height: gridHeight,
+ fill: xcrosshairsFill,
+ filter: shadow,
+ 'fill-opacity': w.config.xaxis.crosshairs.opacity,
+ stroke: w.config.xaxis.crosshairs.stroke.color,
+ 'stroke-width': w.config.xaxis.crosshairs.stroke.width,
+ 'stroke-dasharray': w.config.xaxis.crosshairs.stroke.dashArray
+ })
+
+ if (dropShadow) {
+ xcrosshairs = filters.dropShadow(xcrosshairs, {
+ left: shadowLeft,
+ top: shadowTop,
+ blur: shadowBlur,
+ color: shadowColor,
+ opacity: shadowOpacity
+ })
+ }
+
+ w.globals.dom.elGraphical.add(xcrosshairs)
+ }
+ }
+
+ drawYCrosshairs() {
+ const w = this.w
+
+ let graphics = new Graphics(this.ctx)
+
+ let crosshair = w.config.yaxis[0].crosshairs
+ const offX = w.globals.barPadForNumericAxis
+
+ if (w.config.yaxis[0].crosshairs.show) {
+ let ycrosshairs = graphics.drawLine(
+ -offX,
+ 0,
+ w.globals.gridWidth + offX,
+ 0,
+ crosshair.stroke.color,
+ crosshair.stroke.dashArray,
+ crosshair.stroke.width
+ )
+ ycrosshairs.attr({
+ class: 'apexcharts-ycrosshairs'
+ })
+
+ w.globals.dom.elGraphical.add(ycrosshairs)
+ }
+
+ // draw an invisible crosshair to help in positioning the yaxis tooltip
+ let ycrosshairsHidden = graphics.drawLine(
+ -offX,
+ 0,
+ w.globals.gridWidth + offX,
+ 0,
+ crosshair.stroke.color,
+ 0,
+ 0
+ )
+ ycrosshairsHidden.attr({
+ class: 'apexcharts-ycrosshairs-hidden'
+ })
+
+ w.globals.dom.elGraphical.add(ycrosshairsHidden)
+ }
+}
+
+export default Crosshairs
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Data.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Data.js
new file mode 100644
index 0000000..dc4f299
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Data.js
@@ -0,0 +1,743 @@
+import CoreUtils from './CoreUtils'
+import DateTime from './../utils/DateTime'
+import Series from './Series'
+import Utils from '../utils/Utils'
+import Defaults from './settings/Defaults'
+
+export default class Data {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.twoDSeries = []
+ this.threeDSeries = []
+ this.twoDSeriesX = []
+ this.seriesGoals = []
+ this.coreUtils = new CoreUtils(this.ctx)
+ }
+
+ isMultiFormat() {
+ return this.isFormatXY() || this.isFormat2DArray()
+ }
+
+ // given format is [{x, y}, {x, y}]
+ isFormatXY() {
+ const series = this.w.config.series.slice()
+
+ const sr = new Series(this.ctx)
+ this.activeSeriesIndex = sr.getActiveConfigSeriesIndex()
+
+ if (
+ typeof series[this.activeSeriesIndex].data !== 'undefined' &&
+ series[this.activeSeriesIndex].data.length > 0 &&
+ series[this.activeSeriesIndex].data[0] !== null &&
+ typeof series[this.activeSeriesIndex].data[0].x !== 'undefined' &&
+ series[this.activeSeriesIndex].data[0] !== null
+ ) {
+ return true
+ }
+ }
+
+ // given format is [[x, y], [x, y]]
+ isFormat2DArray() {
+ const series = this.w.config.series.slice()
+
+ const sr = new Series(this.ctx)
+ this.activeSeriesIndex = sr.getActiveConfigSeriesIndex()
+
+ if (
+ typeof series[this.activeSeriesIndex].data !== 'undefined' &&
+ series[this.activeSeriesIndex].data.length > 0 &&
+ typeof series[this.activeSeriesIndex].data[0] !== 'undefined' &&
+ series[this.activeSeriesIndex].data[0] !== null &&
+ series[this.activeSeriesIndex].data[0].constructor === Array
+ ) {
+ return true
+ }
+ }
+
+ handleFormat2DArray(ser, i) {
+ const cnf = this.w.config
+ const gl = this.w.globals
+
+ const isBoxPlot =
+ cnf.chart.type === 'boxPlot' || cnf.series[i].type === 'boxPlot'
+
+ for (let j = 0; j < ser[i].data.length; j++) {
+ if (typeof ser[i].data[j][1] !== 'undefined') {
+ if (
+ Array.isArray(ser[i].data[j][1]) &&
+ ser[i].data[j][1].length === 4 &&
+ !isBoxPlot
+ ) {
+ // candlestick nested ohlc format
+ this.twoDSeries.push(Utils.parseNumber(ser[i].data[j][1][3]))
+ } else if (ser[i].data[j].length >= 5) {
+ // candlestick non-nested ohlc format
+ this.twoDSeries.push(Utils.parseNumber(ser[i].data[j][4]))
+ } else {
+ this.twoDSeries.push(Utils.parseNumber(ser[i].data[j][1]))
+ }
+ gl.dataFormatXNumeric = true
+ }
+ if (cnf.xaxis.type === 'datetime') {
+ // if timestamps are provided and xaxis type is datetime,
+
+ let ts = new Date(ser[i].data[j][0])
+ ts = new Date(ts).getTime()
+ this.twoDSeriesX.push(ts)
+ } else {
+ this.twoDSeriesX.push(ser[i].data[j][0])
+ }
+ }
+
+ for (let j = 0; j < ser[i].data.length; j++) {
+ if (typeof ser[i].data[j][2] !== 'undefined') {
+ this.threeDSeries.push(ser[i].data[j][2])
+ gl.isDataXYZ = true
+ }
+ }
+ }
+
+ handleFormatXY(ser, i) {
+ const cnf = this.w.config
+ const gl = this.w.globals
+
+ const dt = new DateTime(this.ctx)
+
+ let activeI = i
+ if (gl.collapsedSeriesIndices.indexOf(i) > -1) {
+ // fix #368
+ activeI = this.activeSeriesIndex
+ }
+
+ // get series
+ for (let j = 0; j < ser[i].data.length; j++) {
+ if (typeof ser[i].data[j].y !== 'undefined') {
+ if (Array.isArray(ser[i].data[j].y)) {
+ this.twoDSeries.push(
+ Utils.parseNumber(ser[i].data[j].y[ser[i].data[j].y.length - 1])
+ )
+ } else {
+ this.twoDSeries.push(Utils.parseNumber(ser[i].data[j].y))
+ }
+ }
+
+ if (
+ typeof ser[i].data[j].goals !== 'undefined' &&
+ Array.isArray(ser[i].data[j].goals)
+ ) {
+ if (typeof this.seriesGoals[i] === 'undefined') {
+ this.seriesGoals[i] = []
+ }
+ this.seriesGoals[i].push(ser[i].data[j].goals)
+ } else {
+ if (typeof this.seriesGoals[i] === 'undefined') {
+ this.seriesGoals[i] = []
+ }
+ this.seriesGoals[i].push(null)
+ }
+ }
+
+ // get seriesX
+ for (let j = 0; j < ser[activeI].data.length; j++) {
+ const isXString = typeof ser[activeI].data[j].x === 'string'
+ const isXArr = Array.isArray(ser[activeI].data[j].x)
+ const isXDate = !isXArr && !!dt.isValidDate(ser[activeI].data[j].x)
+
+ if (isXString || isXDate) {
+ // user supplied '01/01/2017' or a date string (a JS date object is not supported)
+ if (isXString || cnf.xaxis.convertedCatToNumeric) {
+ const isRangeColumn = gl.isBarHorizontal && gl.isRangeData
+
+ if (cnf.xaxis.type === 'datetime' && !isRangeColumn) {
+ this.twoDSeriesX.push(dt.parseDate(ser[activeI].data[j].x))
+ } else {
+ // a category and not a numeric x value
+ this.fallbackToCategory = true
+ this.twoDSeriesX.push(ser[activeI].data[j].x)
+
+ if (
+ !isNaN(ser[activeI].data[j].x) &&
+ this.w.config.xaxis.type !== 'category' &&
+ typeof ser[activeI].data[j].x !== 'string'
+ ) {
+ gl.isXNumeric = true
+ }
+ }
+ } else {
+ if (cnf.xaxis.type === 'datetime') {
+ this.twoDSeriesX.push(
+ dt.parseDate(ser[activeI].data[j].x.toString())
+ )
+ } else {
+ gl.dataFormatXNumeric = true
+ gl.isXNumeric = true
+ this.twoDSeriesX.push(parseFloat(ser[activeI].data[j].x))
+ }
+ }
+ } else if (isXArr) {
+ // a multiline label described in array format
+ this.fallbackToCategory = true
+ this.twoDSeriesX.push(ser[activeI].data[j].x)
+ } else {
+ // a numeric value in x property
+ gl.isXNumeric = true
+ gl.dataFormatXNumeric = true
+ this.twoDSeriesX.push(ser[activeI].data[j].x)
+ }
+ }
+
+ if (ser[i].data[0] && typeof ser[i].data[0].z !== 'undefined') {
+ for (let t = 0; t < ser[i].data.length; t++) {
+ this.threeDSeries.push(ser[i].data[t].z)
+ }
+ gl.isDataXYZ = true
+ }
+ }
+
+ handleRangeData(ser, i) {
+ const gl = this.w.globals
+
+ let range = {}
+ if (this.isFormat2DArray()) {
+ range = this.handleRangeDataFormat('array', ser, i)
+ } else if (this.isFormatXY()) {
+ range = this.handleRangeDataFormat('xy', ser, i)
+ }
+
+ // Fix: RangeArea Chart: hide all series results in a crash #3984
+ gl.seriesRangeStart.push(range.start === undefined ? [] : range.start)
+ gl.seriesRangeEnd.push(range.end === undefined ? [] : range.end)
+
+ gl.seriesRange.push(range.rangeUniques)
+
+ // check for overlaps to avoid clashes in a timeline chart
+ gl.seriesRange.forEach((sr, si) => {
+ if (sr) {
+ sr.forEach((sarr, sarri) => {
+ sarr.y.forEach((arr, arri) => {
+ for (let sri = 0; sri < sarr.y.length; sri++) {
+ if (arri !== sri) {
+ const range1y1 = arr.y1
+ const range1y2 = arr.y2
+ const range2y1 = sarr.y[sri].y1
+ const range2y2 = sarr.y[sri].y2
+ if (range1y1 <= range2y2 && range2y1 <= range1y2) {
+ if (sarr.overlaps.indexOf(arr.rangeName) < 0) {
+ sarr.overlaps.push(arr.rangeName)
+ }
+ if (sarr.overlaps.indexOf(sarr.y[sri].rangeName) < 0) {
+ sarr.overlaps.push(sarr.y[sri].rangeName)
+ }
+ }
+ }
+ }
+ })
+ })
+ }
+ })
+
+ return range
+ }
+
+ handleCandleStickBoxData(ser, i) {
+ const gl = this.w.globals
+
+ let ohlc = {}
+ if (this.isFormat2DArray()) {
+ ohlc = this.handleCandleStickBoxDataFormat('array', ser, i)
+ } else if (this.isFormatXY()) {
+ ohlc = this.handleCandleStickBoxDataFormat('xy', ser, i)
+ }
+
+ gl.seriesCandleO[i] = ohlc.o
+ gl.seriesCandleH[i] = ohlc.h
+ gl.seriesCandleM[i] = ohlc.m
+ gl.seriesCandleL[i] = ohlc.l
+ gl.seriesCandleC[i] = ohlc.c
+
+ return ohlc
+ }
+
+ handleRangeDataFormat(format, ser, i) {
+ const rangeStart = []
+ const rangeEnd = []
+
+ const uniqueKeys = ser[i].data
+ .filter(
+ (thing, index, self) => index === self.findIndex((t) => t.x === thing.x)
+ )
+ .map((r, index) => {
+ return {
+ x: r.x,
+ overlaps: [],
+ y: [],
+ }
+ })
+
+ if (format === 'array') {
+ for (let j = 0; j < ser[i].data.length; j++) {
+ if (Array.isArray(ser[i].data[j])) {
+ rangeStart.push(ser[i].data[j][1][0])
+ rangeEnd.push(ser[i].data[j][1][1])
+ } else {
+ rangeStart.push(ser[i].data[j])
+ rangeEnd.push(ser[i].data[j])
+ }
+ }
+ } else if (format === 'xy') {
+ for (let j = 0; j < ser[i].data.length; j++) {
+ let isDataPoint2D = Array.isArray(ser[i].data[j].y)
+ const id = Utils.randomId()
+ const x = ser[i].data[j].x
+ const y = {
+ y1: isDataPoint2D ? ser[i].data[j].y[0] : ser[i].data[j].y,
+ y2: isDataPoint2D ? ser[i].data[j].y[1] : ser[i].data[j].y,
+ rangeName: id,
+ }
+
+ // CAUTION: mutating config object by adding a new property
+ // TODO: As this is specifically for timeline rangebar charts, update the docs mentioning the series only supports xy format
+ ser[i].data[j].rangeName = id
+
+ const uI = uniqueKeys.findIndex((t) => t.x === x)
+ uniqueKeys[uI].y.push(y)
+
+ rangeStart.push(y.y1)
+ rangeEnd.push(y.y2)
+ }
+ }
+
+ return {
+ start: rangeStart,
+ end: rangeEnd,
+ rangeUniques: uniqueKeys,
+ }
+ }
+
+ handleCandleStickBoxDataFormat(format, ser, i) {
+ const w = this.w
+ const isBoxPlot =
+ w.config.chart.type === 'boxPlot' || w.config.series[i].type === 'boxPlot'
+
+ const serO = []
+ const serH = []
+ const serM = []
+ const serL = []
+ const serC = []
+
+ if (format === 'array') {
+ if (
+ (isBoxPlot && ser[i].data[0].length === 6) ||
+ (!isBoxPlot && ser[i].data[0].length === 5)
+ ) {
+ for (let j = 0; j < ser[i].data.length; j++) {
+ serO.push(ser[i].data[j][1])
+ serH.push(ser[i].data[j][2])
+
+ if (isBoxPlot) {
+ serM.push(ser[i].data[j][3])
+ serL.push(ser[i].data[j][4])
+ serC.push(ser[i].data[j][5])
+ } else {
+ serL.push(ser[i].data[j][3])
+ serC.push(ser[i].data[j][4])
+ }
+ }
+ } else {
+ for (let j = 0; j < ser[i].data.length; j++) {
+ if (Array.isArray(ser[i].data[j][1])) {
+ serO.push(ser[i].data[j][1][0])
+ serH.push(ser[i].data[j][1][1])
+ if (isBoxPlot) {
+ serM.push(ser[i].data[j][1][2])
+ serL.push(ser[i].data[j][1][3])
+ serC.push(ser[i].data[j][1][4])
+ } else {
+ serL.push(ser[i].data[j][1][2])
+ serC.push(ser[i].data[j][1][3])
+ }
+ }
+ }
+ }
+ } else if (format === 'xy') {
+ for (let j = 0; j < ser[i].data.length; j++) {
+ if (Array.isArray(ser[i].data[j].y)) {
+ serO.push(ser[i].data[j].y[0])
+ serH.push(ser[i].data[j].y[1])
+ if (isBoxPlot) {
+ serM.push(ser[i].data[j].y[2])
+ serL.push(ser[i].data[j].y[3])
+ serC.push(ser[i].data[j].y[4])
+ } else {
+ serL.push(ser[i].data[j].y[2])
+ serC.push(ser[i].data[j].y[3])
+ }
+ }
+ }
+ }
+
+ return {
+ o: serO,
+ h: serH,
+ m: serM,
+ l: serL,
+ c: serC,
+ }
+ }
+
+ parseDataAxisCharts(ser, ctx = this.ctx) {
+ const cnf = this.w.config
+ const gl = this.w.globals
+
+ const dt = new DateTime(ctx)
+
+ const xlabels =
+ cnf.labels.length > 0 ? cnf.labels.slice() : cnf.xaxis.categories.slice()
+
+ gl.isRangeBar = cnf.chart.type === 'rangeBar' && gl.isBarHorizontal
+
+ gl.hasXaxisGroups =
+ cnf.xaxis.type === 'category' && cnf.xaxis.group.groups.length > 0
+ if (gl.hasXaxisGroups) {
+ gl.groups = cnf.xaxis.group.groups
+ }
+
+ ser.forEach((s, i) => {
+ if (s.name !== undefined) {
+ gl.seriesNames.push(s.name)
+ } else {
+ gl.seriesNames.push('series-' + parseInt(i + 1, 10))
+ }
+ })
+
+ this.coreUtils.setSeriesYAxisMappings()
+ // At this point, every series that didn't have a user defined group name
+ // has been given a name according to the yaxis the series is referenced by.
+ // This fits the existing behaviour where all series associated with an axis
+ // are defacto presented as a single group. It is now formalised.
+ let buckets = []
+ let groups = [...new Set(cnf.series.map((s) => s.group))]
+ cnf.series.forEach((s, i) => {
+ let index = groups.indexOf(s.group)
+ if (!buckets[index]) buckets[index] = []
+
+ buckets[index].push(gl.seriesNames[i])
+ })
+ gl.seriesGroups = buckets
+
+ const handleDates = () => {
+ for (let j = 0; j < xlabels.length; j++) {
+ if (typeof xlabels[j] === 'string') {
+ // user provided date strings
+ let isDate = dt.isValidDate(xlabels[j])
+ if (isDate) {
+ this.twoDSeriesX.push(dt.parseDate(xlabels[j]))
+ } else {
+ throw new Error(
+ 'You have provided invalid Date format. Please provide a valid JavaScript Date'
+ )
+ }
+ } else {
+ // user provided timestamps
+ this.twoDSeriesX.push(xlabels[j])
+ }
+ }
+ }
+
+ for (let i = 0; i < ser.length; i++) {
+ this.twoDSeries = []
+ this.twoDSeriesX = []
+ this.threeDSeries = []
+
+ if (typeof ser[i].data === 'undefined') {
+ console.error(
+ "It is a possibility that you may have not included 'data' property in series."
+ )
+ return
+ }
+
+ if (
+ cnf.chart.type === 'rangeBar' ||
+ cnf.chart.type === 'rangeArea' ||
+ ser[i].type === 'rangeBar' ||
+ ser[i].type === 'rangeArea'
+ ) {
+ gl.isRangeData = true
+ if (cnf.chart.type === 'rangeBar' || cnf.chart.type === 'rangeArea') {
+ this.handleRangeData(ser, i)
+ }
+ }
+
+ if (this.isMultiFormat()) {
+ if (this.isFormat2DArray()) {
+ this.handleFormat2DArray(ser, i)
+ } else if (this.isFormatXY()) {
+ this.handleFormatXY(ser, i)
+ }
+
+ if (
+ cnf.chart.type === 'candlestick' ||
+ ser[i].type === 'candlestick' ||
+ cnf.chart.type === 'boxPlot' ||
+ ser[i].type === 'boxPlot'
+ ) {
+ this.handleCandleStickBoxData(ser, i)
+ }
+
+ gl.series.push(this.twoDSeries)
+ gl.labels.push(this.twoDSeriesX)
+ gl.seriesX.push(this.twoDSeriesX)
+ gl.seriesGoals = this.seriesGoals
+
+ if (i === this.activeSeriesIndex && !this.fallbackToCategory) {
+ gl.isXNumeric = true
+ }
+ } else {
+ if (cnf.xaxis.type === 'datetime') {
+ // user didn't supplied [{x,y}] or [[x,y]], but single array in data.
+ // Also labels/categories were supplied differently
+ gl.isXNumeric = true
+
+ handleDates()
+
+ gl.seriesX.push(this.twoDSeriesX)
+ } else if (cnf.xaxis.type === 'numeric') {
+ gl.isXNumeric = true
+
+ if (xlabels.length > 0) {
+ this.twoDSeriesX = xlabels
+ gl.seriesX.push(this.twoDSeriesX)
+ }
+ }
+ gl.labels.push(this.twoDSeriesX)
+ const singleArray = ser[i].data.map((d) => Utils.parseNumber(d))
+ gl.series.push(singleArray)
+ }
+
+ gl.seriesZ.push(this.threeDSeries)
+
+ // overrided default color if user inputs color with series data
+ if (ser[i].color !== undefined) {
+ gl.seriesColors.push(ser[i].color)
+ } else {
+ gl.seriesColors.push(undefined)
+ }
+ }
+
+ return this.w
+ }
+
+ parseDataNonAxisCharts(ser) {
+ const gl = this.w.globals
+ const cnf = this.w.config
+
+ gl.series = ser.slice()
+ gl.seriesNames = cnf.labels.slice()
+ for (let i = 0; i < gl.series.length; i++) {
+ if (gl.seriesNames[i] === undefined) {
+ gl.seriesNames.push('series-' + (i + 1))
+ }
+ }
+
+ return this.w
+ }
+
+ /** User possibly set string categories in xaxis.categories or labels prop
+ * Or didn't set xaxis labels at all - in which case we manually do it.
+ * If user passed series data as [[3, 2], [4, 5]] or [{ x: 3, y: 55 }],
+ * this shouldn't be called
+ * @param {array} ser - the series which user passed to the config
+ */
+ handleExternalLabelsData(ser) {
+ const cnf = this.w.config
+ const gl = this.w.globals
+
+ if (cnf.xaxis.categories.length > 0) {
+ // user provided labels in xaxis.category prop
+ gl.labels = cnf.xaxis.categories
+ } else if (cnf.labels.length > 0) {
+ // user provided labels in labels props
+ gl.labels = cnf.labels.slice()
+ } else if (this.fallbackToCategory) {
+ // user provided labels in x prop in [{ x: 3, y: 55 }] data, and those labels are already stored in gl.labels[0], so just re-arrange the gl.labels array
+ gl.labels = gl.labels[0]
+
+ if (gl.seriesRange.length) {
+ gl.seriesRange.map((srt) => {
+ srt.forEach((sr) => {
+ if (gl.labels.indexOf(sr.x) < 0 && sr.x) {
+ gl.labels.push(sr.x)
+ }
+ })
+ })
+ // remove duplicate x-axis labels
+ gl.labels = Array.from(
+ new Set(gl.labels.map(JSON.stringify)),
+ JSON.parse
+ )
+ }
+
+ if (cnf.xaxis.convertedCatToNumeric) {
+ const defaults = new Defaults(cnf)
+ defaults.convertCatToNumericXaxis(cnf, this.ctx, gl.seriesX[0])
+ this._generateExternalLabels(ser)
+ }
+ } else {
+ this._generateExternalLabels(ser)
+ }
+ }
+
+ _generateExternalLabels(ser) {
+ const gl = this.w.globals
+ const cnf = this.w.config
+ // user didn't provided any labels, fallback to 1-2-3-4-5
+ let labelArr = []
+
+ if (gl.axisCharts) {
+ if (gl.series.length > 0) {
+ if (this.isFormatXY()) {
+ // in case there is a combo chart (boxplot/scatter)
+ // and there are duplicated x values, we need to eliminate duplicates
+ const seriesDataFiltered = cnf.series.map((serie, s) => {
+ return serie.data.filter(
+ (v, i, a) => a.findIndex((t) => t.x === v.x) === i
+ )
+ })
+
+ const len = seriesDataFiltered.reduce(
+ (p, c, i, a) => (a[p].length > c.length ? p : i),
+ 0
+ )
+
+ for (let i = 0; i < seriesDataFiltered[len].length; i++) {
+ labelArr.push(i + 1)
+ }
+ } else {
+ for (let i = 0; i < gl.series[gl.maxValsInArrayIndex].length; i++) {
+ labelArr.push(i + 1)
+ }
+ }
+ }
+
+ gl.seriesX = []
+ // create gl.seriesX as it will be used in calculations of x positions
+ for (let i = 0; i < ser.length; i++) {
+ gl.seriesX.push(labelArr)
+ }
+
+ // turn on the isXNumeric flag to allow minX and maxX to function properly
+ if (!this.w.globals.isBarHorizontal) {
+ gl.isXNumeric = true
+ }
+ }
+
+ // no series to pull labels from, put a 0-10 series
+ // possibly, user collapsed all series. Hence we can't work with above calc
+ if (labelArr.length === 0) {
+ labelArr = gl.axisCharts
+ ? []
+ : gl.series.map((gls, glsi) => {
+ return glsi + 1
+ })
+ for (let i = 0; i < ser.length; i++) {
+ gl.seriesX.push(labelArr)
+ }
+ }
+
+ // Finally, pass the labelArr in gl.labels which will be printed on x-axis
+ gl.labels = labelArr
+
+ if (cnf.xaxis.convertedCatToNumeric) {
+ gl.categoryLabels = labelArr.map((l) => {
+ return cnf.xaxis.labels.formatter(l)
+ })
+ }
+
+ // Turn on this global flag to indicate no labels were provided by user
+ gl.noLabelsProvided = true
+ }
+
+ // Segregate user provided data into appropriate vars
+ parseData(ser) {
+ let w = this.w
+ let cnf = w.config
+ let gl = w.globals
+ this.excludeCollapsedSeriesInYAxis()
+
+ // If we detected string in X prop of series, we fallback to category x-axis
+ this.fallbackToCategory = false
+
+ this.ctx.core.resetGlobals()
+ this.ctx.core.isMultipleY()
+
+ if (gl.axisCharts) {
+ // axisCharts includes line / area / column / scatter
+ this.parseDataAxisCharts(ser)
+ this.coreUtils.getLargestSeries()
+ } else {
+ // non-axis charts are pie / donut
+ this.parseDataNonAxisCharts(ser)
+ }
+
+ // set Null values to 0 in all series when user hides/shows some series
+ if (cnf.chart.stacked) {
+ const series = new Series(this.ctx)
+ gl.series = series.setNullSeriesToZeroValues(gl.series)
+ }
+
+ this.coreUtils.getSeriesTotals()
+ if (gl.axisCharts) {
+ gl.stackedSeriesTotals = this.coreUtils.getStackedSeriesTotals()
+ gl.stackedSeriesTotalsByGroups =
+ this.coreUtils.getStackedSeriesTotalsByGroups()
+ }
+
+ this.coreUtils.getPercentSeries()
+
+ if (
+ !gl.dataFormatXNumeric &&
+ (!gl.isXNumeric ||
+ (cnf.xaxis.type === 'numeric' &&
+ cnf.labels.length === 0 &&
+ cnf.xaxis.categories.length === 0))
+ ) {
+ // x-axis labels couldn't be detected; hence try searching every option in config
+ this.handleExternalLabelsData(ser)
+ }
+
+ // check for multiline xaxis
+ const catLabels = this.coreUtils.getCategoryLabels(gl.labels)
+ for (let l = 0; l < catLabels.length; l++) {
+ if (Array.isArray(catLabels[l])) {
+ gl.isMultiLineX = true
+ break
+ }
+ }
+ }
+
+ excludeCollapsedSeriesInYAxis() {
+ const w = this.w
+ // Post revision 3.46.0 there is no longer a strict one-to-one
+ // correspondence between series and Y axes.
+ // An axis can be ignored only while all series referenced by it
+ // are collapsed.
+ let yAxisIndexes = []
+ w.globals.seriesYAxisMap.forEach((yAxisArr, yi) => {
+ let collapsedCount = 0
+ yAxisArr.forEach((seriesIndex) => {
+ if (w.globals.collapsedSeriesIndices.indexOf(seriesIndex) !== -1) {
+ collapsedCount++
+ }
+ })
+ // It's possible to have a yaxis that doesn't reference any series yet,
+ // eg, because there are no series' yet, so don't list it as ignored
+ // prematurely.
+ if (collapsedCount > 0 && collapsedCount == yAxisArr.length) {
+ yAxisIndexes.push(yi)
+ }
+ })
+ w.globals.ignoreYAxisIndexes = yAxisIndexes.map((x) => x)
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/DataLabels.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/DataLabels.js
new file mode 100644
index 0000000..673c668
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/DataLabels.js
@@ -0,0 +1,411 @@
+import Scatter from './../charts/Scatter'
+import Graphics from './Graphics'
+import Filters from './Filters'
+
+/**
+ * ApexCharts DataLabels Class for drawing dataLabels on Axes based Charts.
+ *
+ * @module DataLabels
+ **/
+
+class DataLabels {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ // When there are many datalabels to be printed, and some of them overlaps each other in the same series, this method will take care of that
+ // Also, when datalabels exceeds the drawable area and get clipped off, we need to adjust and move some pixels to make them visible again
+ dataLabelsCorrection(
+ x,
+ y,
+ val,
+ i,
+ dataPointIndex,
+ alwaysDrawDataLabel,
+ fontSize
+ ) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+ let drawnextLabel = false //
+
+ let textRects = graphics.getTextRects(val, fontSize)
+ let width = textRects.width
+ let height = textRects.height
+
+ if (y < 0) y = 0
+ if (y > w.globals.gridHeight + height) y = w.globals.gridHeight + height / 2
+
+ // first value in series, so push an empty array
+ if (typeof w.globals.dataLabelsRects[i] === 'undefined')
+ w.globals.dataLabelsRects[i] = []
+
+ // then start pushing actual rects in that sub-array
+ w.globals.dataLabelsRects[i].push({ x, y, width, height })
+
+ let len = w.globals.dataLabelsRects[i].length - 2
+ let lastDrawnIndex =
+ typeof w.globals.lastDrawnDataLabelsIndexes[i] !== 'undefined'
+ ? w.globals.lastDrawnDataLabelsIndexes[i][
+ w.globals.lastDrawnDataLabelsIndexes[i].length - 1
+ ]
+ : 0
+
+ if (typeof w.globals.dataLabelsRects[i][len] !== 'undefined') {
+ let lastDataLabelRect = w.globals.dataLabelsRects[i][lastDrawnIndex]
+ if (
+ // next label forward and x not intersecting
+ x > lastDataLabelRect.x + lastDataLabelRect.width ||
+ y > lastDataLabelRect.y + lastDataLabelRect.height ||
+ y + height < lastDataLabelRect.y ||
+ x + width < lastDataLabelRect.x // next label is going to be drawn backwards
+ ) {
+ // the 2 indexes don't override, so OK to draw next label
+ drawnextLabel = true
+ }
+ }
+
+ if (dataPointIndex === 0 || alwaysDrawDataLabel) {
+ drawnextLabel = true
+ }
+
+ return {
+ x,
+ y,
+ textRects,
+ drawnextLabel,
+ }
+ }
+
+ drawDataLabel({ type, pos, i, j, isRangeStart, strokeWidth = 2 }) {
+ // this method handles line, area, bubble, scatter charts as those charts contains markers/points which have pre-defined x/y positions
+ // all other charts like radar / bars / heatmaps will define their own drawDataLabel routine
+ let w = this.w
+
+ const graphics = new Graphics(this.ctx)
+
+ let dataLabelsConfig = w.config.dataLabels
+
+ let x = 0
+ let y = 0
+
+ let dataPointIndex = j
+
+ let elDataLabelsWrap = null
+
+ const seriesCollapsed = w.globals.collapsedSeriesIndices.indexOf(i) !== -1
+
+ if (seriesCollapsed || !dataLabelsConfig.enabled || !Array.isArray(pos.x)) {
+ return elDataLabelsWrap
+ }
+
+ elDataLabelsWrap = graphics.group({
+ class: 'apexcharts-data-labels',
+ })
+
+ for (let q = 0; q < pos.x.length; q++) {
+ x = pos.x[q] + dataLabelsConfig.offsetX
+ y = pos.y[q] + dataLabelsConfig.offsetY + strokeWidth
+
+ if (!isNaN(x)) {
+ // a small hack as we have 2 points for the first val to connect it
+ if (j === 1 && q === 0) dataPointIndex = 0
+ if (j === 1 && q === 1) dataPointIndex = 1
+
+ let val = w.globals.series[i][dataPointIndex]
+
+ if (type === 'rangeArea') {
+ if (isRangeStart) {
+ val = w.globals.seriesRangeStart[i][dataPointIndex]
+ } else {
+ val = w.globals.seriesRangeEnd[i][dataPointIndex]
+ }
+ }
+
+ let text = ''
+
+ const getText = (v) => {
+ return w.config.dataLabels.formatter(v, {
+ ctx: this.ctx,
+ seriesIndex: i,
+ dataPointIndex,
+ w,
+ })
+ }
+
+ if (w.config.chart.type === 'bubble') {
+ val = w.globals.seriesZ[i][dataPointIndex]
+ text = getText(val)
+
+ y = pos.y[q]
+ const scatter = new Scatter(this.ctx)
+ let centerTextInBubbleCoords = scatter.centerTextInBubble(
+ y,
+ i,
+ dataPointIndex
+ )
+ y = centerTextInBubbleCoords.y
+ } else {
+ if (typeof val !== 'undefined') {
+ text = getText(val)
+ }
+ }
+
+ let textAnchor = w.config.dataLabels.textAnchor
+
+ if (w.globals.isSlopeChart) {
+ if (dataPointIndex === 0) {
+ textAnchor = 'end'
+ } else if (dataPointIndex === w.config.series[i].data.length - 1) {
+ textAnchor = 'start'
+ } else {
+ textAnchor = 'middle'
+ }
+ }
+
+ this.plotDataLabelsText({
+ x,
+ y,
+ text,
+ i,
+ j: dataPointIndex,
+ parent: elDataLabelsWrap,
+ offsetCorrection: true,
+ dataLabelsConfig: w.config.dataLabels,
+ textAnchor,
+ })
+ }
+ }
+
+ return elDataLabelsWrap
+ }
+
+ plotDataLabelsText(opts) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+ let {
+ x,
+ y,
+ i,
+ j,
+ text,
+ textAnchor,
+ fontSize,
+ parent,
+ dataLabelsConfig,
+ color,
+ alwaysDrawDataLabel,
+ offsetCorrection,
+ className,
+ } = opts
+
+ let dataLabelText = null
+ if (Array.isArray(w.config.dataLabels.enabledOnSeries)) {
+ if (w.config.dataLabels.enabledOnSeries.indexOf(i) < 0) {
+ return dataLabelText
+ }
+ }
+
+ let correctedLabels = {
+ x,
+ y,
+ drawnextLabel: true,
+ textRects: null,
+ }
+
+ if (offsetCorrection) {
+ correctedLabels = this.dataLabelsCorrection(
+ x,
+ y,
+ text,
+ i,
+ j,
+ alwaysDrawDataLabel,
+ parseInt(dataLabelsConfig.style.fontSize, 10)
+ )
+ }
+
+ // when zoomed, we don't need to correct labels offsets,
+ // but if normally, labels get cropped, correct them
+ if (!w.globals.zoomed) {
+ x = correctedLabels.x
+ y = correctedLabels.y
+ }
+
+ if (correctedLabels.textRects) {
+ // fixes #2264
+ if (
+ x < -20 - correctedLabels.textRects.width ||
+ x > w.globals.gridWidth + correctedLabels.textRects.width + 30
+ ) {
+ // datalabels fall outside drawing area, so draw a blank label
+ text = ''
+ }
+ }
+
+ let dataLabelColor = w.globals.dataLabels.style.colors[i]
+ if (
+ ((w.config.chart.type === 'bar' || w.config.chart.type === 'rangeBar') &&
+ w.config.plotOptions.bar.distributed) ||
+ w.config.dataLabels.distributed
+ ) {
+ dataLabelColor = w.globals.dataLabels.style.colors[j]
+ }
+ if (typeof dataLabelColor === 'function') {
+ dataLabelColor = dataLabelColor({
+ series: w.globals.series,
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ })
+ }
+ if (color) {
+ dataLabelColor = color
+ }
+
+ let offX = dataLabelsConfig.offsetX
+ let offY = dataLabelsConfig.offsetY
+
+ if (w.config.chart.type === 'bar' || w.config.chart.type === 'rangeBar') {
+ // for certain chart types, we handle offsets while calculating datalabels pos
+ // why? because bars/column may have negative values and based on that
+ // offsets becomes reversed
+ offX = 0
+ offY = 0
+ }
+
+ if (w.globals.isSlopeChart) {
+ if (j !== 0) {
+ offX = dataLabelsConfig.offsetX * -2 + 5
+ }
+ if (j !== 0 && j !== w.config.series[i].data.length - 1) {
+ offX = 0
+ }
+ }
+
+ if (correctedLabels.drawnextLabel) {
+ dataLabelText = graphics.drawText({
+ width: 100,
+ height: parseInt(dataLabelsConfig.style.fontSize, 10),
+ x: x + offX,
+ y: y + offY,
+ foreColor: dataLabelColor,
+ textAnchor: textAnchor || dataLabelsConfig.textAnchor,
+ text,
+ fontSize: fontSize || dataLabelsConfig.style.fontSize,
+ fontFamily: dataLabelsConfig.style.fontFamily,
+ fontWeight: dataLabelsConfig.style.fontWeight || 'normal',
+ })
+
+ dataLabelText.attr({
+ class: className || 'apexcharts-datalabel',
+ cx: x,
+ cy: y,
+ })
+
+ if (dataLabelsConfig.dropShadow.enabled) {
+ const textShadow = dataLabelsConfig.dropShadow
+ const filters = new Filters(this.ctx)
+ filters.dropShadow(dataLabelText, textShadow)
+ }
+
+ parent.add(dataLabelText)
+
+ if (typeof w.globals.lastDrawnDataLabelsIndexes[i] === 'undefined') {
+ w.globals.lastDrawnDataLabelsIndexes[i] = []
+ }
+
+ w.globals.lastDrawnDataLabelsIndexes[i].push(j)
+ }
+
+ return dataLabelText
+ }
+
+ addBackgroundToDataLabel(el, coords) {
+ const w = this.w
+
+ const bCnf = w.config.dataLabels.background
+
+ const paddingH = bCnf.padding
+ const paddingV = bCnf.padding / 2
+
+ const width = coords.width
+ const height = coords.height
+ const graphics = new Graphics(this.ctx)
+ const elRect = graphics.drawRect(
+ coords.x - paddingH,
+ coords.y - paddingV / 2,
+ width + paddingH * 2,
+ height + paddingV,
+ bCnf.borderRadius,
+ w.config.chart.background === 'transparent' || !w.config.chart.background
+ ? '#fff'
+ : w.config.chart.background,
+ bCnf.opacity,
+ bCnf.borderWidth,
+ bCnf.borderColor
+ )
+
+ if (bCnf.dropShadow.enabled) {
+ const filters = new Filters(this.ctx)
+ filters.dropShadow(elRect, bCnf.dropShadow)
+ }
+
+ return elRect
+ }
+
+ dataLabelsBackground() {
+ const w = this.w
+
+ if (w.config.chart.type === 'bubble') return
+
+ const elDataLabels = w.globals.dom.baseEl.querySelectorAll(
+ '.apexcharts-datalabels text'
+ )
+
+ for (let i = 0; i < elDataLabels.length; i++) {
+ const el = elDataLabels[i]
+ const coords = el.getBBox()
+ let elRect = null
+
+ if (coords.width && coords.height) {
+ elRect = this.addBackgroundToDataLabel(el, coords)
+ }
+ if (elRect) {
+ el.parentNode.insertBefore(elRect.node, el)
+ const background = el.getAttribute('fill')
+
+ const shouldAnim =
+ w.config.chart.animations.enabled &&
+ !w.globals.resized &&
+ !w.globals.dataChanged
+
+ if (shouldAnim) {
+ elRect.animate().attr({ fill: background })
+ } else {
+ elRect.attr({ fill: background })
+ }
+ el.setAttribute('fill', w.config.dataLabels.background.foreColor)
+ }
+ }
+ }
+
+ bringForward() {
+ const w = this.w
+ const elDataLabelsNodes = w.globals.dom.baseEl.querySelectorAll(
+ '.apexcharts-datalabels'
+ )
+
+ const elSeries = w.globals.dom.baseEl.querySelector(
+ '.apexcharts-plot-series:last-child'
+ )
+
+ for (let i = 0; i < elDataLabelsNodes.length; i++) {
+ if (elSeries) {
+ elSeries.insertBefore(elDataLabelsNodes[i], elSeries.nextSibling)
+ }
+ }
+ }
+}
+
+export default DataLabels
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Events.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Events.js
new file mode 100644
index 0000000..eb742be
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Events.js
@@ -0,0 +1,120 @@
+import Utils from '../utils/Utils'
+
+export default class Events {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.documentEvent = Utils.bind(this.documentEvent, this)
+ }
+
+ addEventListener(name, handler) {
+ const w = this.w
+
+ if (w.globals.events.hasOwnProperty(name)) {
+ w.globals.events[name].push(handler)
+ } else {
+ w.globals.events[name] = [handler]
+ }
+ }
+
+ removeEventListener(name, handler) {
+ const w = this.w
+ if (!w.globals.events.hasOwnProperty(name)) {
+ return
+ }
+
+ let index = w.globals.events[name].indexOf(handler)
+ if (index !== -1) {
+ w.globals.events[name].splice(index, 1)
+ }
+ }
+
+ fireEvent(name, args) {
+ const w = this.w
+
+ if (!w.globals.events.hasOwnProperty(name)) {
+ return
+ }
+
+ if (!args || !args.length) {
+ args = []
+ }
+
+ let evs = w.globals.events[name]
+ let l = evs.length
+
+ for (let i = 0; i < l; i++) {
+ evs[i].apply(null, args)
+ }
+ }
+
+ setupEventHandlers() {
+ const w = this.w
+ const me = this.ctx
+
+ let clickableArea = w.globals.dom.baseEl.querySelector(w.globals.chartClass)
+
+ this.ctx.eventList.forEach((event) => {
+ clickableArea.addEventListener(
+ event,
+ (e) => {
+ const opts = Object.assign({}, w, {
+ seriesIndex: w.globals.axisCharts
+ ? w.globals.capturedSeriesIndex
+ : 0,
+ dataPointIndex: w.globals.capturedDataPointIndex,
+ })
+
+ if (e.type === 'mousemove' || e.type === 'touchmove') {
+ if (typeof w.config.chart.events.mouseMove === 'function') {
+ w.config.chart.events.mouseMove(e, me, opts)
+ }
+ } else if (e.type === 'mouseleave' || e.type === 'touchleave') {
+ if (typeof w.config.chart.events.mouseLeave === 'function') {
+ w.config.chart.events.mouseLeave(e, me, opts)
+ }
+ } else if (
+ (e.type === 'mouseup' && e.which === 1) ||
+ e.type === 'touchend'
+ ) {
+ if (typeof w.config.chart.events.click === 'function') {
+ w.config.chart.events.click(e, me, opts)
+ }
+ me.ctx.events.fireEvent('click', [e, me, opts])
+ }
+ },
+ { capture: false, passive: true }
+ )
+ })
+
+ this.ctx.eventList.forEach((event) => {
+ w.globals.dom.baseEl.addEventListener(event, this.documentEvent, {
+ passive: true,
+ })
+ })
+
+ this.ctx.core.setupBrushHandler()
+ }
+
+ documentEvent(e) {
+ const w = this.w
+ const target = e.target.className
+
+ if (e.type === 'click') {
+ let elMenu = w.globals.dom.baseEl.querySelector('.apexcharts-menu')
+ if (
+ elMenu &&
+ elMenu.classList.contains('apexcharts-menu-open') &&
+ target !== 'apexcharts-menu-icon'
+ ) {
+ elMenu.classList.remove('apexcharts-menu-open')
+ }
+ }
+
+ w.globals.clientX =
+ e.type === 'touchmove' ? e.touches[0].clientX : e.clientX
+ w.globals.clientY =
+ e.type === 'touchmove' ? e.touches[0].clientY : e.clientY
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Exports.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Exports.js
new file mode 100644
index 0000000..80d165a
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Exports.js
@@ -0,0 +1,497 @@
+import Data from '../modules/Data'
+import AxesUtils from '../modules/axes/AxesUtils'
+import Series from '../modules/Series'
+import Utils from '../utils/Utils'
+
+class Exports {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ scaleSvgNode(svg, scale) {
+ // get current both width and height of the svg
+ let svgWidth = parseFloat(svg.getAttributeNS(null, 'width'))
+ let svgHeight = parseFloat(svg.getAttributeNS(null, 'height'))
+ // set new width and height based on the scale
+ svg.setAttributeNS(null, 'width', svgWidth * scale)
+ svg.setAttributeNS(null, 'height', svgHeight * scale)
+ svg.setAttributeNS(null, 'viewBox', '0 0 ' + svgWidth + ' ' + svgHeight)
+ }
+
+ getSvgString() {
+ return new Promise((resolve) => {
+ const w = this.w
+ const width = w.config.chart.toolbar.export.width
+ let scale =
+ w.config.chart.toolbar.export.scale || width / w.globals.svgWidth
+
+ if (!scale) {
+ scale = 1 // if no scale is specified, don't scale...
+ }
+ let svgString = this.w.globals.dom.Paper.svg()
+
+ // clone the svg node so it remains intact in the UI
+ const svgNode = this.w.globals.dom.Paper.node.cloneNode(true)
+
+ // in case the scale is different than 1, the svg needs to be rescaled
+
+ if (scale !== 1) {
+ // scale the image
+ this.scaleSvgNode(svgNode, scale)
+ }
+ // Convert image URLs to base64
+ this.convertImagesToBase64(svgNode).then(() => {
+ svgString = new XMLSerializer().serializeToString(svgNode)
+ resolve(svgString.replace(/ /g, ' '))
+ })
+ })
+ }
+
+ convertImagesToBase64(svgNode) {
+ const images = svgNode.getElementsByTagName('image')
+ const promises = Array.from(images).map((img) => {
+ const href = img.getAttributeNS('http://www.w3.org/1999/xlink', 'href')
+ if (href && !href.startsWith('data:')) {
+ return this.getBase64FromUrl(href)
+ .then((base64) => {
+ img.setAttributeNS('http://www.w3.org/1999/xlink', 'href', base64)
+ })
+ .catch((error) => {
+ console.error('Error converting image to base64:', error)
+ })
+ }
+ return Promise.resolve()
+ })
+ return Promise.all(promises)
+ }
+
+ getBase64FromUrl(url) {
+ return new Promise((resolve, reject) => {
+ const img = new Image()
+ img.crossOrigin = 'Anonymous'
+ img.onload = () => {
+ const canvas = document.createElement('canvas')
+ canvas.width = img.width
+ canvas.height = img.height
+ const ctx = canvas.getContext('2d')
+ ctx.drawImage(img, 0, 0)
+ resolve(canvas.toDataURL())
+ }
+ img.onerror = reject
+ img.src = url
+ })
+ }
+
+ cleanup() {
+ const w = this.w
+
+ // hide some elements to avoid printing them on exported svg
+ const xcrosshairs = w.globals.dom.baseEl.getElementsByClassName(
+ 'apexcharts-xcrosshairs'
+ )
+ const ycrosshairs = w.globals.dom.baseEl.getElementsByClassName(
+ 'apexcharts-ycrosshairs'
+ )
+ const zoomSelectionRects = w.globals.dom.baseEl.querySelectorAll(
+ '.apexcharts-zoom-rect, .apexcharts-selection-rect'
+ )
+ Array.prototype.forEach.call(zoomSelectionRects, (z) => {
+ z.setAttribute('width', 0)
+ })
+ if (xcrosshairs && xcrosshairs[0]) {
+ xcrosshairs[0].setAttribute('x', -500)
+ xcrosshairs[0].setAttribute('x1', -500)
+ xcrosshairs[0].setAttribute('x2', -500)
+ }
+ if (ycrosshairs && ycrosshairs[0]) {
+ ycrosshairs[0].setAttribute('y', -100)
+ ycrosshairs[0].setAttribute('y1', -100)
+ ycrosshairs[0].setAttribute('y2', -100)
+ }
+ }
+
+ svgUrl() {
+ return new Promise((resolve) => {
+ this.cleanup()
+ this.getSvgString().then((svgData) => {
+ const svgBlob = new Blob([svgData], {
+ type: 'image/svg+xml;charset=utf-8',
+ })
+ resolve(URL.createObjectURL(svgBlob))
+ })
+ })
+ }
+
+ dataURI(options) {
+ return new Promise((resolve) => {
+ const w = this.w
+
+ const scale = options
+ ? options.scale || options.width / w.globals.svgWidth
+ : 1
+
+ this.cleanup()
+ const canvas = document.createElement('canvas')
+ canvas.width = w.globals.svgWidth * scale
+ canvas.height = parseInt(w.globals.dom.elWrap.style.height, 10) * scale // because of resizeNonAxisCharts
+
+ const canvasBg =
+ w.config.chart.background === 'transparent' ||
+ !w.config.chart.background
+ ? '#fff'
+ : w.config.chart.background
+
+ let ctx = canvas.getContext('2d')
+ ctx.fillStyle = canvasBg
+ ctx.fillRect(0, 0, canvas.width * scale, canvas.height * scale)
+
+ this.getSvgString().then((svgData) => {
+ const svgUrl = 'data:image/svg+xml,' + encodeURIComponent(svgData)
+ let img = new Image()
+ img.crossOrigin = 'anonymous'
+
+ img.onload = () => {
+ ctx.drawImage(img, 0, 0)
+
+ if (canvas.msToBlob) {
+ // Microsoft Edge can't navigate to data urls, so we return the blob instead
+ let blob = canvas.msToBlob()
+ resolve({ blob })
+ } else {
+ let imgURI = canvas.toDataURL('image/png')
+ resolve({ imgURI })
+ }
+ }
+
+ img.src = svgUrl
+ })
+ })
+ }
+
+ exportToSVG() {
+ this.svgUrl().then((url) => {
+ this.triggerDownload(
+ url,
+ this.w.config.chart.toolbar.export.svg.filename,
+ '.svg'
+ )
+ })
+ }
+
+ exportToPng() {
+ const scale = this.w.config.chart.toolbar.export.scale
+ const width = this.w.config.chart.toolbar.export.width
+ const option = scale
+ ? { scale: scale }
+ : width
+ ? { width: width }
+ : undefined
+ this.dataURI(option).then(({ imgURI, blob }) => {
+ if (blob) {
+ navigator.msSaveOrOpenBlob(blob, this.w.globals.chartID + '.png')
+ } else {
+ this.triggerDownload(
+ imgURI,
+ this.w.config.chart.toolbar.export.png.filename,
+ '.png'
+ )
+ }
+ })
+ }
+
+ exportToCSV({
+ series,
+ fileName,
+ columnDelimiter = ',',
+ lineDelimiter = '\n',
+ }) {
+ const w = this.w
+
+ if (!series) series = w.config.series
+
+ let columns = []
+ let rows = []
+ let result = ''
+ let universalBOM = '\uFEFF'
+ let gSeries = w.globals.series.map((s, i) => {
+ return w.globals.collapsedSeriesIndices.indexOf(i) === -1 ? s : []
+ })
+
+ const getFormattedCategory = (cat) => {
+ if (
+ typeof w.config.chart.toolbar.export.csv.categoryFormatter ===
+ 'function'
+ ) {
+ return w.config.chart.toolbar.export.csv.categoryFormatter(cat)
+ }
+
+ if (w.config.xaxis.type === 'datetime' && String(cat).length >= 10) {
+ return new Date(cat).toDateString()
+ }
+ return Utils.isNumber(cat) ? cat : cat.split(columnDelimiter).join('')
+ }
+
+ const getFormattedValue = (value) => {
+ return typeof w.config.chart.toolbar.export.csv.valueFormatter ===
+ 'function'
+ ? w.config.chart.toolbar.export.csv.valueFormatter(value)
+ : value
+ }
+
+ const seriesMaxDataLength = Math.max(
+ ...series.map((s) => {
+ return s.data ? s.data.length : 0
+ })
+ )
+ const dataFormat = new Data(this.ctx)
+
+ const axesUtils = new AxesUtils(this.ctx)
+ const getCat = (i) => {
+ let cat = ''
+
+ // pie / donut/ radial
+ if (!w.globals.axisCharts) {
+ cat = w.config.labels[i]
+ } else {
+ // xy charts
+
+ // non datetime
+ if (
+ w.config.xaxis.type === 'category' ||
+ w.config.xaxis.convertedCatToNumeric
+ ) {
+ if (w.globals.isBarHorizontal) {
+ let lbFormatter = w.globals.yLabelFormatters[0]
+ let sr = new Series(this.ctx)
+ let activeSeries = sr.getActiveConfigSeriesIndex()
+
+ cat = lbFormatter(w.globals.labels[i], {
+ seriesIndex: activeSeries,
+ dataPointIndex: i,
+ w,
+ })
+ } else {
+ cat = axesUtils.getLabel(
+ w.globals.labels,
+ w.globals.timescaleLabels,
+ 0,
+ i
+ ).text
+ }
+ }
+
+ // datetime, but labels specified in categories or labels
+ if (w.config.xaxis.type === 'datetime') {
+ if (w.config.xaxis.categories.length) {
+ cat = w.config.xaxis.categories[i]
+ } else if (w.config.labels.length) {
+ cat = w.config.labels[i]
+ }
+ }
+ }
+
+ // let the caller know the current category is null. this can happen for example
+ // when dealing with line charts having inconsistent time series data
+ if (cat === null) return 'nullvalue'
+
+ if (Array.isArray(cat)) {
+ cat = cat.join(' ')
+ }
+
+ return Utils.isNumber(cat) ? cat : cat.split(columnDelimiter).join('')
+ }
+
+ // Fix https://github.com/apexcharts/apexcharts.js/issues/3365
+ const getEmptyDataForCsvColumn = () => {
+ return [...Array(seriesMaxDataLength)].map(() => '')
+ }
+
+ const handleAxisRowsColumns = (s, sI) => {
+ if (columns.length && sI === 0) {
+ // It's the first series. Go ahead and create the first row with header information.
+ rows.push(columns.join(columnDelimiter))
+ }
+
+ if (s.data) {
+ // Use the data we have, or generate a properly sized empty array with empty data if some data is missing.
+ s.data = (s.data.length && s.data) || getEmptyDataForCsvColumn()
+ for (let i = 0; i < s.data.length; i++) {
+ // Reset the columns array so that we can start building columns for this row.
+ columns = []
+
+ let cat = getCat(i)
+
+ // current category is null, let's move on to the next one
+ if (cat === 'nullvalue') continue
+
+ if (!cat) {
+ if (dataFormat.isFormatXY()) {
+ cat = series[sI].data[i].x
+ } else if (dataFormat.isFormat2DArray()) {
+ cat = series[sI].data[i] ? series[sI].data[i][0] : ''
+ }
+ }
+
+ if (sI === 0) {
+ // It's the first series. Also handle the category.
+ columns.push(getFormattedCategory(cat))
+
+ for (let ci = 0; ci < w.globals.series.length; ci++) {
+ const value = dataFormat.isFormatXY()
+ ? series[ci].data[i]?.y
+ : gSeries[ci][i]
+ columns.push(getFormattedValue(value))
+ }
+ }
+
+ if (
+ w.config.chart.type === 'candlestick' ||
+ (s.type && s.type === 'candlestick')
+ ) {
+ columns.pop()
+ columns.push(w.globals.seriesCandleO[sI][i])
+ columns.push(w.globals.seriesCandleH[sI][i])
+ columns.push(w.globals.seriesCandleL[sI][i])
+ columns.push(w.globals.seriesCandleC[sI][i])
+ }
+
+ if (
+ w.config.chart.type === 'boxPlot' ||
+ (s.type && s.type === 'boxPlot')
+ ) {
+ columns.pop()
+ columns.push(w.globals.seriesCandleO[sI][i])
+ columns.push(w.globals.seriesCandleH[sI][i])
+ columns.push(w.globals.seriesCandleM[sI][i])
+ columns.push(w.globals.seriesCandleL[sI][i])
+ columns.push(w.globals.seriesCandleC[sI][i])
+ }
+
+ if (w.config.chart.type === 'rangeBar') {
+ columns.pop()
+ columns.push(w.globals.seriesRangeStart[sI][i])
+ columns.push(w.globals.seriesRangeEnd[sI][i])
+ }
+
+ if (columns.length) {
+ rows.push(columns.join(columnDelimiter))
+ }
+ }
+ }
+ }
+
+ const handleUnequalXValues = () => {
+ const categories = new Set()
+ const data = {}
+
+ series.forEach((s, sI) => {
+ s?.data.forEach((dataItem) => {
+ let cat, value
+ if (dataFormat.isFormatXY()) {
+ cat = dataItem.x
+ value = dataItem.y
+ } else if (dataFormat.isFormat2DArray()) {
+ cat = dataItem[0]
+ value = dataItem[1]
+ } else {
+ return
+ }
+ if (!data[cat]) {
+ data[cat] = Array(series.length).fill('')
+ }
+ data[cat][sI] = getFormattedValue(value)
+ categories.add(cat)
+ })
+ })
+
+ if (columns.length) {
+ rows.push(columns.join(columnDelimiter))
+ }
+
+ Array.from(categories)
+ .sort()
+ .forEach((cat) => {
+ rows.push([
+ getFormattedCategory(cat),
+ data[cat].join(columnDelimiter),
+ ])
+ })
+ }
+
+ columns.push(w.config.chart.toolbar.export.csv.headerCategory)
+
+ if (w.config.chart.type === 'boxPlot') {
+ columns.push('minimum')
+ columns.push('q1')
+ columns.push('median')
+ columns.push('q3')
+ columns.push('maximum')
+ } else if (w.config.chart.type === 'candlestick') {
+ columns.push('open')
+ columns.push('high')
+ columns.push('low')
+ columns.push('close')
+ } else if (w.config.chart.type === 'rangeBar') {
+ columns.push('minimum')
+ columns.push('maximum')
+ } else {
+ series.map((s, sI) => {
+ const sname = (s.name ? s.name : `series-${sI}`) + ''
+ if (w.globals.axisCharts) {
+ columns.push(
+ sname.split(columnDelimiter).join('')
+ ? sname.split(columnDelimiter).join('')
+ : `series-${sI}`
+ )
+ }
+ })
+ }
+
+ if (!w.globals.axisCharts) {
+ columns.push(w.config.chart.toolbar.export.csv.headerValue)
+ rows.push(columns.join(columnDelimiter))
+ }
+
+ if (
+ !w.globals.allSeriesHasEqualX &&
+ w.globals.axisCharts &&
+ !w.config.xaxis.categories.length &&
+ !w.config.labels.length
+ ) {
+ handleUnequalXValues()
+ } else {
+ series.map((s, sI) => {
+ if (w.globals.axisCharts) {
+ handleAxisRowsColumns(s, sI)
+ } else {
+ columns = []
+
+ columns.push(getFormattedCategory(w.globals.labels[sI]))
+ columns.push(getFormattedValue(gSeries[sI]))
+ rows.push(columns.join(columnDelimiter))
+ }
+ })
+ }
+
+ result += rows.join(lineDelimiter)
+
+ this.triggerDownload(
+ 'data:text/csv; charset=utf-8,' +
+ encodeURIComponent(universalBOM + result),
+ fileName ? fileName : w.config.chart.toolbar.export.csv.filename,
+ '.csv'
+ )
+ }
+
+ triggerDownload(href, filename, ext) {
+ const downloadLink = document.createElement('a')
+ downloadLink.href = href
+ downloadLink.download = (filename ? filename : this.w.globals.chartID) + ext
+ document.body.appendChild(downloadLink)
+ downloadLink.click()
+ document.body.removeChild(downloadLink)
+ }
+}
+
+export default Exports
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Fill.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Fill.js
new file mode 100644
index 0000000..4348eb8
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Fill.js
@@ -0,0 +1,420 @@
+import Graphics from './Graphics'
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts Fill Class for setting fill options of the paths.
+ *
+ * @module Fill
+ **/
+
+class Fill {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.opts = null
+ this.seriesIndex = 0
+ this.patternIDs = []
+ }
+
+ clippedImgArea(params) {
+ let w = this.w
+ let cnf = w.config
+
+ let svgW = parseInt(w.globals.gridWidth, 10)
+ let svgH = parseInt(w.globals.gridHeight, 10)
+
+ let size = svgW > svgH ? svgW : svgH
+
+ let fillImg = params.image
+
+ let imgWidth = 0
+ let imgHeight = 0
+ if (
+ typeof params.width === 'undefined' &&
+ typeof params.height === 'undefined'
+ ) {
+ if (
+ cnf.fill.image.width !== undefined &&
+ cnf.fill.image.height !== undefined
+ ) {
+ imgWidth = cnf.fill.image.width + 1
+ imgHeight = cnf.fill.image.height
+ } else {
+ imgWidth = size + 1
+ imgHeight = size
+ }
+ } else {
+ imgWidth = params.width
+ imgHeight = params.height
+ }
+
+ let elPattern = document.createElementNS(w.globals.SVGNS, 'pattern')
+
+ Graphics.setAttrs(elPattern, {
+ id: params.patternID,
+ patternUnits: params.patternUnits
+ ? params.patternUnits
+ : 'userSpaceOnUse',
+ width: imgWidth + 'px',
+ height: imgHeight + 'px',
+ })
+
+ let elImage = document.createElementNS(w.globals.SVGNS, 'image')
+ elPattern.appendChild(elImage)
+
+ elImage.setAttributeNS(window.SVG.xlink, 'href', fillImg)
+
+ Graphics.setAttrs(elImage, {
+ x: 0,
+ y: 0,
+ preserveAspectRatio: 'none',
+ width: imgWidth + 'px',
+ height: imgHeight + 'px',
+ })
+
+ elImage.style.opacity = params.opacity
+
+ w.globals.dom.elDefs.node.appendChild(elPattern)
+ }
+
+ getSeriesIndex(opts) {
+ const w = this.w
+ const cType = w.config.chart.type
+
+ if (
+ ((cType === 'bar' || cType === 'rangeBar') &&
+ w.config.plotOptions.bar.distributed) ||
+ cType === 'heatmap' ||
+ cType === 'treemap'
+ ) {
+ this.seriesIndex = opts.seriesNumber
+ } else {
+ this.seriesIndex = opts.seriesNumber % w.globals.series.length
+ }
+
+ return this.seriesIndex
+ }
+
+ fillPath(opts) {
+ let w = this.w
+ this.opts = opts
+
+ let cnf = this.w.config
+ let pathFill
+
+ let patternFill, gradientFill
+
+ this.seriesIndex = this.getSeriesIndex(opts)
+
+ let fillColors = this.getFillColors()
+ let fillColor = fillColors[this.seriesIndex]
+
+ //override fillcolor if user inputted color with data
+ if (w.globals.seriesColors[this.seriesIndex] !== undefined) {
+ fillColor = w.globals.seriesColors[this.seriesIndex]
+ }
+
+ if (typeof fillColor === 'function') {
+ fillColor = fillColor({
+ seriesIndex: this.seriesIndex,
+ dataPointIndex: opts.dataPointIndex,
+ value: opts.value,
+ w,
+ })
+ }
+ let fillType = opts.fillType
+ ? opts.fillType
+ : this.getFillType(this.seriesIndex)
+ let fillOpacity = Array.isArray(cnf.fill.opacity)
+ ? cnf.fill.opacity[this.seriesIndex]
+ : cnf.fill.opacity
+
+ if (opts.color) {
+ fillColor = opts.color
+ }
+
+ // in case a color is undefined, fallback to white color to prevent runtime error
+ if (!fillColor) {
+ fillColor = '#fff'
+ console.warn('undefined color - ApexCharts')
+ }
+
+ let defaultColor = fillColor
+
+ if (fillColor.indexOf('rgb') === -1) {
+ if (fillColor.length < 9) {
+ // if the hex contains alpha and is of 9 digit, skip the opacity
+ defaultColor = Utils.hexToRgba(fillColor, fillOpacity)
+ }
+ } else {
+ if (fillColor.indexOf('rgba') > -1) {
+ fillOpacity = Utils.getOpacityFromRGBA(fillColor)
+ }
+ }
+ if (opts.opacity) fillOpacity = opts.opacity
+
+ if (fillType === 'pattern') {
+ patternFill = this.handlePatternFill({
+ fillConfig: opts.fillConfig,
+ patternFill,
+ fillColor,
+ fillOpacity,
+ defaultColor,
+ })
+ }
+
+ if (fillType === 'gradient') {
+ gradientFill = this.handleGradientFill({
+ fillConfig: opts.fillConfig,
+ fillColor,
+ fillOpacity,
+ i: this.seriesIndex,
+ })
+ }
+
+ if (fillType === 'image') {
+ let imgSrc = cnf.fill.image.src
+
+ let patternID = opts.patternID ? opts.patternID : ''
+ const patternKey = `pattern${w.globals.cuid}${
+ opts.seriesNumber + 1
+ }${patternID}`
+
+ if (this.patternIDs.indexOf(patternKey) === -1) {
+ this.clippedImgArea({
+ opacity: fillOpacity,
+ image: Array.isArray(imgSrc)
+ ? opts.seriesNumber < imgSrc.length
+ ? imgSrc[opts.seriesNumber]
+ : imgSrc[0]
+ : imgSrc,
+ width: opts.width ? opts.width : undefined,
+ height: opts.height ? opts.height : undefined,
+ patternUnits: opts.patternUnits,
+ patternID: patternKey,
+ })
+
+ this.patternIDs.push(patternKey)
+ }
+
+ pathFill = `url(#${patternKey})`
+ } else if (fillType === 'gradient') {
+ pathFill = gradientFill
+ } else if (fillType === 'pattern') {
+ pathFill = patternFill
+ } else {
+ pathFill = defaultColor
+ }
+
+ // override pattern/gradient if opts.solid is true
+ if (opts.solid) {
+ pathFill = defaultColor
+ }
+
+ return pathFill
+ }
+
+ getFillType(seriesIndex) {
+ const w = this.w
+
+ if (Array.isArray(w.config.fill.type)) {
+ return w.config.fill.type[seriesIndex]
+ } else {
+ return w.config.fill.type
+ }
+ }
+
+ getFillColors() {
+ const w = this.w
+ const cnf = w.config
+ const opts = this.opts
+
+ let fillColors = []
+
+ if (w.globals.comboCharts) {
+ if (w.config.series[this.seriesIndex].type === 'line') {
+ if (Array.isArray(w.globals.stroke.colors)) {
+ fillColors = w.globals.stroke.colors
+ } else {
+ fillColors.push(w.globals.stroke.colors)
+ }
+ } else {
+ if (Array.isArray(w.globals.fill.colors)) {
+ fillColors = w.globals.fill.colors
+ } else {
+ fillColors.push(w.globals.fill.colors)
+ }
+ }
+ } else {
+ if (cnf.chart.type === 'line') {
+ if (Array.isArray(w.globals.stroke.colors)) {
+ fillColors = w.globals.stroke.colors
+ } else {
+ fillColors.push(w.globals.stroke.colors)
+ }
+ } else {
+ if (Array.isArray(w.globals.fill.colors)) {
+ fillColors = w.globals.fill.colors
+ } else {
+ fillColors.push(w.globals.fill.colors)
+ }
+ }
+ }
+
+ // colors passed in arguments
+ if (typeof opts.fillColors !== 'undefined') {
+ fillColors = []
+ if (Array.isArray(opts.fillColors)) {
+ fillColors = opts.fillColors.slice()
+ } else {
+ fillColors.push(opts.fillColors)
+ }
+ }
+
+ return fillColors
+ }
+
+ handlePatternFill({
+ fillConfig,
+ patternFill,
+ fillColor,
+ fillOpacity,
+ defaultColor,
+ }) {
+ let fillCnf = this.w.config.fill
+
+ if (fillConfig) {
+ fillCnf = fillConfig
+ }
+
+ const opts = this.opts
+ let graphics = new Graphics(this.ctx)
+
+ let patternStrokeWidth = Array.isArray(fillCnf.pattern.strokeWidth)
+ ? fillCnf.pattern.strokeWidth[this.seriesIndex]
+ : fillCnf.pattern.strokeWidth
+ let patternLineColor = fillColor
+
+ if (Array.isArray(fillCnf.pattern.style)) {
+ if (typeof fillCnf.pattern.style[opts.seriesNumber] !== 'undefined') {
+ let pf = graphics.drawPattern(
+ fillCnf.pattern.style[opts.seriesNumber],
+ fillCnf.pattern.width,
+ fillCnf.pattern.height,
+ patternLineColor,
+ patternStrokeWidth,
+ fillOpacity
+ )
+ patternFill = pf
+ } else {
+ patternFill = defaultColor
+ }
+ } else {
+ patternFill = graphics.drawPattern(
+ fillCnf.pattern.style,
+ fillCnf.pattern.width,
+ fillCnf.pattern.height,
+ patternLineColor,
+ patternStrokeWidth,
+ fillOpacity
+ )
+ }
+ return patternFill
+ }
+
+ handleGradientFill({ fillColor, fillOpacity, fillConfig, i }) {
+ let fillCnf = this.w.config.fill
+
+ if (fillConfig) {
+ fillCnf = {
+ ...fillCnf,
+ ...fillConfig,
+ }
+ }
+ const opts = this.opts
+ let graphics = new Graphics(this.ctx)
+ let utils = new Utils()
+
+ let type = fillCnf.gradient.type
+ let gradientFrom = fillColor
+ let gradientTo
+ let opacityFrom =
+ fillCnf.gradient.opacityFrom === undefined
+ ? fillOpacity
+ : Array.isArray(fillCnf.gradient.opacityFrom)
+ ? fillCnf.gradient.opacityFrom[i]
+ : fillCnf.gradient.opacityFrom
+
+ if (gradientFrom.indexOf('rgba') > -1) {
+ opacityFrom = Utils.getOpacityFromRGBA(gradientFrom)
+ }
+ let opacityTo =
+ fillCnf.gradient.opacityTo === undefined
+ ? fillOpacity
+ : Array.isArray(fillCnf.gradient.opacityTo)
+ ? fillCnf.gradient.opacityTo[i]
+ : fillCnf.gradient.opacityTo
+
+ if (
+ fillCnf.gradient.gradientToColors === undefined ||
+ fillCnf.gradient.gradientToColors.length === 0
+ ) {
+ if (fillCnf.gradient.shade === 'dark') {
+ gradientTo = utils.shadeColor(
+ parseFloat(fillCnf.gradient.shadeIntensity) * -1,
+ fillColor.indexOf('rgb') > -1 ? Utils.rgb2hex(fillColor) : fillColor
+ )
+ } else {
+ gradientTo = utils.shadeColor(
+ parseFloat(fillCnf.gradient.shadeIntensity),
+ fillColor.indexOf('rgb') > -1 ? Utils.rgb2hex(fillColor) : fillColor
+ )
+ }
+ } else {
+ if (fillCnf.gradient.gradientToColors[opts.seriesNumber]) {
+ const gToColor = fillCnf.gradient.gradientToColors[opts.seriesNumber]
+ gradientTo = gToColor
+ if (gToColor.indexOf('rgba') > -1) {
+ opacityTo = Utils.getOpacityFromRGBA(gToColor)
+ }
+ } else {
+ gradientTo = fillColor
+ }
+ }
+
+ if (fillCnf.gradient.gradientFrom) {
+ gradientFrom = fillCnf.gradient.gradientFrom
+ }
+ if (fillCnf.gradient.gradientTo) {
+ gradientTo = fillCnf.gradient.gradientTo
+ }
+
+ if (fillCnf.gradient.inverseColors) {
+ let t = gradientFrom
+ gradientFrom = gradientTo
+ gradientTo = t
+ }
+
+ if (gradientFrom.indexOf('rgb') > -1) {
+ gradientFrom = Utils.rgb2hex(gradientFrom)
+ }
+ if (gradientTo.indexOf('rgb') > -1) {
+ gradientTo = Utils.rgb2hex(gradientTo)
+ }
+
+ return graphics.drawGradient(
+ type,
+ gradientFrom,
+ gradientTo,
+ opacityFrom,
+ opacityTo,
+ opts.size,
+ fillCnf.gradient.stops,
+ fillCnf.gradient.colorStops,
+ i
+ )
+ }
+}
+
+export default Fill
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Filters.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Filters.js
new file mode 100644
index 0000000..d3b7a55
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Filters.js
@@ -0,0 +1,225 @@
+import Utils from './../utils/Utils'
+
+/**
+ * ApexCharts Filters Class for setting hover/active states on the paths.
+ *
+ * @module Formatters
+ **/
+class Filters {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ // create a re-usable filter which can be appended other filter effects and applied to multiple elements
+ getDefaultFilter(el, i) {
+ const w = this.w
+ el.unfilter(true)
+
+ let filter = new window.SVG.Filter()
+ filter.size('120%', '180%', '-5%', '-40%')
+
+ if (w.config.states.normal.filter !== 'none') {
+ this.applyFilter(
+ el,
+ i,
+ w.config.states.normal.filter.type,
+ w.config.states.normal.filter.value
+ )
+ } else {
+ if (w.config.chart.dropShadow.enabled) {
+ this.dropShadow(el, w.config.chart.dropShadow, i)
+ }
+ }
+ }
+
+ addNormalFilter(el, i) {
+ const w = this.w
+
+ // revert shadow if it was there
+ // but, ignore marker as marker don't have dropshadow yet
+ if (
+ w.config.chart.dropShadow.enabled &&
+ !el.node.classList.contains('apexcharts-marker')
+ ) {
+ this.dropShadow(el, w.config.chart.dropShadow, i)
+ }
+ }
+
+ // appends dropShadow to the filter object which can be chained with other filter effects
+ addLightenFilter(el, i, attrs) {
+ const w = this.w
+ const { intensity } = attrs
+
+ el.unfilter(true)
+
+ let filter = new window.SVG.Filter()
+
+ el.filter((add) => {
+ const shadowAttr = w.config.chart.dropShadow
+ if (shadowAttr.enabled) {
+ filter = this.addShadow(add, i, shadowAttr)
+ } else {
+ filter = add
+ }
+ filter.componentTransfer({
+ rgb: { type: 'linear', slope: 1.5, intercept: intensity },
+ })
+ })
+ el.filterer.node.setAttribute('filterUnits', 'userSpaceOnUse')
+
+ this._scaleFilterSize(el.filterer.node)
+ }
+
+ // appends dropShadow to the filter object which can be chained with other filter effects
+ addDarkenFilter(el, i, attrs) {
+ const w = this.w
+ const { intensity } = attrs
+
+ el.unfilter(true)
+
+ let filter = new window.SVG.Filter()
+
+ el.filter((add) => {
+ const shadowAttr = w.config.chart.dropShadow
+ if (shadowAttr.enabled) {
+ filter = this.addShadow(add, i, shadowAttr)
+ } else {
+ filter = add
+ }
+ filter.componentTransfer({
+ rgb: { type: 'linear', slope: intensity },
+ })
+ })
+ el.filterer.node.setAttribute('filterUnits', 'userSpaceOnUse')
+ this._scaleFilterSize(el.filterer.node)
+ }
+
+ applyFilter(el, i, filter, intensity = 0.5) {
+ switch (filter) {
+ case 'none': {
+ this.addNormalFilter(el, i)
+ break
+ }
+ case 'lighten': {
+ this.addLightenFilter(el, i, {
+ intensity,
+ })
+ break
+ }
+ case 'darken': {
+ this.addDarkenFilter(el, i, {
+ intensity,
+ })
+ break
+ }
+ default:
+ // do nothing
+ break
+ }
+ }
+
+ // appends dropShadow to the filter object which can be chained with other filter effects
+ addShadow(add, i, attrs) {
+ const w = this.w
+ const { blur, top, left, color, opacity } = attrs
+
+ if (w.config.chart.dropShadow.enabledOnSeries?.length > 0) {
+ if (w.config.chart.dropShadow.enabledOnSeries.indexOf(i) === -1) {
+ return add
+ }
+ }
+
+ let shadowBlur = add
+ .flood(Array.isArray(color) ? color[i] : color, opacity)
+ .composite(add.sourceAlpha, 'in')
+ .offset(left, top)
+ .gaussianBlur(blur)
+ .merge(add.source)
+ return add.blend(add.source, shadowBlur)
+ }
+
+ // directly adds dropShadow to the element and returns the same element.
+ // the only way it is different from the addShadow() function is that addShadow is chainable to other filters, while this function discards all filters and add dropShadow
+ dropShadow(el, attrs, i = 0) {
+ let { top, left, blur, color, opacity, noUserSpaceOnUse } = attrs
+ const w = this.w
+
+ el.unfilter(true)
+
+ if (Utils.isMsEdge() && w.config.chart.type === 'radialBar') {
+ // in radialbar charts, dropshadow is clipping actual drawing in IE
+ return el
+ }
+
+ if (w.config.chart.dropShadow.enabledOnSeries?.length > 0) {
+ if (w.config.chart.dropShadow.enabledOnSeries?.indexOf(i) === -1) {
+ return el
+ }
+ }
+
+ color = Array.isArray(color) ? color[i] : color
+
+ el.filter((add) => {
+ let shadowBlur = null
+ if (Utils.isSafari() || Utils.isFirefox() || Utils.isMsEdge()) {
+ // safari/firefox/IE have some alternative way to use this filter
+ shadowBlur = add
+ .flood(color, opacity)
+ .composite(add.sourceAlpha, 'in')
+ .offset(left, top)
+ .gaussianBlur(blur)
+ } else {
+ shadowBlur = add
+ .flood(color, opacity)
+ .composite(add.sourceAlpha, 'in')
+ .offset(left, top)
+ .gaussianBlur(blur)
+ .merge(add.source)
+ }
+
+ add.blend(add.source, shadowBlur)
+ })
+
+ if (!noUserSpaceOnUse) {
+ el.filterer.node.setAttribute('filterUnits', 'userSpaceOnUse')
+ }
+
+ this._scaleFilterSize(el.filterer.node)
+
+ return el
+ }
+
+ setSelectionFilter(el, realIndex, dataPointIndex) {
+ const w = this.w
+ if (typeof w.globals.selectedDataPoints[realIndex] !== 'undefined') {
+ if (
+ w.globals.selectedDataPoints[realIndex].indexOf(dataPointIndex) > -1
+ ) {
+ el.node.setAttribute('selected', true)
+ let activeFilter = w.config.states.active.filter
+ if (activeFilter !== 'none') {
+ this.applyFilter(el, realIndex, activeFilter.type, activeFilter.value)
+ }
+ }
+ }
+ }
+
+ _scaleFilterSize(el) {
+ const setAttributes = (attrs) => {
+ for (let key in attrs) {
+ if (attrs.hasOwnProperty(key)) {
+ el.setAttribute(key, attrs[key])
+ }
+ }
+ }
+ setAttributes({
+ width: '200%',
+ height: '200%',
+ x: '-50%',
+ y: '-50%',
+ })
+ }
+}
+
+export default Filters
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Formatters.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Formatters.js
new file mode 100644
index 0000000..31112f4
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Formatters.js
@@ -0,0 +1,185 @@
+import DateTime from '../utils/DateTime'
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts Formatter Class for setting value formatters for axes as well as tooltips.
+ *
+ * @module Formatters
+ **/
+
+class Formatters {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ this.tooltipKeyFormat = 'dd MMM'
+ }
+
+ xLabelFormat(fn, val, timestamp, opts) {
+ let w = this.w
+
+ if (w.config.xaxis.type === 'datetime') {
+ if (w.config.xaxis.labels.formatter === undefined) {
+ // if user has not specified a custom formatter, use the default tooltip.x.format
+ if (w.config.tooltip.x.formatter === undefined) {
+ let datetimeObj = new DateTime(this.ctx)
+ return datetimeObj.formatDate(
+ datetimeObj.getDate(val),
+ w.config.tooltip.x.format
+ )
+ }
+ }
+ }
+
+ return fn(val, timestamp, opts)
+ }
+
+ defaultGeneralFormatter(val) {
+ if (Array.isArray(val)) {
+ return val.map((v) => {
+ return v
+ })
+ } else {
+ return val
+ }
+ }
+
+ defaultYFormatter(v, yaxe, i) {
+ let w = this.w
+
+ if (Utils.isNumber(v)) {
+ if (w.globals.yValueDecimal !== 0) {
+ v = v.toFixed(
+ yaxe.decimalsInFloat !== undefined
+ ? yaxe.decimalsInFloat
+ : w.globals.yValueDecimal
+ )
+ } else {
+ // We have an integer value but the label is not an integer. We can
+ // deduce this is due to the number of ticks exceeding the even lower
+ // integer range. Add an additional decimal place only in this case.
+ const f = v.toFixed(0)
+ // Do not change the == to ===
+ v = v == f ? f : v.toFixed(1)
+ }
+ }
+ return v
+ }
+
+ setLabelFormatters() {
+ let w = this.w
+
+ w.globals.xaxisTooltipFormatter = (val) => {
+ return this.defaultGeneralFormatter(val)
+ }
+
+ w.globals.ttKeyFormatter = (val) => {
+ return this.defaultGeneralFormatter(val)
+ }
+
+ w.globals.ttZFormatter = (val) => {
+ return val
+ }
+
+ w.globals.legendFormatter = (val) => {
+ return this.defaultGeneralFormatter(val)
+ }
+
+ // formatter function will always overwrite format property
+ if (w.config.xaxis.labels.formatter !== undefined) {
+ w.globals.xLabelFormatter = w.config.xaxis.labels.formatter
+ } else {
+ w.globals.xLabelFormatter = (val) => {
+ if (Utils.isNumber(val)) {
+ if (
+ !w.config.xaxis.convertedCatToNumeric &&
+ w.config.xaxis.type === 'numeric'
+ ) {
+ if (Utils.isNumber(w.config.xaxis.decimalsInFloat)) {
+ return val.toFixed(w.config.xaxis.decimalsInFloat)
+ } else {
+ const diff = w.globals.maxX - w.globals.minX
+ if (diff > 0 && diff < 100) {
+ return val.toFixed(1)
+ }
+ return val.toFixed(0)
+ }
+ }
+
+ if (w.globals.isBarHorizontal) {
+ const range = w.globals.maxY - w.globals.minYArr
+ if (range < 4) {
+ return val.toFixed(1)
+ }
+ }
+ return val.toFixed(0)
+ }
+ return val
+ }
+ }
+
+ if (typeof w.config.tooltip.x.formatter === 'function') {
+ w.globals.ttKeyFormatter = w.config.tooltip.x.formatter
+ } else {
+ w.globals.ttKeyFormatter = w.globals.xLabelFormatter
+ }
+
+ if (typeof w.config.xaxis.tooltip.formatter === 'function') {
+ w.globals.xaxisTooltipFormatter = w.config.xaxis.tooltip.formatter
+ }
+
+ if (Array.isArray(w.config.tooltip.y)) {
+ w.globals.ttVal = w.config.tooltip.y
+ } else {
+ if (w.config.tooltip.y.formatter !== undefined) {
+ w.globals.ttVal = w.config.tooltip.y
+ }
+ }
+
+ if (w.config.tooltip.z.formatter !== undefined) {
+ w.globals.ttZFormatter = w.config.tooltip.z.formatter
+ }
+
+ // legend formatter - if user wants to append any global values of series to legend text
+ if (w.config.legend.formatter !== undefined) {
+ w.globals.legendFormatter = w.config.legend.formatter
+ }
+
+ // formatter function will always overwrite format property
+ w.config.yaxis.forEach((yaxe, i) => {
+ if (yaxe.labels.formatter !== undefined) {
+ w.globals.yLabelFormatters[i] = yaxe.labels.formatter
+ } else {
+ w.globals.yLabelFormatters[i] = (val) => {
+ if (!w.globals.xyCharts) return val
+
+ if (Array.isArray(val)) {
+ return val.map((v) => {
+ return this.defaultYFormatter(v, yaxe, i)
+ })
+ } else {
+ return this.defaultYFormatter(val, yaxe, i)
+ }
+ }
+ }
+ })
+
+ return w.globals
+ }
+
+ heatmapLabelFormatters() {
+ const w = this.w
+ if (w.config.chart.type === 'heatmap') {
+ w.globals.yAxisScale[0].result = w.globals.seriesNames.slice()
+
+ // get the longest string from the labels array and also apply label formatter to it
+ let longest = w.globals.seriesNames.reduce(
+ (a, b) => (a.length > b.length ? a : b),
+ 0
+ )
+ w.globals.yAxisScale[0].niceMax = longest
+ w.globals.yAxisScale[0].niceMin = longest
+ }
+ }
+}
+
+export default Formatters
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Graphics.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Graphics.js
new file mode 100644
index 0000000..163627f
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Graphics.js
@@ -0,0 +1,1102 @@
+import Animations from './Animations'
+import Filters from './Filters'
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts Graphics Class for all drawing operations.
+ *
+ * @module Graphics
+ **/
+
+class Graphics {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ /*****************************************************************************
+ * *
+ * SVG Path Rounding Function *
+ * Copyright (C) 2014 Yona Appletree *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); *
+ * you may not use this file except in compliance with the License. *
+ * You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ * *
+ *****************************************************************************/
+
+ /**
+ * SVG Path rounding function. Takes an input path string and outputs a path
+ * string where all line-line corners have been rounded. Only supports absolute
+ * commands at the moment.
+ *
+ * @param pathString The SVG input path
+ * @param radius The amount to round the corners, either a value in the SVG
+ * coordinate space, or, if useFractionalRadius is true, a value
+ * from 0 to 1.
+ * @returns A new SVG path string with the rounding
+ */
+ roundPathCorners(pathString, radius) {
+ if (pathString.indexOf('NaN') > -1) pathString = ''
+
+ function moveTowardsLength(movingPoint, targetPoint, amount) {
+ var width = targetPoint.x - movingPoint.x
+ var height = targetPoint.y - movingPoint.y
+
+ var distance = Math.sqrt(width * width + height * height)
+
+ return moveTowardsFractional(
+ movingPoint,
+ targetPoint,
+ Math.min(1, amount / distance)
+ )
+ }
+ function moveTowardsFractional(movingPoint, targetPoint, fraction) {
+ return {
+ x: movingPoint.x + (targetPoint.x - movingPoint.x) * fraction,
+ y: movingPoint.y + (targetPoint.y - movingPoint.y) * fraction,
+ }
+ }
+
+ // Adjusts the ending position of a command
+ function adjustCommand(cmd, newPoint) {
+ if (cmd.length > 2) {
+ cmd[cmd.length - 2] = newPoint.x
+ cmd[cmd.length - 1] = newPoint.y
+ }
+ }
+
+ // Gives an {x, y} object for a command's ending position
+ function pointForCommand(cmd) {
+ return {
+ x: parseFloat(cmd[cmd.length - 2]),
+ y: parseFloat(cmd[cmd.length - 1]),
+ }
+ }
+
+ // Split apart the path, handing concatonated letters and numbers
+ var pathParts = pathString.split(/[,\s]/).reduce(function (parts, part) {
+ var match = part.match('([a-zA-Z])(.+)')
+ if (match) {
+ parts.push(match[1])
+ parts.push(match[2])
+ } else {
+ parts.push(part)
+ }
+
+ return parts
+ }, [])
+
+ // Group the commands with their arguments for easier handling
+ var commands = pathParts.reduce(function (commands, part) {
+ if (parseFloat(part) == part && commands.length) {
+ commands[commands.length - 1].push(part)
+ } else {
+ commands.push([part])
+ }
+
+ return commands
+ }, [])
+
+ // The resulting commands, also grouped
+ var resultCommands = []
+
+ if (commands.length > 1) {
+ var startPoint = pointForCommand(commands[0])
+
+ // Handle the close path case with a "virtual" closing line
+ var virtualCloseLine = null
+ if (commands[commands.length - 1][0] == 'Z' && commands[0].length > 2) {
+ virtualCloseLine = ['L', startPoint.x, startPoint.y]
+ commands[commands.length - 1] = virtualCloseLine
+ }
+
+ // We always use the first command (but it may be mutated)
+ resultCommands.push(commands[0])
+
+ for (var cmdIndex = 1; cmdIndex < commands.length; cmdIndex++) {
+ var prevCmd = resultCommands[resultCommands.length - 1]
+
+ var curCmd = commands[cmdIndex]
+
+ // Handle closing case
+ var nextCmd =
+ curCmd == virtualCloseLine ? commands[1] : commands[cmdIndex + 1]
+
+ // Nasty logic to decide if this path is a candidite.
+ if (
+ nextCmd &&
+ prevCmd &&
+ prevCmd.length > 2 &&
+ curCmd[0] == 'L' &&
+ nextCmd.length > 2 &&
+ nextCmd[0] == 'L'
+ ) {
+ // Calc the points we're dealing with
+ var prevPoint = pointForCommand(prevCmd)
+ var curPoint = pointForCommand(curCmd)
+ var nextPoint = pointForCommand(nextCmd)
+
+ // The start and end of the cuve are just our point moved towards the previous and next points, respectivly
+ var curveStart, curveEnd
+
+ curveStart = moveTowardsLength(curPoint, prevPoint, radius)
+ curveEnd = moveTowardsLength(curPoint, nextPoint, radius)
+
+ // Adjust the current command and add it
+ adjustCommand(curCmd, curveStart)
+ curCmd.origPoint = curPoint
+ resultCommands.push(curCmd)
+
+ // The curve control points are halfway between the start/end of the curve and
+ // the original point
+ var startControl = moveTowardsFractional(curveStart, curPoint, 0.5)
+ var endControl = moveTowardsFractional(curPoint, curveEnd, 0.5)
+
+ // Create the curve
+ var curveCmd = [
+ 'C',
+ startControl.x,
+ startControl.y,
+ endControl.x,
+ endControl.y,
+ curveEnd.x,
+ curveEnd.y,
+ ]
+ // Save the original point for fractional calculations
+ curveCmd.origPoint = curPoint
+ resultCommands.push(curveCmd)
+ } else {
+ // Pass through commands that don't qualify
+ resultCommands.push(curCmd)
+ }
+ }
+
+ // Fix up the starting point and restore the close path if the path was orignally closed
+ if (virtualCloseLine) {
+ var newStartPoint = pointForCommand(
+ resultCommands[resultCommands.length - 1]
+ )
+ resultCommands.push(['Z'])
+ adjustCommand(resultCommands[0], newStartPoint)
+ }
+ } else {
+ resultCommands = commands
+ }
+
+ return resultCommands.reduce(function (str, c) {
+ return str + c.join(' ') + ' '
+ }, '')
+ }
+
+ drawLine(
+ x1,
+ y1,
+ x2,
+ y2,
+ lineColor = '#a8a8a8',
+ dashArray = 0,
+ strokeWidth = null,
+ strokeLineCap = 'butt'
+ ) {
+ let w = this.w
+ let line = w.globals.dom.Paper.line().attr({
+ x1,
+ y1,
+ x2,
+ y2,
+ stroke: lineColor,
+ 'stroke-dasharray': dashArray,
+ 'stroke-width': strokeWidth,
+ 'stroke-linecap': strokeLineCap,
+ })
+
+ return line
+ }
+
+ drawRect(
+ x1 = 0,
+ y1 = 0,
+ x2 = 0,
+ y2 = 0,
+ radius = 0,
+ color = '#fefefe',
+ opacity = 1,
+ strokeWidth = null,
+ strokeColor = null,
+ strokeDashArray = 0
+ ) {
+ let w = this.w
+ let rect = w.globals.dom.Paper.rect()
+
+ rect.attr({
+ x: x1,
+ y: y1,
+ width: x2 > 0 ? x2 : 0,
+ height: y2 > 0 ? y2 : 0,
+ rx: radius,
+ ry: radius,
+ opacity,
+ 'stroke-width': strokeWidth !== null ? strokeWidth : 0,
+ stroke: strokeColor !== null ? strokeColor : 'none',
+ 'stroke-dasharray': strokeDashArray,
+ })
+
+ // fix apexcharts.js#1410
+ rect.node.setAttribute('fill', color)
+
+ return rect
+ }
+
+ drawPolygon(
+ polygonString,
+ stroke = '#e1e1e1',
+ strokeWidth = 1,
+ fill = 'none'
+ ) {
+ const w = this.w
+ const polygon = w.globals.dom.Paper.polygon(polygonString).attr({
+ fill,
+ stroke,
+ 'stroke-width': strokeWidth,
+ })
+
+ return polygon
+ }
+
+ drawCircle(radius, attrs = null) {
+ const w = this.w
+
+ if (radius < 0) radius = 0
+ const c = w.globals.dom.Paper.circle(radius * 2)
+ if (attrs !== null) {
+ c.attr(attrs)
+ }
+ return c
+ }
+
+ drawPath({
+ d = '',
+ stroke = '#a8a8a8',
+ strokeWidth = 1,
+ fill,
+ fillOpacity = 1,
+ strokeOpacity = 1,
+ classes,
+ strokeLinecap = null,
+ strokeDashArray = 0,
+ }) {
+ let w = this.w
+
+ if (strokeLinecap === null) {
+ strokeLinecap = w.config.stroke.lineCap
+ }
+
+ if (d.indexOf('undefined') > -1 || d.indexOf('NaN') > -1) {
+ d = `M 0 ${w.globals.gridHeight}`
+ }
+ let p = w.globals.dom.Paper.path(d).attr({
+ fill,
+ 'fill-opacity': fillOpacity,
+ stroke,
+ 'stroke-opacity': strokeOpacity,
+ 'stroke-linecap': strokeLinecap,
+ 'stroke-width': strokeWidth,
+ 'stroke-dasharray': strokeDashArray,
+ class: classes,
+ })
+
+ return p
+ }
+
+ group(attrs = null) {
+ const w = this.w
+ const g = w.globals.dom.Paper.group()
+
+ if (attrs !== null) {
+ g.attr(attrs)
+ }
+ return g
+ }
+
+ move(x, y) {
+ let move = ['M', x, y].join(' ')
+ return move
+ }
+
+ line(x, y, hORv = null) {
+ let line = null
+ if (hORv === null) {
+ line = [' L', x, y].join(' ')
+ } else if (hORv === 'H') {
+ line = [' H', x].join(' ')
+ } else if (hORv === 'V') {
+ line = [' V', y].join(' ')
+ }
+ return line
+ }
+
+ curve(x1, y1, x2, y2, x, y) {
+ let curve = ['C', x1, y1, x2, y2, x, y].join(' ')
+ return curve
+ }
+
+ quadraticCurve(x1, y1, x, y) {
+ let curve = ['Q', x1, y1, x, y].join(' ')
+ return curve
+ }
+
+ arc(rx, ry, axisRotation, largeArcFlag, sweepFlag, x, y, relative = false) {
+ let coord = 'A'
+ if (relative) coord = 'a'
+
+ let arc = [coord, rx, ry, axisRotation, largeArcFlag, sweepFlag, x, y].join(
+ ' '
+ )
+ return arc
+ }
+
+ /**
+ * @memberof Graphics
+ * @param {object}
+ * i = series's index
+ * realIndex = realIndex is series's actual index when it was drawn time. After several redraws, the iterating "i" may change in loops, but realIndex doesn't
+ * pathFrom = existing pathFrom to animateTo
+ * pathTo = new Path to which d attr will be animated from pathFrom to pathTo
+ * stroke = line Color
+ * strokeWidth = width of path Line
+ * fill = it can be gradient, single color, pattern or image
+ * animationDelay = how much to delay when starting animation (in milliseconds)
+ * dataChangeSpeed = for dynamic animations, when data changes
+ * className = class attribute to add
+ * @return {object} svg.js path object
+ **/
+ renderPaths({
+ j,
+ realIndex,
+ pathFrom,
+ pathTo,
+ stroke,
+ strokeWidth,
+ strokeLinecap,
+ fill,
+ animationDelay,
+ initialSpeed,
+ dataChangeSpeed,
+ className,
+ chartType,
+ shouldClipToGrid = true,
+ bindEventsOnPaths = true,
+ drawShadow = true,
+ }) {
+ let w = this.w
+ const filters = new Filters(this.ctx)
+ const anim = new Animations(this.ctx)
+
+ let initialAnim = this.w.config.chart.animations.enabled
+ let dynamicAnim =
+ initialAnim && this.w.config.chart.animations.dynamicAnimation.enabled
+
+ let d
+ let shouldAnimate = !!(
+ (initialAnim && !w.globals.resized) ||
+ (dynamicAnim && w.globals.dataChanged && w.globals.shouldAnimate)
+ )
+
+ if (shouldAnimate) {
+ d = pathFrom
+ } else {
+ d = pathTo
+ w.globals.animationEnded = true
+ }
+
+ let strokeDashArrayOpt = w.config.stroke.dashArray
+ let strokeDashArray = 0
+ if (Array.isArray(strokeDashArrayOpt)) {
+ strokeDashArray = strokeDashArrayOpt[realIndex]
+ } else {
+ strokeDashArray = w.config.stroke.dashArray
+ }
+
+ let el = this.drawPath({
+ d,
+ stroke,
+ strokeWidth,
+ fill,
+ fillOpacity: 1,
+ classes: className,
+ strokeLinecap,
+ strokeDashArray,
+ })
+
+ el.attr('index', realIndex)
+
+ if (shouldClipToGrid) {
+ if (
+ (chartType === 'bar' && !w.globals.isHorizontal) ||
+ w.globals.comboCharts
+ ) {
+ el.attr({
+ 'clip-path': `url(#gridRectBarMask${w.globals.cuid})`,
+ })
+ } else {
+ el.attr({
+ 'clip-path': `url(#gridRectMask${w.globals.cuid})`,
+ })
+ }
+ }
+
+ // const defaultFilter = el.filterer
+
+ if (w.config.states.normal.filter.type !== 'none') {
+ filters.getDefaultFilter(el, realIndex)
+ } else {
+ if (w.config.chart.dropShadow.enabled && drawShadow) {
+ const shadow = w.config.chart.dropShadow
+ filters.dropShadow(el, shadow, realIndex)
+ }
+ }
+
+ if (bindEventsOnPaths) {
+ el.node.addEventListener('mouseenter', this.pathMouseEnter.bind(this, el))
+ el.node.addEventListener('mouseleave', this.pathMouseLeave.bind(this, el))
+ el.node.addEventListener('mousedown', this.pathMouseDown.bind(this, el))
+ }
+
+ el.attr({
+ pathTo,
+ pathFrom,
+ })
+
+ const defaultAnimateOpts = {
+ el,
+ j,
+ realIndex,
+ pathFrom,
+ pathTo,
+ fill,
+ strokeWidth,
+ delay: animationDelay,
+ }
+
+ if (initialAnim && !w.globals.resized && !w.globals.dataChanged) {
+ anim.animatePathsGradually({
+ ...defaultAnimateOpts,
+ speed: initialSpeed,
+ })
+ } else {
+ if (w.globals.resized || !w.globals.dataChanged) {
+ anim.showDelayedElements()
+ }
+ }
+
+ if (w.globals.dataChanged && dynamicAnim && shouldAnimate) {
+ anim.animatePathsGradually({
+ ...defaultAnimateOpts,
+ speed: dataChangeSpeed,
+ })
+ }
+
+ return el
+ }
+
+ drawPattern(
+ style,
+ width,
+ height,
+ stroke = '#a8a8a8',
+ strokeWidth = 0,
+ opacity = 1
+ ) {
+ let w = this.w
+
+ let p = w.globals.dom.Paper.pattern(width, height, (add) => {
+ if (style === 'horizontalLines') {
+ add
+ .line(0, 0, height, 0)
+ .stroke({ color: stroke, width: strokeWidth + 1 })
+ } else if (style === 'verticalLines') {
+ add
+ .line(0, 0, 0, width)
+ .stroke({ color: stroke, width: strokeWidth + 1 })
+ } else if (style === 'slantedLines') {
+ add
+ .line(0, 0, width, height)
+ .stroke({ color: stroke, width: strokeWidth })
+ } else if (style === 'squares') {
+ add
+ .rect(width, height)
+ .fill('none')
+ .stroke({ color: stroke, width: strokeWidth })
+ } else if (style === 'circles') {
+ add
+ .circle(width)
+ .fill('none')
+ .stroke({ color: stroke, width: strokeWidth })
+ }
+ })
+
+ return p
+ }
+
+ drawGradient(
+ style,
+ gfrom,
+ gto,
+ opacityFrom,
+ opacityTo,
+ size = null,
+ stops = null,
+ colorStops = null,
+ i = 0
+ ) {
+ let w = this.w
+ let g
+
+ if (gfrom.length < 9 && gfrom.indexOf('#') === 0) {
+ // if the hex contains alpha and is of 9 digit, skip the opacity
+ gfrom = Utils.hexToRgba(gfrom, opacityFrom)
+ }
+ if (gto.length < 9 && gto.indexOf('#') === 0) {
+ gto = Utils.hexToRgba(gto, opacityTo)
+ }
+
+ let stop1 = 0
+ let stop2 = 1
+ let stop3 = 1
+ let stop4 = null
+
+ if (stops !== null) {
+ stop1 = typeof stops[0] !== 'undefined' ? stops[0] / 100 : 0
+ stop2 = typeof stops[1] !== 'undefined' ? stops[1] / 100 : 1
+ stop3 = typeof stops[2] !== 'undefined' ? stops[2] / 100 : 1
+ stop4 = typeof stops[3] !== 'undefined' ? stops[3] / 100 : null
+ }
+
+ let radial = !!(
+ w.config.chart.type === 'donut' ||
+ w.config.chart.type === 'pie' ||
+ w.config.chart.type === 'polarArea' ||
+ w.config.chart.type === 'bubble'
+ )
+
+ if (colorStops === null || colorStops.length === 0) {
+ g = w.globals.dom.Paper.gradient(radial ? 'radial' : 'linear', (stop) => {
+ stop.at(stop1, gfrom, opacityFrom)
+ stop.at(stop2, gto, opacityTo)
+ stop.at(stop3, gto, opacityTo)
+ if (stop4 !== null) {
+ stop.at(stop4, gfrom, opacityFrom)
+ }
+ })
+ } else {
+ g = w.globals.dom.Paper.gradient(radial ? 'radial' : 'linear', (stop) => {
+ let gradientStops = Array.isArray(colorStops[i])
+ ? colorStops[i]
+ : colorStops
+ gradientStops.forEach((s) => {
+ stop.at(s.offset / 100, s.color, s.opacity)
+ })
+ })
+ }
+
+ if (!radial) {
+ if (style === 'vertical') {
+ g.from(0, 0).to(0, 1)
+ } else if (style === 'diagonal') {
+ g.from(0, 0).to(1, 1)
+ } else if (style === 'horizontal') {
+ g.from(0, 1).to(1, 1)
+ } else if (style === 'diagonal2') {
+ g.from(1, 0).to(0, 1)
+ }
+ } else {
+ let offx = w.globals.gridWidth / 2
+ let offy = w.globals.gridHeight / 2
+
+ if (w.config.chart.type !== 'bubble') {
+ g.attr({
+ gradientUnits: 'userSpaceOnUse',
+ cx: offx,
+ cy: offy,
+ r: size,
+ })
+ } else {
+ g.attr({
+ cx: 0.5,
+ cy: 0.5,
+ r: 0.8,
+ fx: 0.2,
+ fy: 0.2,
+ })
+ }
+ }
+
+ return g
+ }
+
+ getTextBasedOnMaxWidth({ text, maxWidth, fontSize, fontFamily }) {
+ const tRects = this.getTextRects(text, fontSize, fontFamily)
+ const wordWidth = tRects.width / text.length
+ const wordsBasedOnWidth = Math.floor(maxWidth / wordWidth)
+ if (maxWidth < tRects.width) {
+ return text.slice(0, wordsBasedOnWidth - 3) + '...'
+ }
+ return text
+ }
+
+ drawText({
+ x,
+ y,
+ text,
+ textAnchor,
+ fontSize,
+ fontFamily,
+ fontWeight,
+ foreColor,
+ opacity,
+ maxWidth,
+ cssClass = '',
+ isPlainText = true,
+ dominantBaseline = 'auto',
+ }) {
+ let w = this.w
+
+ if (typeof text === 'undefined') text = ''
+
+ let truncatedText = text
+ if (!textAnchor) {
+ textAnchor = 'start'
+ }
+
+ if (!foreColor || !foreColor.length) {
+ foreColor = w.config.chart.foreColor
+ }
+ fontFamily = fontFamily || w.config.chart.fontFamily
+ fontSize = fontSize || '11px'
+ fontWeight = fontWeight || 'regular'
+
+ const commonProps = {
+ maxWidth,
+ fontSize,
+ fontFamily,
+ }
+ let elText
+ if (Array.isArray(text)) {
+ elText = w.globals.dom.Paper.text((add) => {
+ for (let i = 0; i < text.length; i++) {
+ truncatedText = text[i]
+ if (maxWidth) {
+ truncatedText = this.getTextBasedOnMaxWidth({
+ text: text[i],
+ ...commonProps,
+ })
+ }
+ i === 0
+ ? add.tspan(truncatedText)
+ : add.tspan(truncatedText).newLine()
+ }
+ })
+ } else {
+ if (maxWidth) {
+ truncatedText = this.getTextBasedOnMaxWidth({
+ text,
+ ...commonProps,
+ })
+ }
+ elText = isPlainText
+ ? w.globals.dom.Paper.plain(text)
+ : w.globals.dom.Paper.text((add) => add.tspan(truncatedText))
+ }
+
+ elText.attr({
+ x,
+ y,
+ 'text-anchor': textAnchor,
+ 'dominant-baseline': dominantBaseline,
+ 'font-size': fontSize,
+ 'font-family': fontFamily,
+ 'font-weight': fontWeight,
+ fill: foreColor,
+ class: 'apexcharts-text ' + cssClass,
+ })
+
+ elText.node.style.fontFamily = fontFamily
+ elText.node.style.opacity = opacity
+
+ return elText
+ }
+
+ getMarkerPath(x, y, type, size) {
+ let d = ''
+ switch (type) {
+ case 'cross':
+ size = size / 1.4
+ d = `M ${x - size} ${y - size} L ${x + size} ${y + size} M ${
+ x - size
+ } ${y + size} L ${x + size} ${y - size}`
+ break
+ case 'plus':
+ size = size / 1.12
+ d = `M ${x - size} ${y} L ${x + size} ${y} M ${x} ${y - size} L ${x} ${
+ y + size
+ }`
+ break
+ case 'star':
+ case 'sparkle':
+ let points = 5
+ size = size * 1.15
+ if (type === 'sparkle') {
+ size = size / 1.1
+ points = 4
+ }
+ const step = Math.PI / points
+
+ for (let i = 0; i <= 2 * points; i++) {
+ const angle = i * step
+ const radius = i % 2 === 0 ? size : size / 2
+ const xPos = x + radius * Math.sin(angle)
+ const yPos = y - radius * Math.cos(angle)
+
+ d += (i === 0 ? 'M' : 'L') + xPos + ',' + yPos
+ }
+ d += 'Z'
+ break
+ case 'triangle':
+ d = `M ${x} ${y - size}
+ L ${x + size} ${y + size}
+ L ${x - size} ${y + size}
+ Z`
+ break
+ case 'square':
+ case 'rect':
+ size = size / 1.125
+ d = `M ${x - size} ${y - size}
+ L ${x + size} ${y - size}
+ L ${x + size} ${y + size}
+ L ${x - size} ${y + size}
+ Z`
+ break
+ case 'diamond':
+ size = size * 1.05
+ d = `M ${x} ${y - size}
+ L ${x + size} ${y}
+ L ${x} ${y + size}
+ L ${x - size} ${y}
+ Z`
+ break
+ case 'line':
+ size = size / 1.1
+ d = `M ${x - size} ${y}
+ L ${x + size} ${y}`
+ break
+ case 'circle':
+ default:
+ size = size * 2
+ d = `M ${x}, ${y}
+ m -${size / 2}, 0
+ a ${size / 2},${size / 2} 0 1,0 ${size},0
+ a ${size / 2},${size / 2} 0 1,0 -${size},0`
+ break
+ }
+ return d
+ }
+
+ /**
+ * @param {number} x - The x-coordinate of the marker
+ * @param {number} y - The y-coordinate of the marker.
+ * @param {number} size - The size of the marker
+ * @param {Object} opts - The options for the marker.
+ * @returns {Object} The created marker.
+ */
+ drawMarkerShape(x, y, type, size, opts) {
+ const path = this.drawPath({
+ d: this.getMarkerPath(x, y, type, size, opts),
+ stroke: opts.pointStrokeColor,
+ strokeDashArray: opts.pointStrokeDashArray,
+ strokeWidth: opts.pointStrokeWidth,
+ fill: opts.pointFillColor,
+ fillOpacity: opts.pointFillOpacity,
+ strokeOpacity: opts.pointStrokeOpacity,
+ })
+
+ path.attr({
+ cx: x,
+ cy: y,
+ shape: opts.shape,
+ class: opts.class ? opts.class : '',
+ })
+
+ return path
+ }
+
+ drawMarker(x, y, opts) {
+ x = x || 0
+ let size = opts.pSize || 0
+
+ if (!Utils.isNumber(y)) {
+ size = 0
+ y = 0
+ }
+
+ return this.drawMarkerShape(x, y, opts?.shape, size, {
+ ...opts,
+ ...(opts.shape === 'line' ||
+ opts.shape === 'plus' ||
+ opts.shape === 'cross'
+ ? {
+ pointStrokeColor: opts.pointFillColor,
+ pointStrokeOpacity: opts.pointFillOpacity,
+ }
+ : {}),
+ })
+ }
+
+ pathMouseEnter(path, e) {
+ let w = this.w
+ const filters = new Filters(this.ctx)
+
+ const i = parseInt(path.node.getAttribute('index'), 10)
+ const j = parseInt(path.node.getAttribute('j'), 10)
+
+ if (typeof w.config.chart.events.dataPointMouseEnter === 'function') {
+ w.config.chart.events.dataPointMouseEnter(e, this.ctx, {
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ })
+ }
+ this.ctx.events.fireEvent('dataPointMouseEnter', [
+ e,
+ this.ctx,
+ { seriesIndex: i, dataPointIndex: j, w },
+ ])
+
+ if (w.config.states.active.filter.type !== 'none') {
+ if (path.node.getAttribute('selected') === 'true') {
+ return
+ }
+ }
+
+ if (w.config.states.hover.filter.type !== 'none') {
+ if (!w.globals.isTouchDevice) {
+ let hoverFilter = w.config.states.hover.filter
+ filters.applyFilter(path, i, hoverFilter.type, hoverFilter.value)
+ }
+ }
+ }
+
+ pathMouseLeave(path, e) {
+ let w = this.w
+ const filters = new Filters(this.ctx)
+
+ const i = parseInt(path.node.getAttribute('index'), 10)
+ const j = parseInt(path.node.getAttribute('j'), 10)
+
+ if (typeof w.config.chart.events.dataPointMouseLeave === 'function') {
+ w.config.chart.events.dataPointMouseLeave(e, this.ctx, {
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ })
+ }
+ this.ctx.events.fireEvent('dataPointMouseLeave', [
+ e,
+ this.ctx,
+ { seriesIndex: i, dataPointIndex: j, w },
+ ])
+
+ if (w.config.states.active.filter.type !== 'none') {
+ if (path.node.getAttribute('selected') === 'true') {
+ return
+ }
+ }
+
+ if (w.config.states.hover.filter.type !== 'none') {
+ filters.getDefaultFilter(path, i)
+ }
+ }
+
+ pathMouseDown(path, e) {
+ let w = this.w
+ const filters = new Filters(this.ctx)
+
+ const i = parseInt(path.node.getAttribute('index'), 10)
+ const j = parseInt(path.node.getAttribute('j'), 10)
+
+ let selected = 'false'
+ if (path.node.getAttribute('selected') === 'true') {
+ path.node.setAttribute('selected', 'false')
+
+ if (w.globals.selectedDataPoints[i].indexOf(j) > -1) {
+ let index = w.globals.selectedDataPoints[i].indexOf(j)
+ w.globals.selectedDataPoints[i].splice(index, 1)
+ }
+ } else {
+ if (
+ !w.config.states.active.allowMultipleDataPointsSelection &&
+ w.globals.selectedDataPoints.length > 0
+ ) {
+ w.globals.selectedDataPoints = []
+ const elPaths = w.globals.dom.Paper.select(
+ '.apexcharts-series path'
+ ).members
+ const elCircles = w.globals.dom.Paper.select(
+ '.apexcharts-series circle, .apexcharts-series rect'
+ ).members
+
+ const deSelect = (els) => {
+ Array.prototype.forEach.call(els, (el) => {
+ el.node.setAttribute('selected', 'false')
+ filters.getDefaultFilter(el, i)
+ })
+ }
+ deSelect(elPaths)
+ deSelect(elCircles)
+ }
+
+ path.node.setAttribute('selected', 'true')
+ selected = 'true'
+
+ if (typeof w.globals.selectedDataPoints[i] === 'undefined') {
+ w.globals.selectedDataPoints[i] = []
+ }
+ w.globals.selectedDataPoints[i].push(j)
+ }
+
+ if (selected === 'true') {
+ let activeFilter = w.config.states.active.filter
+ if (activeFilter !== 'none') {
+ filters.applyFilter(path, i, activeFilter.type, activeFilter.value)
+ } else {
+ // Reapply the hover filter in case it was removed by `deselect`when there is no active filter and it is not a touch device
+ if (w.config.states.hover.filter !== 'none') {
+ if (!w.globals.isTouchDevice) {
+ var hoverFilter = w.config.states.hover.filter
+ filters.applyFilter(path, i, hoverFilter.type, hoverFilter.value)
+ }
+ }
+ }
+ } else {
+ // If the item was deselected, apply hover state filter if it is not a touch device
+ if (w.config.states.active.filter.type !== 'none') {
+ if (
+ w.config.states.hover.filter.type !== 'none' &&
+ !w.globals.isTouchDevice
+ ) {
+ var hoverFilter = w.config.states.hover.filter
+ filters.applyFilter(path, i, hoverFilter.type, hoverFilter.value)
+ } else {
+ filters.getDefaultFilter(path, i)
+ }
+ }
+ }
+
+ if (typeof w.config.chart.events.dataPointSelection === 'function') {
+ w.config.chart.events.dataPointSelection(e, this.ctx, {
+ selectedDataPoints: w.globals.selectedDataPoints,
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ })
+ }
+
+ if (e) {
+ this.ctx.events.fireEvent('dataPointSelection', [
+ e,
+ this.ctx,
+ {
+ selectedDataPoints: w.globals.selectedDataPoints,
+ seriesIndex: i,
+ dataPointIndex: j,
+ w,
+ },
+ ])
+ }
+ }
+
+ rotateAroundCenter(el) {
+ let coord = {}
+ if (el && typeof el.getBBox === 'function') {
+ coord = el.getBBox()
+ }
+ let x = coord.x + coord.width / 2
+ let y = coord.y + coord.height / 2
+
+ return {
+ x,
+ y,
+ }
+ }
+
+ static setAttrs(el, attrs) {
+ for (let key in attrs) {
+ if (attrs.hasOwnProperty(key)) {
+ el.setAttribute(key, attrs[key])
+ }
+ }
+ }
+
+ getTextRects(text, fontSize, fontFamily, transform, useBBox = true) {
+ let w = this.w
+ let virtualText = this.drawText({
+ x: -200,
+ y: -200,
+ text,
+ textAnchor: 'start',
+ fontSize,
+ fontFamily,
+ foreColor: '#fff',
+ opacity: 0,
+ })
+
+ if (transform) {
+ virtualText.attr('transform', transform)
+ }
+ w.globals.dom.Paper.add(virtualText)
+
+ let rect = virtualText.bbox()
+ if (!useBBox) {
+ rect = virtualText.node.getBoundingClientRect()
+ }
+
+ virtualText.remove()
+
+ return {
+ width: rect.width,
+ height: rect.height,
+ }
+ }
+
+ /**
+ * append ... to long text
+ * http://stackoverflow.com/questions/9241315/trimming-text-to-a-given-pixel-width-in-svg
+ * @memberof Graphics
+ **/
+ placeTextWithEllipsis(textObj, textString, width) {
+ if (typeof textObj.getComputedTextLength !== 'function') return
+ textObj.textContent = textString
+ if (textString.length > 0) {
+ // ellipsis is needed
+ if (textObj.getComputedTextLength() >= width / 1.1) {
+ for (let x = textString.length - 3; x > 0; x -= 3) {
+ if (textObj.getSubStringLength(0, x) <= width / 1.1) {
+ textObj.textContent = textString.substring(0, x) + '...'
+ return
+ }
+ }
+ textObj.textContent = '.' // can't place at all
+ }
+ }
+ }
+}
+
+export default Graphics
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Markers.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Markers.js
new file mode 100644
index 0000000..544f214
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Markers.js
@@ -0,0 +1,258 @@
+import Filters from './Filters'
+import Graphics from './Graphics'
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts Markers Class for drawing points on y values in axes charts.
+ *
+ * @module Markers
+ **/
+
+export default class Markers {
+ constructor(ctx, opts) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ setGlobalMarkerSize() {
+ const w = this.w
+
+ w.globals.markers.size = Array.isArray(w.config.markers.size)
+ ? w.config.markers.size
+ : [w.config.markers.size]
+
+ if (w.globals.markers.size.length > 0) {
+ if (w.globals.markers.size.length < w.globals.series.length + 1) {
+ for (let i = 0; i <= w.globals.series.length; i++) {
+ if (typeof w.globals.markers.size[i] === 'undefined') {
+ w.globals.markers.size.push(w.globals.markers.size[0])
+ }
+ }
+ }
+ } else {
+ w.globals.markers.size = w.config.series.map((s) => w.config.markers.size)
+ }
+ }
+
+ plotChartMarkers(pointsPos, seriesIndex, j, pSize, alwaysDrawMarker = false) {
+ let w = this.w
+
+ let i = seriesIndex
+ let p = pointsPos
+ let elPointsWrap = null
+
+ let graphics = new Graphics(this.ctx)
+
+ let point
+
+ const hasDiscreteMarkers =
+ w.config.markers.discrete && w.config.markers.discrete.length
+
+ if (
+ w.globals.markers.size[seriesIndex] > 0 ||
+ alwaysDrawMarker ||
+ hasDiscreteMarkers
+ ) {
+ elPointsWrap = graphics.group({
+ class:
+ alwaysDrawMarker || hasDiscreteMarkers
+ ? ''
+ : 'apexcharts-series-markers',
+ })
+
+ elPointsWrap.attr(
+ 'clip-path',
+ `url(#gridRectMarkerMask${w.globals.cuid})`
+ )
+ }
+
+ if (Array.isArray(p.x)) {
+ for (let q = 0; q < p.x.length; q++) {
+ let dataPointIndex = j
+
+ // a small hack as we have 2 points for the first val to connect it
+ if (j === 1 && q === 0) dataPointIndex = 0
+ if (j === 1 && q === 1) dataPointIndex = 1
+
+ let PointClasses = 'apexcharts-marker'
+ if (
+ (w.config.chart.type === 'line' || w.config.chart.type === 'area') &&
+ !w.globals.comboCharts &&
+ !w.config.tooltip.intersect
+ ) {
+ PointClasses += ' no-pointer-events'
+ }
+
+ const shouldMarkerDraw = Array.isArray(w.config.markers.size)
+ ? w.globals.markers.size[seriesIndex] > 0
+ : w.config.markers.size > 0
+
+ if (shouldMarkerDraw || alwaysDrawMarker || hasDiscreteMarkers) {
+ if (Utils.isNumber(p.y[q])) {
+ PointClasses += ` w${Utils.randomId()}`
+ } else {
+ PointClasses = 'apexcharts-nullpoint'
+ }
+
+ let opts = this.getMarkerConfig({
+ cssClass: PointClasses,
+ seriesIndex,
+ dataPointIndex,
+ })
+
+ if (w.config.series[i].data[dataPointIndex]) {
+ if (w.config.series[i].data[dataPointIndex].fillColor) {
+ opts.pointFillColor =
+ w.config.series[i].data[dataPointIndex].fillColor
+ }
+
+ if (w.config.series[i].data[dataPointIndex].strokeColor) {
+ opts.pointStrokeColor =
+ w.config.series[i].data[dataPointIndex].strokeColor
+ }
+ }
+
+ if (typeof pSize !== 'undefined') {
+ opts.pSize = pSize
+ }
+
+ if (
+ p.x[q] < -w.globals.markers.largestSize ||
+ p.x[q] > w.globals.gridWidth + w.globals.markers.largestSize ||
+ p.y[q] < -w.globals.markers.largestSize ||
+ p.y[q] > w.globals.gridHeight + w.globals.markers.largestSize
+ ) {
+ opts.pSize = 0
+ }
+
+ point = graphics.drawMarker(p.x[q], p.y[q], opts)
+
+ point.attr('rel', dataPointIndex)
+ point.attr('j', dataPointIndex)
+ point.attr('index', seriesIndex)
+ point.node.setAttribute('default-marker-size', opts.pSize)
+
+ const filters = new Filters(this.ctx)
+ filters.setSelectionFilter(point, seriesIndex, dataPointIndex)
+ this.addEvents(point)
+
+ if (elPointsWrap) {
+ elPointsWrap.add(point)
+ }
+ } else {
+ // dynamic array creation - multidimensional
+ if (typeof w.globals.pointsArray[seriesIndex] === 'undefined')
+ w.globals.pointsArray[seriesIndex] = []
+
+ w.globals.pointsArray[seriesIndex].push([p.x[q], p.y[q]])
+ }
+ }
+ }
+
+ return elPointsWrap
+ }
+
+ getMarkerConfig({
+ cssClass,
+ seriesIndex,
+ dataPointIndex = null,
+ radius = null,
+ size = null,
+ strokeWidth = null,
+ }) {
+ const w = this.w
+ let pStyle = this.getMarkerStyle(seriesIndex)
+ let pSize = size === null ? w.globals.markers.size[seriesIndex] : size
+
+ const m = w.config.markers
+
+ // discrete markers is an option where user can specify a particular marker with different shape, size and color
+
+ if (dataPointIndex !== null && m.discrete.length) {
+ m.discrete.map((marker) => {
+ if (
+ marker.seriesIndex === seriesIndex &&
+ marker.dataPointIndex === dataPointIndex
+ ) {
+ pStyle.pointStrokeColor = marker.strokeColor
+ pStyle.pointFillColor = marker.fillColor
+ pSize = marker.size
+ pStyle.pointShape = marker.shape
+ }
+ })
+ }
+
+ return {
+ pSize: radius === null ? pSize : radius,
+ pRadius: radius !== null ? radius : m.radius,
+ pointStrokeWidth:
+ strokeWidth !== null
+ ? strokeWidth
+ : Array.isArray(m.strokeWidth)
+ ? m.strokeWidth[seriesIndex]
+ : m.strokeWidth,
+ pointStrokeColor: pStyle.pointStrokeColor,
+ pointFillColor: pStyle.pointFillColor,
+ shape:
+ pStyle.pointShape ||
+ (Array.isArray(m.shape) ? m.shape[seriesIndex] : m.shape),
+ class: cssClass,
+ pointStrokeOpacity: Array.isArray(m.strokeOpacity)
+ ? m.strokeOpacity[seriesIndex]
+ : m.strokeOpacity,
+ pointStrokeDashArray: Array.isArray(m.strokeDashArray)
+ ? m.strokeDashArray[seriesIndex]
+ : m.strokeDashArray,
+ pointFillOpacity: Array.isArray(m.fillOpacity)
+ ? m.fillOpacity[seriesIndex]
+ : m.fillOpacity,
+ seriesIndex,
+ }
+ }
+
+ addEvents(marker) {
+ const w = this.w
+
+ const graphics = new Graphics(this.ctx)
+ marker.node.addEventListener(
+ 'mouseenter',
+ graphics.pathMouseEnter.bind(this.ctx, marker)
+ )
+ marker.node.addEventListener(
+ 'mouseleave',
+ graphics.pathMouseLeave.bind(this.ctx, marker)
+ )
+
+ marker.node.addEventListener(
+ 'mousedown',
+ graphics.pathMouseDown.bind(this.ctx, marker)
+ )
+
+ marker.node.addEventListener('click', w.config.markers.onClick)
+ marker.node.addEventListener('dblclick', w.config.markers.onDblClick)
+
+ marker.node.addEventListener(
+ 'touchstart',
+ graphics.pathMouseDown.bind(this.ctx, marker),
+ { passive: true }
+ )
+ }
+
+ getMarkerStyle(seriesIndex) {
+ let w = this.w
+
+ let colors = w.globals.markers.colors
+ let strokeColors =
+ w.config.markers.strokeColor || w.config.markers.strokeColors
+
+ let pointStrokeColor = Array.isArray(strokeColors)
+ ? strokeColors[seriesIndex]
+ : strokeColors
+ let pointFillColor = Array.isArray(colors) ? colors[seriesIndex] : colors
+
+ return {
+ pointStrokeColor,
+ pointFillColor,
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Range.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Range.js
new file mode 100644
index 0000000..2c29663
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Range.js
@@ -0,0 +1,662 @@
+import Utils from '../utils/Utils'
+import DateTime from '../utils/DateTime'
+import Scales from './Scales'
+
+/**
+ * Range is used to generates values between min and max.
+ *
+ * @module Range
+ **/
+
+class Range {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.scales = new Scales(ctx)
+ }
+
+ init() {
+ this.setYRange()
+ this.setXRange()
+ this.setZRange()
+ }
+
+ getMinYMaxY(
+ startingSeriesIndex,
+ lowestY = Number.MAX_VALUE,
+ highestY = -Number.MAX_VALUE,
+ endingSeriesIndex = null
+ ) {
+ const cnf = this.w.config
+ const gl = this.w.globals
+ let maxY = -Number.MAX_VALUE
+ let minY = Number.MIN_VALUE
+
+ if (endingSeriesIndex === null) {
+ endingSeriesIndex = startingSeriesIndex + 1
+ }
+ let series = gl.series
+ let seriesMin = series
+ let seriesMax = series
+
+ if (cnf.chart.type === 'candlestick') {
+ seriesMin = gl.seriesCandleL
+ seriesMax = gl.seriesCandleH
+ } else if (cnf.chart.type === 'boxPlot') {
+ seriesMin = gl.seriesCandleO
+ seriesMax = gl.seriesCandleC
+ } else if (gl.isRangeData) {
+ seriesMin = gl.seriesRangeStart
+ seriesMax = gl.seriesRangeEnd
+ }
+ let autoScaleYaxis = false
+ if (gl.seriesX.length >= endingSeriesIndex) {
+ // Eventually brushSource will be set if the current chart is a target.
+ // That is, after the appropriate event causes us to update.
+ let brush = gl.brushSource?.w.config.chart.brush
+ if (
+ (cnf.chart.zoom.enabled && cnf.chart.zoom.autoScaleYaxis) ||
+ (brush?.enabled && brush?.autoScaleYaxis)
+ ) {
+ autoScaleYaxis = true
+ }
+ }
+
+ for (let i = startingSeriesIndex; i < endingSeriesIndex; i++) {
+ gl.dataPoints = Math.max(gl.dataPoints, series[i].length)
+
+ const seriesType = cnf.series[i].type
+
+ if (gl.categoryLabels.length) {
+ gl.dataPoints = gl.categoryLabels.filter(
+ (label) => typeof label !== 'undefined'
+ ).length
+ }
+
+ if (
+ gl.labels.length &&
+ cnf.xaxis.type !== 'datetime' &&
+ gl.series.reduce((a, c) => a + c.length, 0) !== 0
+ ) {
+ // the condition cnf.xaxis.type !== 'datetime' fixes #3897 and #3905
+ gl.dataPoints = Math.max(gl.dataPoints, gl.labels.length)
+ }
+ let firstXIndex = 0
+ let lastXIndex = series[i].length - 1
+ if (autoScaleYaxis) {
+ // Scale the Y axis to the min..max within the possibly zoomed X axis domain.
+ if (cnf.xaxis.min) {
+ for (
+ ;
+ firstXIndex < lastXIndex &&
+ gl.seriesX[i][firstXIndex] < cnf.xaxis.min;
+ firstXIndex++
+ ) {}
+ }
+ if (cnf.xaxis.max) {
+ for (
+ ;
+ lastXIndex > firstXIndex &&
+ gl.seriesX[i][lastXIndex] > cnf.xaxis.max;
+ lastXIndex--
+ ) {}
+ }
+ }
+ for (
+ let j = firstXIndex;
+ j <= lastXIndex && j < gl.series[i].length;
+ j++
+ ) {
+ let val = series[i][j]
+ if (val !== null && Utils.isNumber(val)) {
+ if (typeof seriesMax[i][j] !== 'undefined') {
+ maxY = Math.max(maxY, seriesMax[i][j])
+ lowestY = Math.min(lowestY, seriesMax[i][j])
+ }
+ if (typeof seriesMin[i][j] !== 'undefined') {
+ lowestY = Math.min(lowestY, seriesMin[i][j])
+ highestY = Math.max(highestY, seriesMin[i][j])
+ }
+
+ // These series arrays are dual purpose:
+ // Array : CandleO, CandleH, CandleM, CandleL, CandleC
+ // Candlestick: O H L C
+ // Boxplot : Min Q1 Median Q3 Max
+ switch (seriesType) {
+ case 'candlestick':
+ {
+ if (typeof gl.seriesCandleC[i][j] !== 'undefined') {
+ maxY = Math.max(maxY, gl.seriesCandleH[i][j])
+ lowestY = Math.min(lowestY, gl.seriesCandleL[i][j])
+ }
+ }
+ break
+ case 'boxPlot':
+ {
+ if (typeof gl.seriesCandleC[i][j] !== 'undefined') {
+ maxY = Math.max(maxY, gl.seriesCandleC[i][j])
+ lowestY = Math.min(lowestY, gl.seriesCandleO[i][j])
+ }
+ }
+ break
+ }
+
+ // there is a combo chart and the specified series in not either
+ // candlestick, boxplot, or rangeArea/rangeBar; find the max there.
+ if (
+ seriesType &&
+ seriesType !== 'candlestick' &&
+ seriesType !== 'boxPlot' &&
+ seriesType !== 'rangeArea' &&
+ seriesType !== 'rangeBar'
+ ) {
+ maxY = Math.max(maxY, gl.series[i][j])
+ lowestY = Math.min(lowestY, gl.series[i][j])
+ }
+ highestY = maxY
+
+ if (
+ gl.seriesGoals[i] &&
+ gl.seriesGoals[i][j] &&
+ Array.isArray(gl.seriesGoals[i][j])
+ ) {
+ gl.seriesGoals[i][j].forEach((g) => {
+ if (minY !== Number.MIN_VALUE) {
+ minY = Math.min(minY, g.value)
+ lowestY = minY
+ }
+ maxY = Math.max(maxY, g.value)
+ highestY = maxY
+ })
+ }
+
+ if (Utils.isFloat(val)) {
+ val = Utils.noExponents(val)
+ gl.yValueDecimal = Math.max(
+ gl.yValueDecimal,
+ val.toString().split('.')[1].length
+ )
+ }
+ if (minY > seriesMin[i][j] && seriesMin[i][j] < 0) {
+ minY = seriesMin[i][j]
+ }
+ } else {
+ gl.hasNullValues = true
+ }
+ }
+ if (seriesType === 'bar' || seriesType === 'column') {
+ if (minY < 0 && maxY < 0) {
+ // all negative values in a bar series, hence make the max to 0
+ maxY = 0
+ highestY = Math.max(highestY, 0)
+ }
+ if (minY === Number.MIN_VALUE) {
+ minY = 0
+ lowestY = Math.min(lowestY, 0)
+ }
+ }
+ }
+
+ if (
+ cnf.chart.type === 'rangeBar' &&
+ gl.seriesRangeStart.length &&
+ gl.isBarHorizontal
+ ) {
+ minY = lowestY
+ }
+
+ if (cnf.chart.type === 'bar') {
+ if (minY < 0 && maxY < 0) {
+ // all negative values in a bar chart, hence make the max to 0
+ maxY = 0
+ }
+ if (minY === Number.MIN_VALUE) {
+ minY = 0
+ }
+ }
+
+ return {
+ minY,
+ maxY,
+ lowestY,
+ highestY,
+ }
+ }
+
+ setYRange() {
+ let gl = this.w.globals
+ let cnf = this.w.config
+ gl.maxY = -Number.MAX_VALUE
+ gl.minY = Number.MIN_VALUE
+
+ let lowestYInAllSeries = Number.MAX_VALUE
+ let minYMaxY
+
+ if (gl.isMultipleYAxis) {
+ // we need to get minY and maxY for multiple y axis
+ lowestYInAllSeries = Number.MAX_VALUE
+ for (let i = 0; i < gl.series.length; i++) {
+ minYMaxY = this.getMinYMaxY(i)
+ gl.minYArr[i] = minYMaxY.lowestY
+ gl.maxYArr[i] = minYMaxY.highestY
+ lowestYInAllSeries = Math.min(lowestYInAllSeries, minYMaxY.lowestY)
+ }
+ }
+
+ // and then, get the minY and maxY from all series
+ minYMaxY = this.getMinYMaxY(0, lowestYInAllSeries, null, gl.series.length)
+ if (cnf.chart.type === 'bar') {
+ gl.minY = minYMaxY.minY
+ gl.maxY = minYMaxY.maxY
+ } else {
+ gl.minY = minYMaxY.lowestY
+ gl.maxY = minYMaxY.highestY
+ }
+ lowestYInAllSeries = minYMaxY.lowestY
+
+ if (cnf.chart.stacked) {
+ this._setStackedMinMax()
+ }
+
+ // if the numbers are too big, reduce the range
+ // for eg, if number is between 100000-110000, putting 0 as the lowest
+ // value is not so good idea. So change the gl.minY for
+ // line/area/scatter/candlesticks/boxPlot/vertical rangebar
+ if (
+ cnf.chart.type === 'line' ||
+ cnf.chart.type === 'area' ||
+ cnf.chart.type === 'scatter' ||
+ cnf.chart.type === 'candlestick' ||
+ cnf.chart.type === 'boxPlot' ||
+ (cnf.chart.type === 'rangeBar' && !gl.isBarHorizontal)
+ ) {
+ if (
+ gl.minY === Number.MIN_VALUE &&
+ lowestYInAllSeries !== -Number.MAX_VALUE &&
+ lowestYInAllSeries !== gl.maxY // single value possibility
+ ) {
+ gl.minY = lowestYInAllSeries
+ }
+ } else {
+ gl.minY =
+ gl.minY !== Number.MIN_VALUE
+ ? Math.min(minYMaxY.minY, gl.minY)
+ : minYMaxY.minY
+ }
+
+ cnf.yaxis.forEach((yaxe, index) => {
+ // override all min/max values by user defined values (y axis)
+ if (yaxe.max !== undefined) {
+ if (typeof yaxe.max === 'number') {
+ gl.maxYArr[index] = yaxe.max
+ } else if (typeof yaxe.max === 'function') {
+ // fixes apexcharts.js/issues/2098
+ gl.maxYArr[index] = yaxe.max(
+ gl.isMultipleYAxis ? gl.maxYArr[index] : gl.maxY
+ )
+ }
+
+ // gl.maxY is for single y-axis chart, it will be ignored in multi-yaxis
+ gl.maxY = gl.maxYArr[index]
+ }
+ if (yaxe.min !== undefined) {
+ if (typeof yaxe.min === 'number') {
+ gl.minYArr[index] = yaxe.min
+ } else if (typeof yaxe.min === 'function') {
+ // fixes apexcharts.js/issues/2098
+ gl.minYArr[index] = yaxe.min(
+ gl.isMultipleYAxis
+ ? gl.minYArr[index] === Number.MIN_VALUE
+ ? 0
+ : gl.minYArr[index]
+ : gl.minY
+ )
+ }
+ // gl.minY is for single y-axis chart, it will be ignored in multi-yaxis
+ gl.minY = gl.minYArr[index]
+ }
+ })
+
+ // for horizontal bar charts, we need to check xaxis min/max as user may have specified there
+ if (gl.isBarHorizontal) {
+ const minmax = ['min', 'max']
+ minmax.forEach((m) => {
+ if (cnf.xaxis[m] !== undefined && typeof cnf.xaxis[m] === 'number') {
+ m === 'min' ? (gl.minY = cnf.xaxis[m]) : (gl.maxY = cnf.xaxis[m])
+ }
+ })
+ }
+
+ if (gl.isMultipleYAxis) {
+ this.scales.scaleMultipleYAxes()
+ gl.minY = lowestYInAllSeries
+ } else {
+ this.scales.setYScaleForIndex(0, gl.minY, gl.maxY)
+ gl.minY = gl.yAxisScale[0].niceMin
+ gl.maxY = gl.yAxisScale[0].niceMax
+ gl.minYArr[0] = gl.minY
+ gl.maxYArr[0] = gl.maxY
+ }
+
+ gl.barGroups = []
+ gl.lineGroups = []
+ gl.areaGroups = []
+ cnf.series.forEach((s) => {
+ let type = s.type || cnf.chart.type
+ switch (type) {
+ case 'bar':
+ case 'column':
+ gl.barGroups.push(s.group)
+ break
+ case 'line':
+ gl.lineGroups.push(s.group)
+ break
+ case 'area':
+ gl.areaGroups.push(s.group)
+ break
+ }
+ })
+ // Uniquify the group names in each stackable chart type.
+ gl.barGroups = gl.barGroups.filter((v, i, a) => a.indexOf(v) === i)
+ gl.lineGroups = gl.lineGroups.filter((v, i, a) => a.indexOf(v) === i)
+ gl.areaGroups = gl.areaGroups.filter((v, i, a) => a.indexOf(v) === i)
+
+ return {
+ minY: gl.minY,
+ maxY: gl.maxY,
+ minYArr: gl.minYArr,
+ maxYArr: gl.maxYArr,
+ yAxisScale: gl.yAxisScale,
+ }
+ }
+
+ setXRange() {
+ let gl = this.w.globals
+ let cnf = this.w.config
+
+ const isXNumeric =
+ cnf.xaxis.type === 'numeric' ||
+ cnf.xaxis.type === 'datetime' ||
+ (cnf.xaxis.type === 'category' && !gl.noLabelsProvided) ||
+ gl.noLabelsProvided ||
+ gl.isXNumeric
+
+ const getInitialMinXMaxX = () => {
+ for (let i = 0; i < gl.series.length; i++) {
+ if (gl.labels[i]) {
+ for (let j = 0; j < gl.labels[i].length; j++) {
+ if (gl.labels[i][j] !== null && Utils.isNumber(gl.labels[i][j])) {
+ gl.maxX = Math.max(gl.maxX, gl.labels[i][j])
+ gl.initialMaxX = Math.max(gl.maxX, gl.labels[i][j])
+ gl.minX = Math.min(gl.minX, gl.labels[i][j])
+ gl.initialMinX = Math.min(gl.minX, gl.labels[i][j])
+ }
+ }
+ }
+ }
+ }
+ // minX maxX starts here
+ if (gl.isXNumeric) {
+ getInitialMinXMaxX()
+ }
+
+ if (gl.noLabelsProvided) {
+ if (cnf.xaxis.categories.length === 0) {
+ gl.maxX = gl.labels[gl.labels.length - 1]
+ gl.initialMaxX = gl.labels[gl.labels.length - 1]
+ gl.minX = 1
+ gl.initialMinX = 1
+ }
+ }
+
+ if (gl.isXNumeric || gl.noLabelsProvided || gl.dataFormatXNumeric) {
+ let ticks = 10
+
+ if (cnf.xaxis.tickAmount === undefined) {
+ ticks = Math.round(gl.svgWidth / 150)
+
+ // no labels provided and total number of dataPoints is less than 30
+ if (cnf.xaxis.type === 'numeric' && gl.dataPoints < 30) {
+ ticks = gl.dataPoints - 1
+ }
+
+ // this check is for when ticks exceeds total datapoints and that would result in duplicate labels
+ if (ticks > gl.dataPoints && gl.dataPoints !== 0) {
+ ticks = gl.dataPoints - 1
+ }
+ } else if (cnf.xaxis.tickAmount === 'dataPoints') {
+ if (gl.series.length > 1) {
+ ticks = gl.series[gl.maxValsInArrayIndex].length - 1
+ }
+ if (gl.isXNumeric) {
+ const diff = gl.maxX - gl.minX
+ if (diff < 30) {
+ ticks = diff - 1
+ }
+ }
+ } else {
+ ticks = cnf.xaxis.tickAmount
+ }
+ gl.xTickAmount = ticks
+
+ // override all min/max values by user defined values (x axis)
+ if (cnf.xaxis.max !== undefined && typeof cnf.xaxis.max === 'number') {
+ gl.maxX = cnf.xaxis.max
+ }
+ if (cnf.xaxis.min !== undefined && typeof cnf.xaxis.min === 'number') {
+ gl.minX = cnf.xaxis.min
+ }
+
+ // if range is provided, adjust the new minX
+ if (cnf.xaxis.range !== undefined) {
+ gl.minX = gl.maxX - cnf.xaxis.range
+ }
+
+ if (gl.minX !== Number.MAX_VALUE && gl.maxX !== -Number.MAX_VALUE) {
+ if (cnf.xaxis.convertedCatToNumeric && !gl.dataFormatXNumeric) {
+ let catScale = []
+ for (let i = gl.minX - 1; i < gl.maxX; i++) {
+ catScale.push(i + 1)
+ }
+ gl.xAxisScale = {
+ result: catScale,
+ niceMin: catScale[0],
+ niceMax: catScale[catScale.length - 1],
+ }
+ } else {
+ gl.xAxisScale = this.scales.setXScale(gl.minX, gl.maxX)
+ }
+ } else {
+ gl.xAxisScale = this.scales.linearScale(
+ 0,
+ ticks,
+ ticks,
+ 0,
+ cnf.xaxis.stepSize
+ )
+ if (gl.noLabelsProvided && gl.labels.length > 0) {
+ gl.xAxisScale = this.scales.linearScale(
+ 1,
+ gl.labels.length,
+ ticks - 1,
+ 0,
+ cnf.xaxis.stepSize
+ )
+
+ // this is the only place seriesX is again mutated
+ gl.seriesX = gl.labels.slice()
+ }
+ }
+ // we will still store these labels as the count for this will be different (to draw grid and labels placement)
+ if (isXNumeric) {
+ gl.labels = gl.xAxisScale.result.slice()
+ }
+ }
+
+ if (gl.isBarHorizontal && gl.labels.length) {
+ gl.xTickAmount = gl.labels.length
+ }
+
+ // single dataPoint
+ this._handleSingleDataPoint()
+
+ // minimum x difference to calculate bar width in numeric bars
+ this._getMinXDiff()
+
+ return {
+ minX: gl.minX,
+ maxX: gl.maxX,
+ }
+ }
+
+ setZRange() {
+ // minZ, maxZ starts here
+ let gl = this.w.globals
+
+ if (!gl.isDataXYZ) return
+ for (let i = 0; i < gl.series.length; i++) {
+ if (typeof gl.seriesZ[i] !== 'undefined') {
+ for (let j = 0; j < gl.seriesZ[i].length; j++) {
+ if (gl.seriesZ[i][j] !== null && Utils.isNumber(gl.seriesZ[i][j])) {
+ gl.maxZ = Math.max(gl.maxZ, gl.seriesZ[i][j])
+ gl.minZ = Math.min(gl.minZ, gl.seriesZ[i][j])
+ }
+ }
+ }
+ }
+ }
+
+ _handleSingleDataPoint() {
+ const gl = this.w.globals
+ const cnf = this.w.config
+
+ if (gl.minX === gl.maxX) {
+ let datetimeObj = new DateTime(this.ctx)
+
+ if (cnf.xaxis.type === 'datetime') {
+ const newMinX = datetimeObj.getDate(gl.minX)
+ if (cnf.xaxis.labels.datetimeUTC) {
+ newMinX.setUTCDate(newMinX.getUTCDate() - 2)
+ } else {
+ newMinX.setDate(newMinX.getDate() - 2)
+ }
+
+ gl.minX = new Date(newMinX).getTime()
+
+ const newMaxX = datetimeObj.getDate(gl.maxX)
+ if (cnf.xaxis.labels.datetimeUTC) {
+ newMaxX.setUTCDate(newMaxX.getUTCDate() + 2)
+ } else {
+ newMaxX.setDate(newMaxX.getDate() + 2)
+ }
+ gl.maxX = new Date(newMaxX).getTime()
+ } else if (
+ cnf.xaxis.type === 'numeric' ||
+ (cnf.xaxis.type === 'category' && !gl.noLabelsProvided)
+ ) {
+ gl.minX = gl.minX - 2
+ gl.initialMinX = gl.minX
+ gl.maxX = gl.maxX + 2
+ gl.initialMaxX = gl.maxX
+ }
+ }
+ }
+
+ _getMinXDiff() {
+ const gl = this.w.globals
+
+ if (gl.isXNumeric) {
+ // get the least x diff if numeric x axis is present
+ gl.seriesX.forEach((sX, i) => {
+ if (sX.length === 1) {
+ // a small hack to prevent overlapping multiple bars when there is just 1 datapoint in bar series.
+ // fix #811
+ sX.push(
+ gl.seriesX[gl.maxValsInArrayIndex][
+ gl.seriesX[gl.maxValsInArrayIndex].length - 1
+ ]
+ )
+ }
+
+ // fix #983 (clone the array to avoid side effects)
+ const seriesX = sX.slice()
+ seriesX.sort((a, b) => a - b)
+
+ seriesX.forEach((s, j) => {
+ if (j > 0) {
+ let xDiff = s - seriesX[j - 1]
+ if (xDiff > 0) {
+ gl.minXDiff = Math.min(xDiff, gl.minXDiff)
+ }
+ }
+ })
+ if (gl.dataPoints === 1 || gl.minXDiff === Number.MAX_VALUE) {
+ // fixes apexcharts.js #1221
+ gl.minXDiff = 0.5
+ }
+ })
+ }
+ }
+
+ _setStackedMinMax() {
+ const gl = this.w.globals
+ // for stacked charts, we calculate each series's parallel values.
+ // i.e, series[0][j] + series[1][j] .... [series[i.length][j]]
+ // and get the max out of it
+
+ if (!gl.series.length) return
+ let seriesGroups = gl.seriesGroups
+
+ if (!seriesGroups.length) {
+ seriesGroups = [this.w.globals.seriesNames.map((name) => name)]
+ }
+ let stackedPoss = {}
+ let stackedNegs = {}
+
+ seriesGroups.forEach((group) => {
+ stackedPoss[group] = []
+ stackedNegs[group] = []
+ const indicesOfSeriesInGroup = this.w.config.series
+ .map((serie, si) =>
+ group.indexOf(gl.seriesNames[si]) > -1 ? si : null
+ )
+ .filter((f) => f !== null)
+
+ indicesOfSeriesInGroup.forEach((i) => {
+ for (let j = 0; j < gl.series[gl.maxValsInArrayIndex].length; j++) {
+ if (typeof stackedPoss[group][j] === 'undefined') {
+ stackedPoss[group][j] = 0
+ stackedNegs[group][j] = 0
+ }
+
+ let stackSeries =
+ (this.w.config.chart.stacked && !gl.comboCharts) ||
+ (this.w.config.chart.stacked &&
+ gl.comboCharts &&
+ (!this.w.config.chart.stackOnlyBar ||
+ this.w.config.series?.[i]?.type === 'bar' ||
+ this.w.config.series?.[i]?.type === 'column'))
+
+ if (stackSeries) {
+ if (gl.series[i][j] !== null && Utils.isNumber(gl.series[i][j])) {
+ gl.series[i][j] > 0
+ ? (stackedPoss[group][j] +=
+ parseFloat(gl.series[i][j]) + 0.0001)
+ : (stackedNegs[group][j] += parseFloat(gl.series[i][j]))
+ }
+ }
+ }
+ })
+ })
+
+ Object.entries(stackedPoss).forEach(([key]) => {
+ stackedPoss[key].forEach((_, stgi) => {
+ gl.maxY = Math.max(gl.maxY, stackedPoss[key][stgi])
+ gl.minY = Math.min(gl.minY, stackedNegs[key][stgi])
+ })
+ })
+ }
+}
+
+export default Range
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Responsive.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Responsive.js
new file mode 100644
index 0000000..5311f05
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Responsive.js
@@ -0,0 +1,78 @@
+import Config from './settings/Config'
+import Utils from '../utils/Utils'
+import CoreUtils from './CoreUtils'
+
+/**
+ * ApexCharts Responsive Class to override options for different screen sizes.
+ *
+ * @module Responsive
+ **/
+
+export default class Responsive {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ // the opts parameter if not null has to be set overriding everything
+ // as the opts is set by user externally
+ checkResponsiveConfig(opts) {
+ const w = this.w
+ const cnf = w.config
+
+ // check if responsive config exists
+ if (cnf.responsive.length === 0) return
+
+ let res = cnf.responsive.slice()
+ res
+ .sort((a, b) =>
+ a.breakpoint > b.breakpoint ? 1 : b.breakpoint > a.breakpoint ? -1 : 0
+ )
+ .reverse()
+
+ let config = new Config({})
+
+ const iterateResponsiveOptions = (newOptions = {}) => {
+ let largestBreakpoint = res[0].breakpoint
+ const width = window.innerWidth > 0 ? window.innerWidth : screen.width
+
+ if (width > largestBreakpoint) {
+ let initialConfig = Utils.clone(w.globals.initialConfig)
+ // Retain state of series in case any have been collapsed
+ // (indicated by series.data === [], these series' will be zeroed later
+ // enabling stacking to work correctly)
+ initialConfig.series = Utils.clone(w.config.series)
+ let options = CoreUtils.extendArrayProps(
+ config,
+ initialConfig,
+ w
+ )
+ newOptions = Utils.extend(options, newOptions)
+ newOptions = Utils.extend(w.config, newOptions)
+ this.overrideResponsiveOptions(newOptions)
+ } else {
+ for (let i = 0; i < res.length; i++) {
+ if (width < res[i].breakpoint) {
+ newOptions = CoreUtils.extendArrayProps(config, res[i].options, w)
+ newOptions = Utils.extend(w.config, newOptions)
+ this.overrideResponsiveOptions(newOptions)
+ }
+ }
+ }
+ }
+
+ if (opts) {
+ let options = CoreUtils.extendArrayProps(config, opts, w)
+ options = Utils.extend(w.config, options)
+ options = Utils.extend(options, opts)
+ iterateResponsiveOptions(options)
+ } else {
+ iterateResponsiveOptions({})
+ }
+ }
+
+ overrideResponsiveOptions(newOptions) {
+ let newConfig = new Config(newOptions).init({ responsiveOverride: true })
+ this.w.config = newConfig
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Scales.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Scales.js
new file mode 100644
index 0000000..27bb0f5
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Scales.js
@@ -0,0 +1,754 @@
+import CoreUtils from './CoreUtils'
+import Utils from '../utils/Utils'
+
+export default class Scales {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ this.coreUtils = new CoreUtils(this.ctx)
+ }
+
+ // http://stackoverflow.com/questions/326679/choosing-an-attractive-linear-scale-for-a-graphs-y-axis
+ // This routine creates the Y axis values for a graph.
+ niceScale(yMin, yMax, index = 0) {
+ // Calculate Min amd Max graphical labels and graph
+ // increments.
+ //
+ // Output will be an array of the Y axis values that
+ // encompass the Y values.
+ const jsPrecision = 1e-11 // JS precision errors
+ const w = this.w
+ const gl = w.globals
+ let axisCnf
+ let maxTicks
+ let gotMin
+ let gotMax
+ if (gl.isBarHorizontal) {
+ axisCnf = w.config.xaxis
+ // The most ticks we can fit into the svg chart dimensions
+ maxTicks = Math.max((gl.svgWidth - 100) / 25, 2) // Guestimate
+ } else {
+ axisCnf = w.config.yaxis[index]
+ maxTicks = Math.max((gl.svgHeight - 100) / 15, 2)
+ }
+ if (!Utils.isNumber(maxTicks)) {
+ maxTicks = 10
+ }
+ gotMin = axisCnf.min !== undefined && axisCnf.min !== null
+ gotMax = axisCnf.max !== undefined && axisCnf.min !== null
+ let gotStepSize =
+ axisCnf.stepSize !== undefined && axisCnf.stepSize !== null
+ let gotTickAmount =
+ axisCnf.tickAmount !== undefined && axisCnf.tickAmount !== null
+ let ticks = gotTickAmount
+ ? axisCnf.tickAmount
+ : gl.niceScaleDefaultTicks[
+ Math.min(
+ Math.round(maxTicks / 2),
+ gl.niceScaleDefaultTicks.length - 1
+ )
+ ]
+
+ // In case we have a multi axis chart:
+ // Ensure subsequent series start with the same tickAmount as series[0],
+ // because the tick lines are drawn based on series[0]. This does not
+ // override user defined options for any yaxis.
+ if (gl.isMultipleYAxis && !gotTickAmount && gl.multiAxisTickAmount > 0) {
+ ticks = gl.multiAxisTickAmount
+ gotTickAmount = true
+ }
+
+ if (ticks === 'dataPoints') {
+ ticks = gl.dataPoints - 1
+ } else {
+ // Ensure ticks is an integer
+ ticks = Math.abs(Math.round(ticks))
+ }
+
+ if (
+ (yMin === Number.MIN_VALUE && yMax === 0) ||
+ (!Utils.isNumber(yMin) && !Utils.isNumber(yMax)) ||
+ (yMin === Number.MIN_VALUE && yMax === -Number.MAX_VALUE)
+ ) {
+ // when all values are 0
+ yMin = Utils.isNumber(axisCnf.min) ? axisCnf.min : 0
+ yMax = Utils.isNumber(axisCnf.max) ? axisCnf.max : yMin + ticks
+ gl.allSeriesCollapsed = false
+ }
+
+ if (yMin > yMax) {
+ // if somehow due to some wrong config, user sent max less than min,
+ // adjust the min/max again
+ console.warn(
+ 'axis.min cannot be greater than axis.max: swapping min and max'
+ )
+ let temp = yMax
+ yMax = yMin
+ yMin = temp
+ } else if (yMin === yMax) {
+ // If yMin and yMax are identical, then
+ // adjust the yMin and yMax values to actually
+ // make a graph. Also avoids division by zero errors.
+ yMin = yMin === 0 ? 0 : yMin - 1 // choose an integer in case yValueDecimals=0
+ yMax = yMax === 0 ? 2 : yMax + 1 // choose an integer in case yValueDecimals=0
+ }
+
+ let result = []
+
+ if (ticks < 1) {
+ ticks = 1
+ }
+ let tiks = ticks
+
+ // Determine Range
+ let range = Math.abs(yMax - yMin)
+
+ // Snap min or max to zero if close
+ let proximityRatio = 0.15
+ if (!gotMin && yMin > 0 && yMin / range < proximityRatio) {
+ yMin = 0
+ gotMin = true
+ }
+ if (!gotMax && yMax < 0 && -yMax / range < proximityRatio) {
+ yMax = 0
+ gotMax = true
+ }
+ range = Math.abs(yMax - yMin)
+
+ // Calculate a pretty step value based on ticks
+
+ // Initial stepSize
+ let stepSize = range / tiks
+ let niceStep = stepSize
+ let mag = Math.floor(Math.log10(niceStep))
+ let magPow = Math.pow(10, mag)
+ // ceil() is used below in conjunction with the values populating
+ // niceScaleAllowedMagMsd[][] to ensure that (niceStep * tiks)
+ // produces a range that doesn't clip data points after stretching
+ // the raw range out a little to match the prospective new range.
+ let magMsd = Math.ceil(niceStep / magPow)
+ // See globals.js for info on what niceScaleAllowedMagMsd does
+ magMsd = gl.niceScaleAllowedMagMsd[gl.yValueDecimal === 0 ? 0 : 1][magMsd]
+ niceStep = magMsd * magPow
+
+ // Initial stepSize
+ stepSize = niceStep
+
+ // Get step value
+ if (gl.isBarHorizontal && axisCnf.stepSize && axisCnf.type !== 'datetime') {
+ stepSize = axisCnf.stepSize
+ gotStepSize = true
+ } else if (gotStepSize) {
+ stepSize = axisCnf.stepSize
+ }
+ if (gotStepSize) {
+ if (axisCnf.forceNiceScale) {
+ // Check that given stepSize is sane with respect to the range.
+ //
+ // The user can, by setting forceNiceScale = true,
+ // define a stepSize that will be scaled to a useful value before
+ // it's checked for consistency.
+ //
+ // If, for example, the range = 4 and the user defined stepSize = 8
+ // (or 8000 or 0.0008, etc), then stepSize is inapplicable as
+ // it is. Reducing it to 0.8 will fit with 5 ticks.
+ //
+ let stepMag = Math.floor(Math.log10(stepSize))
+ stepSize *= Math.pow(10, mag - stepMag)
+ }
+ }
+
+ // Start applying some rules
+ if (gotMin && gotMax) {
+ let crudeStep = range / tiks
+ // min and max (range) cannot be changed
+ if (gotTickAmount) {
+ if (gotStepSize) {
+ if (Utils.mod(range, stepSize) != 0) {
+ // stepSize conflicts with range
+ let gcdStep = Utils.getGCD(stepSize, crudeStep)
+ // gcdStep is a multiple of range because crudeStep is a multiple.
+ // gcdStep is also a multiple of stepSize, so it partially honoured
+ // All three could be equal, which would be very nice
+ // if the computed stepSize generates too many ticks they will be
+ // reduced later, unless the number is prime, in which case,
+ // the chart will display all of them or just one (plus the X axis)
+ // depending on svg dimensions. Setting forceNiceScale: true will force
+ // the display of at least the default number of ticks.
+ if (crudeStep / gcdStep < 10) {
+ stepSize = gcdStep
+ } else {
+ // stepSize conflicts and no reasonable adjustment, but must
+ // honour tickAmount
+ stepSize = crudeStep
+ }
+ } else {
+ // stepSize fits
+ if (Utils.mod(stepSize, crudeStep) == 0) {
+ // crudeStep is a multiple of stepSize, or vice versa
+ // but we know that crudeStep will generate tickAmount ticks
+ stepSize = crudeStep
+ } else {
+ // stepSize conflicts with tickAmount
+ // if the user is setting up a multi-axis chart and wants
+ // synced axis ticks then they should not define stepSize
+ // or ensure there is no conflict between any of their options
+ // on any axis.
+ crudeStep = stepSize
+ // De-prioritizing ticks from now on
+ gotTickAmount = false
+ }
+ }
+ } else {
+ // no user stepSize, honour tickAmount
+ stepSize = crudeStep
+ }
+ } else {
+ // default ticks in use, tiks can change
+ if (gotStepSize) {
+ if (Utils.mod(range, stepSize) == 0) {
+ // user stepSize fits
+ crudeStep = stepSize
+ } else {
+ stepSize = crudeStep
+ }
+ } else {
+ // no user stepSize
+ if (Utils.mod(range, stepSize) == 0) {
+ // generated nice stepSize fits
+ crudeStep = stepSize
+ } else {
+ tiks = Math.ceil(range / stepSize)
+ crudeStep = range / tiks
+ let gcdStep = Utils.getGCD(range, stepSize)
+ if (range / gcdStep < maxTicks) {
+ crudeStep = gcdStep
+ }
+ stepSize = crudeStep
+ }
+ }
+ }
+ tiks = Math.round(range / stepSize)
+ } else {
+ // Snap range to ticks
+ if (!gotMin && !gotMax) {
+ if (gl.isMultipleYAxis && gotTickAmount) {
+ // Ensure graph doesn't clip.
+ let tMin = stepSize * Math.floor(yMin / stepSize)
+ let tMax = tMin + stepSize * tiks
+ if (tMax < yMax) {
+ stepSize *= 2
+ }
+ yMin = tMin
+ tMax = yMax
+ yMax = yMin + stepSize * tiks
+ // Snap min or max to zero if possible
+ range = Math.abs(yMax - yMin)
+ if (yMin > 0 && yMin < Math.abs(tMax - yMax)) {
+ yMin = 0
+ yMax = stepSize * tiks
+ }
+ if (yMax < 0 && -yMax < Math.abs(tMin - yMin)) {
+ yMax = 0
+ yMin = -stepSize * tiks
+ }
+ } else {
+ yMin = stepSize * Math.floor(yMin / stepSize)
+ yMax = stepSize * Math.ceil(yMax / stepSize)
+ }
+ } else if (gotMax) {
+ if (gotTickAmount) {
+ yMin = yMax - stepSize * tiks
+ } else {
+ let yMinPrev = yMin
+ yMin = stepSize * Math.floor(yMin / stepSize)
+ if (
+ Math.abs(yMax - yMin) / Utils.getGCD(range, stepSize) >
+ maxTicks
+ ) {
+ // Use default ticks to compute yMin then shrinkwrap
+ yMin = yMax - stepSize * ticks
+ yMin += stepSize * Math.floor((yMinPrev - yMin) / stepSize)
+ }
+ }
+ } else if (gotMin) {
+ if (gotTickAmount) {
+ yMax = yMin + stepSize * tiks
+ } else {
+ let yMaxPrev = yMax
+ yMax = stepSize * Math.ceil(yMax / stepSize)
+ if (
+ Math.abs(yMax - yMin) / Utils.getGCD(range, stepSize) >
+ maxTicks
+ ) {
+ // Use default ticks to compute yMin then shrinkwrap
+ yMax = yMin + stepSize * ticks
+ yMax += stepSize * Math.ceil((yMaxPrev - yMax) / stepSize)
+ }
+ }
+ }
+ range = Math.abs(yMax - yMin)
+ // Final check and possible adjustment of stepSize to prevent
+ // overriding the user's min or max choice.
+ stepSize = Utils.getGCD(range, stepSize)
+ tiks = Math.round(range / stepSize)
+ }
+
+ // Shrinkwrap ticks to the range
+ if (!gotTickAmount && !(gotMin || gotMax)) {
+ tiks = Math.ceil((range - jsPrecision) / (stepSize + jsPrecision))
+ // No user tickAmount, or min or max, we are free to adjust to avoid a
+ // prime number. This helps when reducing ticks for small svg dimensions.
+ if (tiks > 16 && Utils.getPrimeFactors(tiks).length < 2) {
+ tiks++
+ }
+ }
+
+ // Prune tiks down to range if series is all integers. Since tiks > range,
+ // range is very low (< 10 or so). Skip this step if gotTickAmount is true
+ // because either the user set tickAmount or the chart is multiscale and
+ // this axis is not determining the number of grid lines.
+ if (
+ !gotTickAmount &&
+ axisCnf.forceNiceScale &&
+ gl.yValueDecimal === 0 &&
+ tiks > range
+ ) {
+ tiks = range
+ stepSize = Math.round(range / tiks)
+ }
+
+ if (
+ tiks > maxTicks &&
+ (!(gotTickAmount || gotStepSize) || axisCnf.forceNiceScale)
+ ) {
+ // Reduce the number of ticks nicely if chart svg dimensions shrink too far.
+ // The reduced tick set should always be a subset of the full set.
+ //
+ // This following products of prime factors method works as follows:
+ // We compute the prime factors of the full tick count (tiks), then all the
+ // possible products of those factors in order from smallest to biggest,
+ // until we find a product P such that: tiks/P < maxTicks.
+ //
+ // Example:
+ // Computing products of the prime factors of 30.
+ //
+ // tiks | pf | 1 2 3 4 5 6 <-- compute order
+ // --------------------------------------------------
+ // 30 | 5 | 5 5 5 <-- Multiply all
+ // | 3 | 3 3 3 3 <-- primes in each
+ // | 2 | 2 2 2 <-- column = P
+ // --------------------------------------------------
+ // 15 10 6 5 2 1 <-- tiks/P
+ //
+ // tiks = 30 has prime factors [2, 3, 5]
+ // The loop below computes the products [2,3,5,6,15,30].
+ // The last product of P = 2*3*5 is skipped since 30/P = 1.
+ // This yields tiks/P = [15,10,6,5,2,1], checked in order until
+ // tiks/P < maxTicks.
+ //
+ // Pros:
+ // 1) The ticks in the reduced set are always members of the
+ // full set of ticks.
+ // Cons:
+ // 1) None: if tiks is prime, we get all or one, nothing between, so
+ // the worst case is to display all, which is the status quo. Really
+ // only a problem visually for larger tick numbers, say, > 7.
+ //
+ let pf = Utils.getPrimeFactors(tiks)
+ let last = pf.length - 1
+ let tt = tiks
+ reduceLoop: for (var xFactors = 0; xFactors < last; xFactors++) {
+ for (var lowest = 0; lowest <= last - xFactors; lowest++) {
+ let stop = Math.min(lowest + xFactors, last)
+ let t = tt
+ let div = 1
+ for (var next = lowest; next <= stop; next++) {
+ div *= pf[next]
+ }
+ t /= div
+ if (t < maxTicks) {
+ tt = t
+ break reduceLoop
+ }
+ }
+ }
+ if (tt === tiks) {
+ // Could not reduce ticks at all, go all in and display just the
+ // X axis and one tick.
+ stepSize = range
+ } else {
+ stepSize = range / tt
+ }
+ tiks = Math.round(range / stepSize)
+ }
+
+ // Record final tiks for use by other series that call niceScale().
+ // Note: some don't, like logarithmicScale(), etc.
+ if (
+ gl.isMultipleYAxis &&
+ gl.multiAxisTickAmount == 0 &&
+ gl.ignoreYAxisIndexes.indexOf(index) < 0
+ ) {
+ gl.multiAxisTickAmount = tiks
+ }
+
+ // build Y label array.
+
+ let val = yMin - stepSize
+ // Ensure we don't under/over shoot due to JS precision errors.
+ // This also fixes (amongst others):
+ // https://github.com/apexcharts/apexcharts.js/issues/430
+ let err = stepSize * jsPrecision
+ do {
+ val += stepSize
+ result.push(Utils.stripNumber(val, 7))
+ } while (yMax - val > err)
+
+ return {
+ result,
+ niceMin: result[0],
+ niceMax: result[result.length - 1],
+ }
+ }
+
+ linearScale(yMin, yMax, ticks = 10, index = 0, step = undefined) {
+ let range = Math.abs(yMax - yMin)
+ let result = []
+
+ if (yMin === yMax) {
+ result = [yMin]
+
+ return {
+ result,
+ niceMin: result[0],
+ niceMax: result[result.length - 1],
+ }
+ }
+
+ ticks = this._adjustTicksForSmallRange(ticks, index, range)
+
+ if (ticks === 'dataPoints') {
+ ticks = this.w.globals.dataPoints - 1
+ }
+
+ if (!step) {
+ step = range / ticks
+ }
+
+ step = Math.round((step + Number.EPSILON) * 10) / 10
+
+ if (ticks === Number.MAX_VALUE) {
+ ticks = 5
+ step = 1
+ }
+
+ let v = yMin
+
+ while (ticks >= 0) {
+ result.push(v)
+ v = Utils.preciseAddition(v, step)
+ ticks -= 1
+ }
+
+ return {
+ result,
+ niceMin: result[0],
+ niceMax: result[result.length - 1],
+ }
+ }
+
+ logarithmicScaleNice(yMin, yMax, base) {
+ // Basic validation to avoid for loop starting at -inf.
+ if (yMax <= 0) yMax = Math.max(yMin, base)
+ if (yMin <= 0) yMin = Math.min(yMax, base)
+
+ const logs = []
+
+ // Get powers of base for our max and min
+ const logMax = Math.ceil(Math.log(yMax) / Math.log(base) + 1)
+ const logMin = Math.floor(Math.log(yMin) / Math.log(base))
+
+ for (let i = logMin; i < logMax; i++) {
+ logs.push(Math.pow(base, i))
+ }
+
+ return {
+ result: logs,
+ niceMin: logs[0],
+ niceMax: logs[logs.length - 1],
+ }
+ }
+
+ logarithmicScale(yMin, yMax, base) {
+ // Basic validation to avoid for loop starting at -inf.
+ if (yMax <= 0) yMax = Math.max(yMin, base)
+ if (yMin <= 0) yMin = Math.min(yMax, base)
+
+ const logs = []
+
+ // Get the logarithmic range.
+ const logMax = Math.log(yMax) / Math.log(base)
+ const logMin = Math.log(yMin) / Math.log(base)
+
+ // Get the exact logarithmic range.
+ // (This is the exact number of multiples of the base there are between yMin and yMax).
+ const logRange = logMax - logMin
+
+ // Round the logarithmic range to get the number of ticks we will create.
+ // If the chosen min/max values are multiples of each other WRT the base, this will be neat.
+ // If the chosen min/max aren't, we will at least still provide USEFUL ticks.
+ const ticks = Math.round(logRange)
+
+ // Get the logarithmic spacing between ticks.
+ const logTickSpacing = logRange / ticks
+
+ // Create as many ticks as there is range in the logs.
+ for (
+ let i = 0, logTick = logMin;
+ i < ticks;
+ i++, logTick += logTickSpacing
+ ) {
+ logs.push(Math.pow(base, logTick))
+ }
+
+ // Add a final tick at the yMax.
+ logs.push(Math.pow(base, logMax))
+
+ return {
+ result: logs,
+ niceMin: yMin,
+ niceMax: yMax,
+ }
+ }
+
+ _adjustTicksForSmallRange(ticks, index, range) {
+ let newTicks = ticks
+ if (
+ typeof index !== 'undefined' &&
+ this.w.config.yaxis[index].labels.formatter &&
+ this.w.config.yaxis[index].tickAmount === undefined
+ ) {
+ const formattedVal = Number(
+ this.w.config.yaxis[index].labels.formatter(1)
+ )
+ if (Utils.isNumber(formattedVal) && this.w.globals.yValueDecimal === 0) {
+ newTicks = Math.ceil(range)
+ }
+ }
+ return newTicks < ticks ? newTicks : ticks
+ }
+
+ setYScaleForIndex(index, minY, maxY) {
+ const gl = this.w.globals
+ const cnf = this.w.config
+
+ let y = gl.isBarHorizontal ? cnf.xaxis : cnf.yaxis[index]
+
+ if (typeof gl.yAxisScale[index] === 'undefined') {
+ gl.yAxisScale[index] = []
+ }
+
+ let range = Math.abs(maxY - minY)
+
+ if (y.logarithmic && range <= 5) {
+ gl.invalidLogScale = true
+ }
+
+ if (y.logarithmic && range > 5) {
+ gl.allSeriesCollapsed = false
+ gl.yAxisScale[index] = y.forceNiceScale
+ ? this.logarithmicScaleNice(minY, maxY, y.logBase)
+ : this.logarithmicScale(minY, maxY, y.logBase)
+ } else {
+ if (
+ maxY === -Number.MAX_VALUE ||
+ !Utils.isNumber(maxY) ||
+ minY === Number.MAX_VALUE ||
+ !Utils.isNumber(minY)
+ ) {
+ // no data in the chart.
+ // Either all series collapsed or user passed a blank array.
+ // Show the user's yaxis with their scale options but with a range.
+ gl.yAxisScale[index] = this.niceScale(Number.MIN_VALUE, 0, index)
+ } else {
+ // there is some data. Turn off the allSeriesCollapsed flag
+ gl.allSeriesCollapsed = false
+ gl.yAxisScale[index] = this.niceScale(minY, maxY, index)
+ }
+ }
+ }
+
+ setXScale(minX, maxX) {
+ const w = this.w
+ const gl = w.globals
+ let diff = Math.abs(maxX - minX)
+ if (maxX === -Number.MAX_VALUE || !Utils.isNumber(maxX)) {
+ // no data in the chart. Either all series collapsed or user passed a blank array
+ gl.xAxisScale = this.linearScale(0, 10, 10)
+ } else {
+ let ticks = gl.xTickAmount + 1
+
+ if (diff < 10 && diff > 1) {
+ ticks = diff
+ }
+ gl.xAxisScale = this.linearScale(
+ minX,
+ maxX,
+ ticks,
+ 0,
+ w.config.xaxis.stepSize
+ )
+ }
+ return gl.xAxisScale
+ }
+
+ scaleMultipleYAxes() {
+ const cnf = this.w.config
+ const gl = this.w.globals
+
+ this.coreUtils.setSeriesYAxisMappings()
+
+ let axisSeriesMap = gl.seriesYAxisMap
+ let minYArr = gl.minYArr
+ let maxYArr = gl.maxYArr
+
+ // Compute min..max for each yaxis
+ gl.allSeriesCollapsed = true
+ gl.barGroups = []
+ axisSeriesMap.forEach((axisSeries, ai) => {
+ let groupNames = []
+ axisSeries.forEach((as) => {
+ let group = cnf.series[as].group
+ if (groupNames.indexOf(group) < 0) {
+ groupNames.push(group)
+ }
+ })
+ if (axisSeries.length > 0) {
+ let minY = Number.MAX_VALUE
+ let maxY = -Number.MAX_VALUE
+ let lowestY = minY
+ let highestY = maxY
+ let seriesType
+ let seriesGroupName
+ if (cnf.chart.stacked) {
+ // Series' on this axis with the same group name will be stacked.
+ // Sum series in each group separately
+ let mapSeries = new Array(gl.dataPoints).fill(0)
+ let sumSeries = []
+ let posSeries = []
+ let negSeries = []
+ groupNames.forEach(() => {
+ sumSeries.push(mapSeries.map(() => Number.MIN_VALUE))
+ posSeries.push(mapSeries.map(() => Number.MIN_VALUE))
+ negSeries.push(mapSeries.map(() => Number.MIN_VALUE))
+ })
+ for (let i = 0; i < axisSeries.length; i++) {
+ // Assume chart type but the first series that has a type overrides.
+ if (!seriesType && cnf.series[axisSeries[i]].type) {
+ seriesType = cnf.series[axisSeries[i]].type
+ }
+ // Sum all series for this yaxis at each corresponding datapoint
+ // For bar and column charts we need to keep positive and negative
+ // values separate, for each group separately.
+ let si = axisSeries[i]
+ if (cnf.series[si].group) {
+ seriesGroupName = cnf.series[si].group
+ } else {
+ seriesGroupName = 'axis-'.concat(ai)
+ }
+ let collapsed = !(
+ gl.collapsedSeriesIndices.indexOf(si) < 0 &&
+ gl.ancillaryCollapsedSeriesIndices.indexOf(si) < 0
+ )
+ if (!collapsed) {
+ gl.allSeriesCollapsed = false
+ groupNames.forEach((gn, gni) => {
+ // Undefined group names will be grouped together as their own
+ // group.
+ if (cnf.series[si].group === gn) {
+ for (let j = 0; j < gl.series[si].length; j++) {
+ let val = gl.series[si][j]
+ if (val >= 0) {
+ posSeries[gni][j] += val
+ } else {
+ negSeries[gni][j] += val
+ }
+ sumSeries[gni][j] += val
+ // For non bar-like series' we need these point max/min values.
+ lowestY = Math.min(lowestY, val)
+ highestY = Math.max(highestY, val)
+ }
+ }
+ })
+ }
+ if (seriesType === 'bar' || seriesType === 'column') {
+ gl.barGroups.push(seriesGroupName)
+ }
+ }
+ if (!seriesType) {
+ seriesType = cnf.chart.type
+ }
+ if (seriesType === 'bar' || seriesType === 'column') {
+ groupNames.forEach((gn, gni) => {
+ minY = Math.min(minY, Math.min.apply(null, negSeries[gni]))
+ maxY = Math.max(maxY, Math.max.apply(null, posSeries[gni]))
+ })
+ } else {
+ groupNames.forEach((gn, gni) => {
+ lowestY = Math.min(lowestY, Math.min.apply(null, sumSeries[gni]))
+ highestY = Math.max(
+ highestY,
+ Math.max.apply(null, sumSeries[gni])
+ )
+ })
+ minY = lowestY
+ maxY = highestY
+ }
+ if (minY === Number.MIN_VALUE && maxY === Number.MIN_VALUE) {
+ // No series data
+ maxY = -Number.MAX_VALUE
+ }
+ } else {
+ for (let i = 0; i < axisSeries.length; i++) {
+ let si = axisSeries[i]
+ minY = Math.min(minY, minYArr[si])
+ maxY = Math.max(maxY, maxYArr[si])
+ let collapsed = !(
+ gl.collapsedSeriesIndices.indexOf(si) < 0 &&
+ gl.ancillaryCollapsedSeriesIndices.indexOf(si) < 0
+ )
+ if (!collapsed) {
+ gl.allSeriesCollapsed = false
+ }
+ }
+ }
+ if (cnf.yaxis[ai].min !== undefined) {
+ if (typeof cnf.yaxis[ai].min === 'function') {
+ minY = cnf.yaxis[ai].min(minY)
+ } else {
+ minY = cnf.yaxis[ai].min
+ }
+ }
+ if (cnf.yaxis[ai].max !== undefined) {
+ if (typeof cnf.yaxis[ai].max === 'function') {
+ maxY = cnf.yaxis[ai].max(maxY)
+ } else {
+ maxY = cnf.yaxis[ai].max
+ }
+ }
+ gl.barGroups = gl.barGroups.filter((v, i, a) => a.indexOf(v) === i)
+ // Set the scale for this yaxis
+ this.setYScaleForIndex(ai, minY, maxY)
+ // Set individual series min and max to nice values
+ axisSeries.forEach((si) => {
+ minYArr[si] = gl.yAxisScale[ai].niceMin
+ maxYArr[si] = gl.yAxisScale[ai].niceMax
+ })
+ } else {
+ // No series referenced by this yaxis
+ this.setYScaleForIndex(ai, 0, -Number.MAX_VALUE)
+ }
+ })
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Series.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Series.js
new file mode 100644
index 0000000..64e497a
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Series.js
@@ -0,0 +1,511 @@
+import Graphics from './Graphics'
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts Series Class for interaction with the Series of the chart.
+ *
+ * @module Series
+ **/
+
+export default class Series {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.legendInactiveClass = 'legend-mouseover-inactive'
+ }
+
+ getAllSeriesEls() {
+ return this.w.globals.dom.baseEl.getElementsByClassName(`apexcharts-series`)
+ }
+
+ getSeriesByName(seriesName) {
+ return this.w.globals.dom.baseEl.querySelector(
+ `.apexcharts-inner .apexcharts-series[seriesName='${Utils.escapeString(
+ seriesName
+ )}']`
+ )
+ }
+
+ isSeriesHidden(seriesName) {
+ const targetElement = this.getSeriesByName(seriesName)
+ let realIndex = parseInt(targetElement.getAttribute('data:realIndex'), 10)
+ let isHidden = targetElement.classList.contains(
+ 'apexcharts-series-collapsed'
+ )
+
+ return { isHidden, realIndex }
+ }
+
+ addCollapsedClassToSeries(elSeries, index) {
+ const w = this.w
+ function iterateOnAllCollapsedSeries(series) {
+ for (let cs = 0; cs < series.length; cs++) {
+ if (series[cs].index === index) {
+ elSeries.node.classList.add('apexcharts-series-collapsed')
+ }
+ }
+ }
+
+ iterateOnAllCollapsedSeries(w.globals.collapsedSeries)
+ iterateOnAllCollapsedSeries(w.globals.ancillaryCollapsedSeries)
+ }
+
+ toggleSeries(seriesName) {
+ let isSeriesHidden = this.isSeriesHidden(seriesName)
+
+ this.ctx.legend.legendHelpers.toggleDataSeries(
+ isSeriesHidden.realIndex,
+ isSeriesHidden.isHidden
+ )
+
+ return isSeriesHidden.isHidden
+ }
+
+ showSeries(seriesName) {
+ let isSeriesHidden = this.isSeriesHidden(seriesName)
+
+ if (isSeriesHidden.isHidden) {
+ this.ctx.legend.legendHelpers.toggleDataSeries(
+ isSeriesHidden.realIndex,
+ true
+ )
+ }
+ }
+
+ hideSeries(seriesName) {
+ let isSeriesHidden = this.isSeriesHidden(seriesName)
+
+ if (!isSeriesHidden.isHidden) {
+ this.ctx.legend.legendHelpers.toggleDataSeries(
+ isSeriesHidden.realIndex,
+ false
+ )
+ }
+ }
+
+ resetSeries(
+ shouldUpdateChart = true,
+ shouldResetZoom = true,
+ shouldResetCollapsed = true
+ ) {
+ const w = this.w
+
+ let series = Utils.clone(w.globals.initialSeries)
+
+ w.globals.previousPaths = []
+
+ if (shouldResetCollapsed) {
+ w.globals.collapsedSeries = []
+ w.globals.ancillaryCollapsedSeries = []
+ w.globals.collapsedSeriesIndices = []
+ w.globals.ancillaryCollapsedSeriesIndices = []
+ } else {
+ series = this.emptyCollapsedSeries(series)
+ }
+
+ w.config.series = series
+
+ if (shouldUpdateChart) {
+ if (shouldResetZoom) {
+ w.globals.zoomed = false
+ this.ctx.updateHelpers.revertDefaultAxisMinMax()
+ }
+ this.ctx.updateHelpers._updateSeries(
+ series,
+ w.config.chart.animations.dynamicAnimation.enabled
+ )
+ }
+ }
+
+ emptyCollapsedSeries(series) {
+ const w = this.w
+ for (let i = 0; i < series.length; i++) {
+ if (w.globals.collapsedSeriesIndices.indexOf(i) > -1) {
+ series[i].data = []
+ }
+ }
+ return series
+ }
+
+ highlightSeries(seriesName) {
+ const w = this.w
+
+ const targetElement = this.getSeriesByName(seriesName)
+ let realIndex = parseInt(targetElement?.getAttribute('data:realIndex'), 10)
+
+ let allSeriesEls = w.globals.dom.baseEl.querySelectorAll(
+ `.apexcharts-series, .apexcharts-datalabels, .apexcharts-yaxis`
+ )
+
+ let seriesEl = null
+ let dataLabelEl = null
+ let yaxisEl = null
+ if (w.globals.axisCharts || w.config.chart.type === 'radialBar') {
+ if (w.globals.axisCharts) {
+ seriesEl = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-series[data\\:realIndex='${realIndex}']`
+ )
+ dataLabelEl = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-datalabels[data\\:realIndex='${realIndex}']`
+ )
+ let yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex]
+ yaxisEl = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-yaxis[rel='${yaxisIndex}']`
+ )
+ } else {
+ seriesEl = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-series[rel='${realIndex + 1}']`
+ )
+ }
+ } else {
+ seriesEl = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-series[rel='${realIndex + 1}'] path`
+ )
+ }
+
+ for (let se = 0; se < allSeriesEls.length; se++) {
+ allSeriesEls[se].classList.add(this.legendInactiveClass)
+ }
+
+ if (seriesEl) {
+ if (!w.globals.axisCharts) {
+ seriesEl.parentNode.classList.remove(this.legendInactiveClass)
+ }
+ seriesEl.classList.remove(this.legendInactiveClass)
+
+ if (dataLabelEl !== null) {
+ dataLabelEl.classList.remove(this.legendInactiveClass)
+ }
+
+ if (yaxisEl !== null) {
+ yaxisEl.classList.remove(this.legendInactiveClass)
+ }
+ } else {
+ for (let se = 0; se < allSeriesEls.length; se++) {
+ allSeriesEls[se].classList.remove(this.legendInactiveClass)
+ }
+ }
+ }
+
+ toggleSeriesOnHover(e, targetElement) {
+ const w = this.w
+
+ if (!targetElement) targetElement = e.target
+
+ let allSeriesEls = w.globals.dom.baseEl.querySelectorAll(
+ `.apexcharts-series, .apexcharts-datalabels, .apexcharts-yaxis`
+ )
+
+ if (e.type === 'mousemove') {
+ let realIndex = parseInt(targetElement.getAttribute('rel'), 10) - 1
+
+ this.highlightSeries(w.globals.seriesNames[realIndex])
+ } else if (e.type === 'mouseout') {
+ for (let se = 0; se < allSeriesEls.length; se++) {
+ allSeriesEls[se].classList.remove(this.legendInactiveClass)
+ }
+ }
+ }
+
+ highlightRangeInSeries(e, targetElement) {
+ const w = this.w
+ const allHeatMapElements = w.globals.dom.baseEl.getElementsByClassName(
+ 'apexcharts-heatmap-rect'
+ )
+
+ const activeInactive = (action) => {
+ for (let i = 0; i < allHeatMapElements.length; i++) {
+ allHeatMapElements[i].classList[action](this.legendInactiveClass)
+ }
+ }
+
+ const removeInactiveClassFromHoveredRange = (range, rangeMax) => {
+ for (let i = 0; i < allHeatMapElements.length; i++) {
+ const val = Number(allHeatMapElements[i].getAttribute('val'))
+ if (
+ val >= range.from &&
+ (val < range.to || (range.to === rangeMax && val === rangeMax))
+ ) {
+ allHeatMapElements[i].classList.remove(this.legendInactiveClass)
+ }
+ }
+ }
+
+ if (e.type === 'mousemove') {
+ let seriesCnt = parseInt(targetElement.getAttribute('rel'), 10) - 1
+ activeInactive('add')
+
+ const ranges = w.config.plotOptions.heatmap.colorScale.ranges
+ const range = ranges[seriesCnt]
+ const rangeMax = ranges.reduce((acc, cur) => Math.max(acc, cur.to), 0)
+
+ removeInactiveClassFromHoveredRange(range, rangeMax)
+ } else if (e.type === 'mouseout') {
+ activeInactive('remove')
+ }
+ }
+
+ getActiveConfigSeriesIndex(order = 'asc', chartTypes = []) {
+ const w = this.w
+ let activeIndex = 0
+
+ if (w.config.series.length > 1) {
+ // active series flag is required to know if user has not deactivated via legend click
+ let activeSeriesIndex = w.config.series.map((s, index) => {
+ const checkChartType = () => {
+ if (w.globals.comboCharts) {
+ return (
+ chartTypes.length === 0 ||
+ (chartTypes.length &&
+ chartTypes.indexOf(w.config.series[index].type) > -1)
+ )
+ }
+ return true
+ }
+
+ const hasData =
+ s.data &&
+ s.data.length > 0 &&
+ w.globals.collapsedSeriesIndices.indexOf(index) === -1
+
+ return hasData && checkChartType() ? index : -1
+ })
+ for (
+ let a = order === 'asc' ? 0 : activeSeriesIndex.length - 1;
+ order === 'asc' ? a < activeSeriesIndex.length : a >= 0;
+ order === 'asc' ? a++ : a--
+ ) {
+ if (activeSeriesIndex[a] !== -1) {
+ activeIndex = activeSeriesIndex[a]
+ break
+ }
+ }
+ }
+
+ return activeIndex
+ }
+
+ getBarSeriesIndices() {
+ const w = this.w
+ if (w.globals.comboCharts) {
+ return this.w.config.series
+ .map((s, i) => {
+ return s.type === 'bar' || s.type === 'column' ? i : -1
+ })
+ .filter((i) => {
+ return i !== -1
+ })
+ }
+ return this.w.config.series.map((s, i) => {
+ return i
+ })
+ }
+
+ getPreviousPaths() {
+ let w = this.w
+
+ w.globals.previousPaths = []
+
+ function pushPaths(seriesEls, i, type) {
+ let paths = seriesEls[i].childNodes
+ let dArr = {
+ type,
+ paths: [],
+ realIndex: seriesEls[i].getAttribute('data:realIndex'),
+ }
+
+ for (let j = 0; j < paths.length; j++) {
+ if (paths[j].hasAttribute('pathTo')) {
+ let d = paths[j].getAttribute('pathTo')
+ dArr.paths.push({
+ d,
+ })
+ }
+ }
+
+ w.globals.previousPaths.push(dArr)
+ }
+
+ const getPaths = (chartType) => {
+ return w.globals.dom.baseEl.querySelectorAll(
+ `.apexcharts-${chartType}-series .apexcharts-series`
+ )
+ }
+
+ const chartTypes = [
+ 'line',
+ 'area',
+ 'bar',
+ 'rangebar',
+ 'rangeArea',
+ 'candlestick',
+ 'radar',
+ ]
+ chartTypes.forEach((type) => {
+ const paths = getPaths(type)
+ for (let p = 0; p < paths.length; p++) {
+ pushPaths(paths, p, type)
+ }
+ })
+
+ this.handlePrevBubbleScatterPaths('bubble')
+ this.handlePrevBubbleScatterPaths('scatter')
+
+ let heatTreeSeries = w.globals.dom.baseEl.querySelectorAll(
+ `.apexcharts-${w.config.chart.type} .apexcharts-series`
+ )
+
+ if (heatTreeSeries.length > 0) {
+ for (let h = 0; h < heatTreeSeries.length; h++) {
+ let seriesEls = w.globals.dom.baseEl.querySelectorAll(
+ `.apexcharts-${w.config.chart.type} .apexcharts-series[data\\:realIndex='${h}'] rect`
+ )
+
+ let dArr = []
+
+ for (let i = 0; i < seriesEls.length; i++) {
+ const getAttr = (x) => {
+ return seriesEls[i].getAttribute(x)
+ }
+ const rect = {
+ x: parseFloat(getAttr('x')),
+ y: parseFloat(getAttr('y')),
+ width: parseFloat(getAttr('width')),
+ height: parseFloat(getAttr('height')),
+ }
+ dArr.push({
+ rect,
+ color: seriesEls[i].getAttribute('color'),
+ })
+ }
+ w.globals.previousPaths.push(dArr)
+ }
+ }
+
+ if (!w.globals.axisCharts) {
+ // for non-axis charts (i.e., circular charts, pathFrom is not usable. We need whole series)
+ w.globals.previousPaths = w.globals.series
+ }
+ }
+
+ handlePrevBubbleScatterPaths(type) {
+ const w = this.w
+ let paths = w.globals.dom.baseEl.querySelectorAll(
+ `.apexcharts-${type}-series .apexcharts-series`
+ )
+ if (paths.length > 0) {
+ for (let s = 0; s < paths.length; s++) {
+ let seriesEls = w.globals.dom.baseEl.querySelectorAll(
+ `.apexcharts-${type}-series .apexcharts-series[data\\:realIndex='${s}'] circle`
+ )
+ let dArr = []
+
+ for (let i = 0; i < seriesEls.length; i++) {
+ dArr.push({
+ x: seriesEls[i].getAttribute('cx'),
+ y: seriesEls[i].getAttribute('cy'),
+ r: seriesEls[i].getAttribute('r'),
+ })
+ }
+ w.globals.previousPaths.push(dArr)
+ }
+ }
+ }
+
+ clearPreviousPaths() {
+ const w = this.w
+ w.globals.previousPaths = []
+ w.globals.allSeriesCollapsed = false
+ }
+
+ handleNoData() {
+ const w = this.w
+ const me = this
+
+ const noDataOpts = w.config.noData
+ const graphics = new Graphics(me.ctx)
+
+ let x = w.globals.svgWidth / 2
+ let y = w.globals.svgHeight / 2
+ let textAnchor = 'middle'
+
+ w.globals.noData = true
+ w.globals.animationEnded = true
+
+ if (noDataOpts.align === 'left') {
+ x = 10
+ textAnchor = 'start'
+ } else if (noDataOpts.align === 'right') {
+ x = w.globals.svgWidth - 10
+ textAnchor = 'end'
+ }
+
+ if (noDataOpts.verticalAlign === 'top') {
+ y = 50
+ } else if (noDataOpts.verticalAlign === 'bottom') {
+ y = w.globals.svgHeight - 50
+ }
+
+ x = x + noDataOpts.offsetX
+ y = y + parseInt(noDataOpts.style.fontSize, 10) + 2 + noDataOpts.offsetY
+
+ if (noDataOpts.text !== undefined && noDataOpts.text !== '') {
+ let titleText = graphics.drawText({
+ x,
+ y,
+ text: noDataOpts.text,
+ textAnchor,
+ fontSize: noDataOpts.style.fontSize,
+ fontFamily: noDataOpts.style.fontFamily,
+ foreColor: noDataOpts.style.color,
+ opacity: 1,
+ class: 'apexcharts-text-nodata',
+ })
+
+ w.globals.dom.Paper.add(titleText)
+ }
+ }
+
+ // When user clicks on legends, the collapsed series is filled with [0,0,0,...,0]
+ // This is because we don't want to alter the series' length as it is used at many places
+ setNullSeriesToZeroValues(series) {
+ let w = this.w
+ for (let sl = 0; sl < series.length; sl++) {
+ if (series[sl].length === 0) {
+ for (let j = 0; j < series[w.globals.maxValsInArrayIndex].length; j++) {
+ series[sl].push(0)
+ }
+ }
+ }
+ return series
+ }
+
+ hasAllSeriesEqualX() {
+ let equalLen = true
+ const w = this.w
+
+ const filteredSerX = this.filteredSeriesX()
+
+ for (let i = 0; i < filteredSerX.length - 1; i++) {
+ if (filteredSerX[i][0] !== filteredSerX[i + 1][0]) {
+ equalLen = false
+ break
+ }
+ }
+
+ w.globals.allSeriesHasEqualX = equalLen
+
+ return equalLen
+ }
+
+ filteredSeriesX() {
+ const w = this.w
+
+ const filteredSeriesX = w.globals.seriesX.map((ser) =>
+ ser.length > 0 ? ser : []
+ )
+
+ return filteredSeriesX
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Theme.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Theme.js
new file mode 100644
index 0000000..50355c8
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Theme.js
@@ -0,0 +1,240 @@
+import Utils from '../utils/Utils'
+
+/**
+ * ApexCharts Theme Class for setting the colors and palettes.
+ *
+ * @module Theme
+ **/
+
+export default class Theme {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ this.colors = []
+ this.isColorFn = false
+ this.isHeatmapDistributed = this.checkHeatmapDistributed()
+ this.isBarDistributed = this.checkBarDistributed()
+ }
+
+ checkHeatmapDistributed() {
+ const { chart, plotOptions } = this.w.config
+ return (
+ (chart.type === 'treemap' &&
+ plotOptions.treemap &&
+ plotOptions.treemap.distributed) ||
+ (chart.type === 'heatmap' &&
+ plotOptions.heatmap &&
+ plotOptions.heatmap.distributed)
+ )
+ }
+
+ checkBarDistributed() {
+ const { chart, plotOptions } = this.w.config
+ return (
+ plotOptions.bar &&
+ plotOptions.bar.distributed &&
+ (chart.type === 'bar' || chart.type === 'rangeBar')
+ )
+ }
+
+ init() {
+ this.setDefaultColors()
+ }
+
+ setDefaultColors() {
+ const w = this.w
+ const utils = new Utils()
+
+ w.globals.dom.elWrap.classList.add(
+ `apexcharts-theme-${w.config.theme.mode}`
+ )
+
+ // Create a copy of config.colors array to avoid mutating the original config.colors
+ const configColors = [...(w.config.colors || w.config.fill.colors || [])]
+ w.globals.colors = this.getColors(configColors)
+
+ this.applySeriesColors(w.globals.seriesColors, w.globals.colors)
+
+ if (w.config.theme.monochrome.enabled) {
+ w.globals.colors = this.getMonochromeColors(
+ w.config.theme.monochrome,
+ w.globals.series,
+ utils
+ )
+ }
+
+ const defaultColors = w.globals.colors.slice()
+ this.pushExtraColors(w.globals.colors)
+
+ this.applyColorTypes(['fill', 'stroke'], defaultColors)
+ this.applyDataLabelsColors(defaultColors)
+ this.applyRadarPolygonsColors()
+ this.applyMarkersColors(defaultColors)
+ }
+
+ getColors(configColors) {
+ const w = this.w
+ if (!configColors || configColors.length === 0) {
+ return this.predefined()
+ }
+
+ if (
+ Array.isArray(configColors) &&
+ configColors.length > 0 &&
+ typeof configColors[0] === 'function'
+ ) {
+ this.isColorFn = true
+ return w.config.series.map((s, i) => {
+ let c = configColors[i] || configColors[0]
+ return typeof c === 'function'
+ ? c({
+ value: w.globals.axisCharts
+ ? w.globals.series[i][0] || 0
+ : w.globals.series[i],
+ seriesIndex: i,
+ dataPointIndex: i,
+ w: this.w,
+ })
+ : c
+ })
+ }
+
+ return configColors
+ }
+
+ applySeriesColors(seriesColors, globalsColors) {
+ seriesColors.forEach((c, i) => {
+ if (c) {
+ globalsColors[i] = c
+ }
+ })
+ }
+
+ getMonochromeColors(monochrome, series, utils) {
+ const { color, shadeIntensity, shadeTo } = monochrome
+ const glsCnt =
+ this.isBarDistributed || this.isHeatmapDistributed
+ ? series[0].length * series.length
+ : series.length
+ const part = 1 / (glsCnt / shadeIntensity)
+ let percent = 0
+
+ return Array.from({ length: glsCnt }, () => {
+ const newColor =
+ shadeTo === 'dark'
+ ? utils.shadeColor(percent * -1, color)
+ : utils.shadeColor(percent, color)
+ percent += part
+ return newColor
+ })
+ }
+
+ applyColorTypes(colorTypes, defaultColors) {
+ const w = this.w
+ colorTypes.forEach((c) => {
+ w.globals[c].colors =
+ w.config[c].colors === undefined
+ ? this.isColorFn
+ ? w.config.colors
+ : defaultColors
+ : w.config[c].colors.slice()
+ this.pushExtraColors(w.globals[c].colors)
+ })
+ }
+
+ applyDataLabelsColors(defaultColors) {
+ const w = this.w
+ w.globals.dataLabels.style.colors =
+ w.config.dataLabels.style.colors === undefined
+ ? defaultColors
+ : w.config.dataLabels.style.colors.slice()
+ this.pushExtraColors(w.globals.dataLabels.style.colors, 50)
+ }
+
+ applyRadarPolygonsColors() {
+ const w = this.w
+ w.globals.radarPolygons.fill.colors =
+ w.config.plotOptions.radar.polygons.fill.colors === undefined
+ ? [w.config.theme.mode === 'dark' ? '#424242' : 'none']
+ : w.config.plotOptions.radar.polygons.fill.colors.slice()
+ this.pushExtraColors(w.globals.radarPolygons.fill.colors, 20)
+ }
+
+ applyMarkersColors(defaultColors) {
+ const w = this.w
+ w.globals.markers.colors =
+ w.config.markers.colors === undefined
+ ? defaultColors
+ : w.config.markers.colors.slice()
+ this.pushExtraColors(w.globals.markers.colors)
+ }
+
+ pushExtraColors(colorSeries, length, distributed = null) {
+ const w = this.w
+ let len = length || w.globals.series.length
+
+ if (distributed === null) {
+ distributed =
+ this.isBarDistributed ||
+ this.isHeatmapDistributed ||
+ (w.config.chart.type === 'heatmap' &&
+ w.config.plotOptions.heatmap &&
+ w.config.plotOptions.heatmap.colorScale.inverse)
+ }
+
+ if (distributed && w.globals.series.length) {
+ len =
+ w.globals.series[w.globals.maxValsInArrayIndex].length *
+ w.globals.series.length
+ }
+
+ if (colorSeries.length < len) {
+ let diff = len - colorSeries.length
+ for (let i = 0; i < diff; i++) {
+ colorSeries.push(colorSeries[i])
+ }
+ }
+ }
+
+ updateThemeOptions(options) {
+ options.chart = options.chart || {}
+ options.tooltip = options.tooltip || {}
+ const mode = options.theme.mode
+ const palette =
+ mode === 'dark'
+ ? 'palette4'
+ : mode === 'light'
+ ? 'palette1'
+ : options.theme.palette || 'palette1'
+ const foreColor =
+ mode === 'dark'
+ ? '#f6f7f8'
+ : mode === 'light'
+ ? '#373d3f'
+ : options.chart.foreColor || '#373d3f'
+
+ options.tooltip.theme = mode || 'light'
+ options.chart.foreColor = foreColor
+ options.theme.palette = palette
+
+ return options
+ }
+
+ predefined() {
+ const palette = this.w.config.theme.palette
+ const palettes = {
+ palette1: ['#008FFB', '#00E396', '#FEB019', '#FF4560', '#775DD0'],
+ palette2: ['#3f51b5', '#03a9f4', '#4caf50', '#f9ce1d', '#FF9800'],
+ palette3: ['#33b2df', '#546E7A', '#d4526e', '#13d8aa', '#A5978B'],
+ palette4: ['#4ecdc4', '#c7f464', '#81D4FA', '#fd6a6a', '#546E7A'],
+ palette5: ['#2b908f', '#f9a3a4', '#90ee7e', '#fa4443', '#69d2e7'],
+ palette6: ['#449DD1', '#F86624', '#EA3546', '#662E9B', '#C5D86D'],
+ palette7: ['#D7263D', '#1B998B', '#2E294E', '#F46036', '#E2C044'],
+ palette8: ['#662E9B', '#F86624', '#F9C80E', '#EA3546', '#43BCCD'],
+ palette9: ['#5C4742', '#A5978B', '#8D5B4C', '#5A2A27', '#C4BBAF'],
+ palette10: ['#A300D6', '#7D02EB', '#5653FE', '#2983FF', '#00B1F2'],
+ default: ['#008FFB', '#00E396', '#FEB019', '#FF4560', '#775DD0'],
+ }
+ return palettes[palette] || palettes.default
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/TimeScale.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/TimeScale.js
new file mode 100644
index 0000000..0d7f5db
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/TimeScale.js
@@ -0,0 +1,927 @@
+import DateTime from '../utils/DateTime'
+import Dimensions from './dimensions/Dimensions'
+import Graphics from './Graphics'
+import Utils from '../utils/Utils'
+
+const MINUTES_IN_DAY = 24 * 60
+const SECONDS_IN_DAY = MINUTES_IN_DAY * 60
+const MIN_ZOOM_DAYS = 10 / SECONDS_IN_DAY
+
+/**
+ * ApexCharts TimeScale Class for generating time ticks for x-axis.
+ *
+ * @module TimeScale
+ **/
+
+class TimeScale {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ this.timeScaleArray = []
+ this.utc = this.w.config.xaxis.labels.datetimeUTC
+ }
+
+ calculateTimeScaleTicks(minX, maxX) {
+ let w = this.w
+
+ // null check when no series to show
+ if (w.globals.allSeriesCollapsed) {
+ w.globals.labels = []
+ w.globals.timescaleLabels = []
+ return []
+ }
+
+ let dt = new DateTime(this.ctx)
+
+ const daysDiff = (maxX - minX) / (1000 * SECONDS_IN_DAY)
+ this.determineInterval(daysDiff)
+
+ w.globals.disableZoomIn = false
+ w.globals.disableZoomOut = false
+
+ if (daysDiff < MIN_ZOOM_DAYS) {
+ w.globals.disableZoomIn = true
+ } else if (daysDiff > 50000) {
+ w.globals.disableZoomOut = true
+ }
+
+ const timeIntervals = dt.getTimeUnitsfromTimestamp(minX, maxX, this.utc)
+
+ const daysWidthOnXAxis = w.globals.gridWidth / daysDiff
+ const hoursWidthOnXAxis = daysWidthOnXAxis / 24
+ const minutesWidthOnXAxis = hoursWidthOnXAxis / 60
+ const secondsWidthOnXAxis = minutesWidthOnXAxis / 60
+
+ let numberOfHours = Math.floor(daysDiff * 24)
+ let numberOfMinutes = Math.floor(daysDiff * MINUTES_IN_DAY)
+ let numberOfSeconds = Math.floor(daysDiff * SECONDS_IN_DAY)
+ let numberOfDays = Math.floor(daysDiff)
+ let numberOfMonths = Math.floor(daysDiff / 30)
+ let numberOfYears = Math.floor(daysDiff / 365)
+
+ const firstVal = {
+ minMillisecond: timeIntervals.minMillisecond,
+ minSecond: timeIntervals.minSecond,
+ minMinute: timeIntervals.minMinute,
+ minHour: timeIntervals.minHour,
+ minDate: timeIntervals.minDate,
+ minMonth: timeIntervals.minMonth,
+ minYear: timeIntervals.minYear,
+ }
+
+ let currentMillisecond = firstVal.minMillisecond
+ let currentSecond = firstVal.minSecond
+ let currentMinute = firstVal.minMinute
+ let currentHour = firstVal.minHour
+ let currentMonthDate = firstVal.minDate
+ let currentDate = firstVal.minDate
+ let currentMonth = firstVal.minMonth
+ let currentYear = firstVal.minYear
+
+ const params = {
+ firstVal,
+ currentMillisecond,
+ currentSecond,
+ currentMinute,
+ currentHour,
+ currentMonthDate,
+ currentDate,
+ currentMonth,
+ currentYear,
+ daysWidthOnXAxis,
+ hoursWidthOnXAxis,
+ minutesWidthOnXAxis,
+ secondsWidthOnXAxis,
+ numberOfSeconds,
+ numberOfMinutes,
+ numberOfHours,
+ numberOfDays,
+ numberOfMonths,
+ numberOfYears,
+ }
+
+ switch (this.tickInterval) {
+ case 'years': {
+ this.generateYearScale(params)
+ break
+ }
+ case 'months':
+ case 'half_year': {
+ this.generateMonthScale(params)
+ break
+ }
+ case 'months_days':
+ case 'months_fortnight':
+ case 'days':
+ case 'week_days': {
+ this.generateDayScale(params)
+ break
+ }
+ case 'hours': {
+ this.generateHourScale(params)
+ break
+ }
+ case 'minutes_fives':
+ case 'minutes':
+ this.generateMinuteScale(params)
+ break
+ case 'seconds_tens':
+ case 'seconds_fives':
+ case 'seconds':
+ this.generateSecondScale(params)
+ break
+ }
+
+ // first, we will adjust the month values index
+ // as in the upper function, it is starting from 0
+ // we will start them from 1
+ const adjustedMonthInTimeScaleArray = this.timeScaleArray.map((ts) => {
+ let defaultReturn = {
+ position: ts.position,
+ unit: ts.unit,
+ year: ts.year,
+ day: ts.day ? ts.day : 1,
+ hour: ts.hour ? ts.hour : 0,
+ month: ts.month + 1,
+ }
+ if (ts.unit === 'month') {
+ return {
+ ...defaultReturn,
+ day: 1,
+ value: ts.value + 1,
+ }
+ } else if (ts.unit === 'day' || ts.unit === 'hour') {
+ return {
+ ...defaultReturn,
+ value: ts.value,
+ }
+ } else if (ts.unit === 'minute') {
+ return {
+ ...defaultReturn,
+ value: ts.value,
+ minute: ts.value,
+ }
+ } else if (ts.unit === 'second') {
+ return {
+ ...defaultReturn,
+ value: ts.value,
+ minute: ts.minute,
+ second: ts.second,
+ }
+ }
+
+ return ts
+ })
+
+ const filteredTimeScale = adjustedMonthInTimeScaleArray.filter((ts) => {
+ let modulo = 1
+ let ticks = Math.ceil(w.globals.gridWidth / 120)
+ let value = ts.value
+ if (w.config.xaxis.tickAmount !== undefined) {
+ ticks = w.config.xaxis.tickAmount
+ }
+ if (adjustedMonthInTimeScaleArray.length > ticks) {
+ modulo = Math.floor(adjustedMonthInTimeScaleArray.length / ticks)
+ }
+
+ let shouldNotSkipUnit = false // there is a big change in unit i.e days to months
+ let shouldNotPrint = false // should skip these values
+
+ switch (this.tickInterval) {
+ case 'years':
+ // make years label denser
+ if (ts.unit === 'year') {
+ shouldNotSkipUnit = true
+ }
+ break
+ case 'half_year':
+ modulo = 7
+ if (ts.unit === 'year') {
+ shouldNotSkipUnit = true
+ }
+ break
+ case 'months':
+ modulo = 1
+ if (ts.unit === 'year') {
+ shouldNotSkipUnit = true
+ }
+ break
+ case 'months_fortnight':
+ modulo = 15
+ if (ts.unit === 'year' || ts.unit === 'month') {
+ shouldNotSkipUnit = true
+ }
+ if (value === 30) {
+ shouldNotPrint = true
+ }
+ break
+ case 'months_days':
+ modulo = 10
+ if (ts.unit === 'month') {
+ shouldNotSkipUnit = true
+ }
+ if (value === 30) {
+ shouldNotPrint = true
+ }
+ break
+ case 'week_days':
+ modulo = 8
+ if (ts.unit === 'month') {
+ shouldNotSkipUnit = true
+ }
+ break
+ case 'days':
+ modulo = 1
+ if (ts.unit === 'month') {
+ shouldNotSkipUnit = true
+ }
+ break
+ case 'hours':
+ if (ts.unit === 'day') {
+ shouldNotSkipUnit = true
+ }
+ break
+ case 'minutes_fives':
+ if (value % 5 !== 0) {
+ shouldNotPrint = true
+ }
+ break
+ case 'seconds_tens':
+ if (value % 10 !== 0) {
+ shouldNotPrint = true
+ }
+ break
+ case 'seconds_fives':
+ if (value % 5 !== 0) {
+ shouldNotPrint = true
+ }
+ break
+ }
+
+ if (
+ this.tickInterval === 'hours' ||
+ this.tickInterval === 'minutes_fives' ||
+ this.tickInterval === 'seconds_tens' ||
+ this.tickInterval === 'seconds_fives'
+ ) {
+ if (!shouldNotPrint) {
+ return true
+ }
+ } else {
+ if ((value % modulo === 0 || shouldNotSkipUnit) && !shouldNotPrint) {
+ return true
+ }
+ }
+ })
+
+ return filteredTimeScale
+ }
+
+ recalcDimensionsBasedOnFormat(filteredTimeScale, inverted) {
+ const w = this.w
+ const reformattedTimescaleArray = this.formatDates(filteredTimeScale)
+
+ const removedOverlappingTS = this.removeOverlappingTS(
+ reformattedTimescaleArray
+ )
+
+ w.globals.timescaleLabels = removedOverlappingTS.slice()
+
+ // at this stage, we need to re-calculate coords of the grid as timeline labels may have altered the xaxis labels coords
+ // The reason we can't do this prior to this stage is because timeline labels depends on gridWidth, and as the ticks are calculated based on available gridWidth, there can be unknown number of ticks generated for different minX and maxX
+ // Dependency on Dimensions(), need to refactor correctly
+ // TODO - find an alternate way to avoid calling this Heavy method twice
+ let dimensions = new Dimensions(this.ctx)
+ dimensions.plotCoords()
+ }
+
+ determineInterval(daysDiff) {
+ const yearsDiff = daysDiff / 365
+ const hoursDiff = daysDiff * 24
+ const minutesDiff = hoursDiff * 60
+ const secondsDiff = minutesDiff * 60
+ switch (true) {
+ case yearsDiff > 5:
+ this.tickInterval = 'years'
+ break
+ case daysDiff > 800:
+ this.tickInterval = 'half_year'
+ break
+ case daysDiff > 180:
+ this.tickInterval = 'months'
+ break
+ case daysDiff > 90:
+ this.tickInterval = 'months_fortnight'
+ break
+ case daysDiff > 60:
+ this.tickInterval = 'months_days'
+ break
+ case daysDiff > 30:
+ this.tickInterval = 'week_days'
+ break
+ case daysDiff > 2:
+ this.tickInterval = 'days'
+ break
+ case hoursDiff > 2.4:
+ this.tickInterval = 'hours'
+ break
+ case minutesDiff > 15:
+ this.tickInterval = 'minutes_fives'
+ break
+ case minutesDiff > 5:
+ this.tickInterval = 'minutes'
+ break
+ case minutesDiff > 1:
+ this.tickInterval = 'seconds_tens'
+ break
+ case secondsDiff > 20:
+ this.tickInterval = 'seconds_fives'
+ break
+ default:
+ this.tickInterval = 'seconds'
+ break
+ }
+ }
+
+ generateYearScale({
+ firstVal,
+ currentMonth,
+ currentYear,
+ daysWidthOnXAxis,
+ numberOfYears,
+ }) {
+ let firstTickValue = firstVal.minYear
+ let firstTickPosition = 0
+ const dt = new DateTime(this.ctx)
+
+ let unit = 'year'
+
+ if (firstVal.minDate > 1 || firstVal.minMonth > 0) {
+ let remainingDays = dt.determineRemainingDaysOfYear(
+ firstVal.minYear,
+ firstVal.minMonth,
+ firstVal.minDate
+ )
+
+ // remainingDaysofFirstMonth is used to reacht the 2nd tick position
+ let remainingDaysOfFirstYear =
+ dt.determineDaysOfYear(firstVal.minYear) - remainingDays + 1
+
+ // calculate the first tick position
+ firstTickPosition = remainingDaysOfFirstYear * daysWidthOnXAxis
+ firstTickValue = firstVal.minYear + 1
+ // push the first tick in the array
+ this.timeScaleArray.push({
+ position: firstTickPosition,
+ value: firstTickValue,
+ unit,
+ year: firstTickValue,
+ month: Utils.monthMod(currentMonth + 1),
+ })
+ } else if (firstVal.minDate === 1 && firstVal.minMonth === 0) {
+ // push the first tick in the array
+ this.timeScaleArray.push({
+ position: firstTickPosition,
+ value: firstTickValue,
+ unit,
+ year: currentYear,
+ month: Utils.monthMod(currentMonth + 1),
+ })
+ }
+
+ let year = firstTickValue
+ let pos = firstTickPosition
+
+ // keep drawing rest of the ticks
+ for (let i = 0; i < numberOfYears; i++) {
+ year++
+ pos = dt.determineDaysOfYear(year - 1) * daysWidthOnXAxis + pos
+ this.timeScaleArray.push({
+ position: pos,
+ value: year,
+ unit,
+ year,
+ month: 1,
+ })
+ }
+ }
+
+ generateMonthScale({
+ firstVal,
+ currentMonthDate,
+ currentMonth,
+ currentYear,
+ daysWidthOnXAxis,
+ numberOfMonths,
+ }) {
+ let firstTickValue = currentMonth
+ let firstTickPosition = 0
+ const dt = new DateTime(this.ctx)
+ let unit = 'month'
+ let yrCounter = 0
+
+ if (firstVal.minDate > 1) {
+ // remainingDaysofFirstMonth is used to reacht the 2nd tick position
+ let remainingDaysOfFirstMonth =
+ dt.determineDaysOfMonths(currentMonth + 1, firstVal.minYear) -
+ currentMonthDate +
+ 1
+
+ // calculate the first tick position
+ firstTickPosition = remainingDaysOfFirstMonth * daysWidthOnXAxis
+ firstTickValue = Utils.monthMod(currentMonth + 1)
+
+ let year = currentYear + yrCounter
+ let month = Utils.monthMod(firstTickValue)
+ let value = firstTickValue
+ // it's Jan, so update the year
+ if (firstTickValue === 0) {
+ unit = 'year'
+ value = year
+ month = 1
+ yrCounter += 1
+ year = year + yrCounter
+ }
+
+ // push the first tick in the array
+ this.timeScaleArray.push({
+ position: firstTickPosition,
+ value,
+ unit,
+ year,
+ month,
+ })
+ } else {
+ // push the first tick in the array
+ this.timeScaleArray.push({
+ position: firstTickPosition,
+ value: firstTickValue,
+ unit,
+ year: currentYear,
+ month: Utils.monthMod(currentMonth),
+ })
+ }
+
+ let month = firstTickValue + 1
+ let pos = firstTickPosition
+
+ // keep drawing rest of the ticks
+ for (let i = 0, j = 1; i < numberOfMonths; i++, j++) {
+ month = Utils.monthMod(month)
+
+ if (month === 0) {
+ unit = 'year'
+ yrCounter += 1
+ } else {
+ unit = 'month'
+ }
+ let year = this._getYear(currentYear, month, yrCounter)
+
+ pos = dt.determineDaysOfMonths(month, year) * daysWidthOnXAxis + pos
+ let monthVal = month === 0 ? year : month
+ this.timeScaleArray.push({
+ position: pos,
+ value: monthVal,
+ unit,
+ year,
+ month: month === 0 ? 1 : month,
+ })
+ month++
+ }
+ }
+
+ generateDayScale({
+ firstVal,
+ currentMonth,
+ currentYear,
+ hoursWidthOnXAxis,
+ numberOfDays,
+ }) {
+ const dt = new DateTime(this.ctx)
+ let unit = 'day'
+ let firstTickValue = firstVal.minDate + 1
+ let date = firstTickValue
+
+ const changeMonth = (dateVal, month, year) => {
+ let monthdays = dt.determineDaysOfMonths(month + 1, year)
+
+ if (dateVal > monthdays) {
+ month = month + 1
+ date = 1
+ unit = 'month'
+ val = month
+ return month
+ }
+
+ return month
+ }
+
+ let remainingHours = 24 - firstVal.minHour
+ let yrCounter = 0
+
+ // calculate the first tick position
+ let firstTickPosition = remainingHours * hoursWidthOnXAxis
+
+ let val = firstTickValue
+ let month = changeMonth(date, currentMonth, currentYear)
+
+ if (firstVal.minHour === 0 && firstVal.minDate === 1) {
+ // the first value is the first day of month
+ firstTickPosition = 0
+ val = Utils.monthMod(firstVal.minMonth)
+ unit = 'month'
+ date = firstVal.minDate
+ // numberOfDays++
+ // removed the above line to fix https://github.com/apexcharts/apexcharts.js/issues/305#issuecomment-1019520513
+ } else if (
+ firstVal.minDate !== 1 &&
+ firstVal.minHour === 0 &&
+ firstVal.minMinute === 0
+ ) {
+ // fixes apexcharts/apexcharts.js/issues/1730
+ firstTickPosition = 0
+ firstTickValue = firstVal.minDate
+ date = firstTickValue
+ val = firstTickValue
+ // in case it's the last date of month, we need to check it
+ month = changeMonth(date, currentMonth, currentYear)
+ }
+
+ // push the first tick in the array
+ this.timeScaleArray.push({
+ position: firstTickPosition,
+ value: val,
+ unit,
+ year: this._getYear(currentYear, month, yrCounter),
+ month: Utils.monthMod(month),
+ day: date,
+ })
+
+ let pos = firstTickPosition
+ // keep drawing rest of the ticks
+ for (let i = 0; i < numberOfDays; i++) {
+ date += 1
+ unit = 'day'
+ month = changeMonth(
+ date,
+ month,
+ this._getYear(currentYear, month, yrCounter)
+ )
+
+ let year = this._getYear(currentYear, month, yrCounter)
+
+ pos = 24 * hoursWidthOnXAxis + pos
+ let value = date === 1 ? Utils.monthMod(month) : date
+ this.timeScaleArray.push({
+ position: pos,
+ value,
+ unit,
+ year,
+ month: Utils.monthMod(month),
+ day: value,
+ })
+ }
+ }
+
+ generateHourScale({
+ firstVal,
+ currentDate,
+ currentMonth,
+ currentYear,
+ minutesWidthOnXAxis,
+ numberOfHours,
+ }) {
+ const dt = new DateTime(this.ctx)
+
+ let yrCounter = 0
+ let unit = 'hour'
+
+ const changeDate = (dateVal, month) => {
+ let monthdays = dt.determineDaysOfMonths(month + 1, currentYear)
+ if (dateVal > monthdays) {
+ date = 1
+ month = month + 1
+ }
+ return { month, date }
+ }
+
+ const changeMonth = (dateVal, month) => {
+ let monthdays = dt.determineDaysOfMonths(month + 1, currentYear)
+ if (dateVal > monthdays) {
+ month = month + 1
+ return month
+ }
+
+ return month
+ }
+
+ // factor in minSeconds as well
+ let remainingMins = 60 - (firstVal.minMinute + firstVal.minSecond / 60.0)
+
+ let firstTickPosition = remainingMins * minutesWidthOnXAxis
+ let firstTickValue = firstVal.minHour + 1
+ let hour = firstTickValue
+
+ if (remainingMins === 60) {
+ firstTickPosition = 0
+ firstTickValue = firstVal.minHour
+ hour = firstTickValue
+ }
+
+ let date = currentDate
+
+ // we need to apply date switching logic here as well, to avoid duplicated labels
+ if (hour >= 24) {
+ hour = 0
+ date += 1
+ unit = 'day'
+ }
+
+ const checkNextMonth = changeDate(date, currentMonth)
+
+ let month = checkNextMonth.month
+ month = changeMonth(date, month)
+
+ // push the first tick in the array
+ this.timeScaleArray.push({
+ position: firstTickPosition,
+ value: firstTickValue,
+ unit,
+ day: date,
+ hour,
+ year: currentYear,
+ month: Utils.monthMod(month),
+ })
+
+ hour++
+
+ let pos = firstTickPosition
+ // keep drawing rest of the ticks
+ for (let i = 0; i < numberOfHours; i++) {
+ unit = 'hour'
+
+ if (hour >= 24) {
+ hour = 0
+ date += 1
+ unit = 'day'
+
+ const checkNextMonth = changeDate(date, month)
+
+ month = checkNextMonth.month
+ month = changeMonth(date, month)
+ }
+
+ let year = this._getYear(currentYear, month, yrCounter)
+ pos = 60 * minutesWidthOnXAxis + pos
+ let val = hour === 0 ? date : hour
+ this.timeScaleArray.push({
+ position: pos,
+ value: val,
+ unit,
+ hour,
+ day: date,
+ year,
+ month: Utils.monthMod(month),
+ })
+
+ hour++
+ }
+ }
+
+ generateMinuteScale({
+ currentMillisecond,
+ currentSecond,
+ currentMinute,
+ currentHour,
+ currentDate,
+ currentMonth,
+ currentYear,
+ minutesWidthOnXAxis,
+ secondsWidthOnXAxis,
+ numberOfMinutes,
+ }) {
+ let yrCounter = 0
+ let unit = 'minute'
+
+ let remainingSecs = 60 - currentSecond
+ let firstTickPosition =
+ (remainingSecs - currentMillisecond / 1000) * secondsWidthOnXAxis
+ let minute = currentMinute + 1
+
+ let date = currentDate
+ let month = currentMonth
+ let year = currentYear
+ let hour = currentHour
+
+ let pos = firstTickPosition
+ for (let i = 0; i < numberOfMinutes; i++) {
+ if (minute >= 60) {
+ minute = 0
+ hour += 1
+ if (hour === 24) {
+ hour = 0
+ }
+ }
+
+ this.timeScaleArray.push({
+ position: pos,
+ value: minute,
+ unit,
+ hour,
+ minute,
+ day: date,
+ year: this._getYear(year, month, yrCounter),
+ month: Utils.monthMod(month),
+ })
+
+ pos += minutesWidthOnXAxis
+ minute++
+ }
+ }
+
+ generateSecondScale({
+ currentMillisecond,
+ currentSecond,
+ currentMinute,
+ currentHour,
+ currentDate,
+ currentMonth,
+ currentYear,
+ secondsWidthOnXAxis,
+ numberOfSeconds,
+ }) {
+ let yrCounter = 0
+ let unit = 'second'
+
+ const remainingMillisecs = 1000 - currentMillisecond
+ let firstTickPosition = (remainingMillisecs / 1000) * secondsWidthOnXAxis
+
+ let second = currentSecond + 1
+ let minute = currentMinute
+ let date = currentDate
+ let month = currentMonth
+ let year = currentYear
+ let hour = currentHour
+
+ let pos = firstTickPosition
+ for (let i = 0; i < numberOfSeconds; i++) {
+ if (second >= 60) {
+ minute++
+ second = 0
+ if (minute >= 60) {
+ hour++
+ minute = 0
+ if (hour === 24) {
+ hour = 0
+ }
+ }
+ }
+
+ this.timeScaleArray.push({
+ position: pos,
+ value: second,
+ unit,
+ hour,
+ minute,
+ second,
+ day: date,
+ year: this._getYear(year, month, yrCounter),
+ month: Utils.monthMod(month),
+ })
+
+ pos += secondsWidthOnXAxis
+ second++
+ }
+ }
+
+ createRawDateString(ts, value) {
+ let raw = ts.year
+
+ if (ts.month === 0) {
+ // invalid month, correct it
+ ts.month = 1
+ }
+ raw += '-' + ('0' + ts.month.toString()).slice(-2)
+
+ // unit is day
+ if (ts.unit === 'day') {
+ raw += ts.unit === 'day' ? '-' + ('0' + value).slice(-2) : '-01'
+ } else {
+ raw += '-' + ('0' + (ts.day ? ts.day : '1')).slice(-2)
+ }
+
+ // unit is hour
+ if (ts.unit === 'hour') {
+ raw += ts.unit === 'hour' ? 'T' + ('0' + value).slice(-2) : 'T00'
+ } else {
+ raw += 'T' + ('0' + (ts.hour ? ts.hour : '0')).slice(-2)
+ }
+
+ if (ts.unit === 'minute') {
+ raw += ':' + ('0' + value).slice(-2)
+ } else {
+ raw += ':' + (ts.minute ? ('0' + ts.minute).slice(-2) : '00')
+ }
+
+ if (ts.unit === 'second') {
+ raw += ':' + ('0' + value).slice(-2)
+ } else {
+ raw += ':00'
+ }
+
+ if (this.utc) {
+ raw += '.000Z'
+ }
+ return raw
+ }
+
+ formatDates(filteredTimeScale) {
+ const w = this.w
+
+ const reformattedTimescaleArray = filteredTimeScale.map((ts) => {
+ let value = ts.value.toString()
+
+ let dt = new DateTime(this.ctx)
+
+ const raw = this.createRawDateString(ts, value)
+
+ let dateToFormat = dt.getDate(dt.parseDate(raw))
+ if (!this.utc) {
+ // Fixes #1726, #1544, #1485, #1255
+ dateToFormat = dt.getDate(dt.parseDateWithTimezone(raw))
+ }
+
+ if (w.config.xaxis.labels.format === undefined) {
+ let customFormat = 'dd MMM'
+ const dtFormatter = w.config.xaxis.labels.datetimeFormatter
+ if (ts.unit === 'year') customFormat = dtFormatter.year
+ if (ts.unit === 'month') customFormat = dtFormatter.month
+ if (ts.unit === 'day') customFormat = dtFormatter.day
+ if (ts.unit === 'hour') customFormat = dtFormatter.hour
+ if (ts.unit === 'minute') customFormat = dtFormatter.minute
+ if (ts.unit === 'second') customFormat = dtFormatter.second
+
+ value = dt.formatDate(dateToFormat, customFormat)
+ } else {
+ value = dt.formatDate(dateToFormat, w.config.xaxis.labels.format)
+ }
+
+ return {
+ dateString: raw,
+ position: ts.position,
+ value,
+ unit: ts.unit,
+ year: ts.year,
+ month: ts.month,
+ }
+ })
+
+ return reformattedTimescaleArray
+ }
+
+ removeOverlappingTS(arr) {
+ const graphics = new Graphics(this.ctx)
+
+ let equalLabelLengthFlag = false // These labels got same length?
+ let constantLabelWidth // If true, what is the constant length to use
+ if (
+ arr.length > 0 && // check arr length
+ arr[0].value && // check arr[0] contains value
+ arr.every((lb) => lb.value.length === arr[0].value.length) // check every arr label value is the same as the first one
+ ) {
+ equalLabelLengthFlag = true // These labels got same length
+ constantLabelWidth = graphics.getTextRects(arr[0].value).width // The constant label width to use
+ }
+
+ let lastDrawnIndex = 0
+
+ let filteredArray = arr.map((item, index) => {
+ if (index > 0 && this.w.config.xaxis.labels.hideOverlappingLabels) {
+ const prevLabelWidth = !equalLabelLengthFlag // if vary in label length
+ ? graphics.getTextRects(arr[lastDrawnIndex].value).width // get individual length
+ : constantLabelWidth // else: use constant length
+ const prevPos = arr[lastDrawnIndex].position
+ const pos = item.position
+
+ if (pos > prevPos + prevLabelWidth + 10) {
+ lastDrawnIndex = index
+ return item
+ } else {
+ return null
+ }
+ } else {
+ return item
+ }
+ })
+
+ filteredArray = filteredArray.filter((f) => f !== null)
+
+ return filteredArray
+ }
+
+ _getYear(currentYear, month, yrCounter) {
+ return currentYear + Math.floor(month / 12) + yrCounter
+ }
+}
+
+export default TimeScale
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/TitleSubtitle.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/TitleSubtitle.js
new file mode 100644
index 0000000..2b3f88e
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/TitleSubtitle.js
@@ -0,0 +1,52 @@
+import Graphics from './Graphics'
+
+export default class TitleSubtitle {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ draw() {
+ this.drawTitleSubtitle('title')
+ this.drawTitleSubtitle('subtitle')
+ }
+
+ drawTitleSubtitle(type) {
+ let w = this.w
+ const tsConfig = type === 'title' ? w.config.title : w.config.subtitle
+
+ let x = w.globals.svgWidth / 2
+ let y = tsConfig.offsetY
+ let textAnchor = 'middle'
+
+ if (tsConfig.align === 'left') {
+ x = 10
+ textAnchor = 'start'
+ } else if (tsConfig.align === 'right') {
+ x = w.globals.svgWidth - 10
+ textAnchor = 'end'
+ }
+
+ x = x + tsConfig.offsetX
+ y = y + parseInt(tsConfig.style.fontSize, 10) + tsConfig.margin / 2
+
+ if (tsConfig.text !== undefined) {
+ let graphics = new Graphics(this.ctx)
+ let titleText = graphics.drawText({
+ x,
+ y,
+ text: tsConfig.text,
+ textAnchor,
+ fontSize: tsConfig.style.fontSize,
+ fontFamily: tsConfig.style.fontFamily,
+ fontWeight: tsConfig.style.fontWeight,
+ foreColor: tsConfig.style.color,
+ opacity: 1
+ })
+
+ titleText.node.setAttribute('class', `apexcharts-${type}-text`)
+
+ w.globals.dom.Paper.add(titleText)
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Toolbar.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Toolbar.js
new file mode 100644
index 0000000..774ad79
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/Toolbar.js
@@ -0,0 +1,521 @@
+import Graphics from './Graphics'
+import Exports from './Exports'
+import Scales from './Scales'
+import Utils from './../utils/Utils'
+import icoPan from './.assets/ico-pan-hand.svg'
+import icoZoom from './.assets/ico-zoom-in.svg'
+import icoReset from './.assets/ico-home.svg'
+import icoZoomIn from './.assets/ico-plus.svg'
+import icoZoomOut from './.assets/ico-minus.svg'
+import icoSelect from './.assets/ico-select.svg'
+import icoMenu from './.assets/ico-menu.svg'
+
+/**
+ * ApexCharts Toolbar Class for creating toolbar in axis based charts.
+ *
+ * @module Toolbar
+ **/
+
+export default class Toolbar {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ const w = this.w
+
+ this.ev = this.w.config.chart.events
+ this.selectedClass = 'apexcharts-selected'
+
+ this.localeValues = this.w.globals.locale.toolbar
+
+ this.minX = w.globals.minX
+ this.maxX = w.globals.maxX
+ }
+
+ createToolbar() {
+ let w = this.w
+
+ const createDiv = () => {
+ return document.createElement('div')
+ }
+ const elToolbarWrap = createDiv()
+ elToolbarWrap.setAttribute('class', 'apexcharts-toolbar')
+ elToolbarWrap.style.top = w.config.chart.toolbar.offsetY + 'px'
+ elToolbarWrap.style.right = -w.config.chart.toolbar.offsetX + 3 + 'px'
+ w.globals.dom.elWrap.appendChild(elToolbarWrap)
+
+ this.elZoom = createDiv()
+ this.elZoomIn = createDiv()
+ this.elZoomOut = createDiv()
+ this.elPan = createDiv()
+ this.elSelection = createDiv()
+ this.elZoomReset = createDiv()
+ this.elMenuIcon = createDiv()
+ this.elMenu = createDiv()
+ this.elCustomIcons = []
+
+ this.t = w.config.chart.toolbar.tools
+
+ if (Array.isArray(this.t.customIcons)) {
+ for (let i = 0; i < this.t.customIcons.length; i++) {
+ this.elCustomIcons.push(createDiv())
+ }
+ }
+
+ let toolbarControls = []
+
+ const appendZoomControl = (type, el, ico) => {
+ const tool = type.toLowerCase()
+ if (this.t[tool] && w.config.chart.zoom.enabled) {
+ toolbarControls.push({
+ el,
+ icon: typeof this.t[tool] === 'string' ? this.t[tool] : ico,
+ title: this.localeValues[type],
+ class: `apexcharts-${tool}-icon`,
+ })
+ }
+ }
+
+ appendZoomControl('zoomIn', this.elZoomIn, icoZoomIn)
+ appendZoomControl('zoomOut', this.elZoomOut, icoZoomOut)
+
+ const zoomSelectionCtrls = (z) => {
+ if (this.t[z] && w.config.chart[z].enabled) {
+ toolbarControls.push({
+ el: z === 'zoom' ? this.elZoom : this.elSelection,
+ icon:
+ typeof this.t[z] === 'string'
+ ? this.t[z]
+ : z === 'zoom'
+ ? icoZoom
+ : icoSelect,
+ title:
+ this.localeValues[z === 'zoom' ? 'selectionZoom' : 'selection'],
+ class: w.globals.isTouchDevice
+ ? 'apexcharts-element-hidden'
+ : `apexcharts-${z}-icon`,
+ })
+ }
+ }
+ zoomSelectionCtrls('zoom')
+ zoomSelectionCtrls('selection')
+
+ if (this.t.pan && w.config.chart.zoom.enabled) {
+ toolbarControls.push({
+ el: this.elPan,
+ icon: typeof this.t.pan === 'string' ? this.t.pan : icoPan,
+ title: this.localeValues.pan,
+ class: w.globals.isTouchDevice
+ ? 'apexcharts-element-hidden'
+ : 'apexcharts-pan-icon',
+ })
+ }
+
+ appendZoomControl('reset', this.elZoomReset, icoReset)
+
+ if (this.t.download) {
+ toolbarControls.push({
+ el: this.elMenuIcon,
+ icon: typeof this.t.download === 'string' ? this.t.download : icoMenu,
+ title: this.localeValues.menu,
+ class: 'apexcharts-menu-icon',
+ })
+ }
+
+ for (let i = 0; i < this.elCustomIcons.length; i++) {
+ toolbarControls.push({
+ el: this.elCustomIcons[i],
+ icon: this.t.customIcons[i].icon,
+ title: this.t.customIcons[i].title,
+ index: this.t.customIcons[i].index,
+ class: 'apexcharts-toolbar-custom-icon ' + this.t.customIcons[i].class,
+ })
+ }
+
+ toolbarControls.forEach((t, index) => {
+ if (t.index) {
+ Utils.moveIndexInArray(toolbarControls, index, t.index)
+ }
+ })
+
+ for (let i = 0; i < toolbarControls.length; i++) {
+ Graphics.setAttrs(toolbarControls[i].el, {
+ class: toolbarControls[i].class,
+ title: toolbarControls[i].title,
+ })
+
+ toolbarControls[i].el.innerHTML = toolbarControls[i].icon
+ elToolbarWrap.appendChild(toolbarControls[i].el)
+ }
+
+ this._createHamburgerMenu(elToolbarWrap)
+
+ if (w.globals.zoomEnabled) {
+ this.elZoom.classList.add(this.selectedClass)
+ } else if (w.globals.panEnabled) {
+ this.elPan.classList.add(this.selectedClass)
+ } else if (w.globals.selectionEnabled) {
+ this.elSelection.classList.add(this.selectedClass)
+ }
+
+ this.addToolbarEventListeners()
+ }
+
+ _createHamburgerMenu(parent) {
+ this.elMenuItems = []
+ parent.appendChild(this.elMenu)
+
+ Graphics.setAttrs(this.elMenu, {
+ class: 'apexcharts-menu',
+ })
+
+ const menuItems = [
+ {
+ name: 'exportSVG',
+ title: this.localeValues.exportToSVG,
+ },
+ {
+ name: 'exportPNG',
+ title: this.localeValues.exportToPNG,
+ },
+ {
+ name: 'exportCSV',
+ title: this.localeValues.exportToCSV,
+ },
+ ]
+
+ for (let i = 0; i < menuItems.length; i++) {
+ this.elMenuItems.push(document.createElement('div'))
+ this.elMenuItems[i].innerHTML = menuItems[i].title
+ Graphics.setAttrs(this.elMenuItems[i], {
+ class: `apexcharts-menu-item ${menuItems[i].name}`,
+ title: menuItems[i].title,
+ })
+ this.elMenu.appendChild(this.elMenuItems[i])
+ }
+ }
+
+ addToolbarEventListeners() {
+ this.elZoomReset.addEventListener('click', this.handleZoomReset.bind(this))
+ this.elSelection.addEventListener(
+ 'click',
+ this.toggleZoomSelection.bind(this, 'selection')
+ )
+ this.elZoom.addEventListener(
+ 'click',
+ this.toggleZoomSelection.bind(this, 'zoom')
+ )
+ this.elZoomIn.addEventListener('click', this.handleZoomIn.bind(this))
+ this.elZoomOut.addEventListener('click', this.handleZoomOut.bind(this))
+ this.elPan.addEventListener('click', this.togglePanning.bind(this))
+ this.elMenuIcon.addEventListener('click', this.toggleMenu.bind(this))
+ this.elMenuItems.forEach((m) => {
+ if (m.classList.contains('exportSVG')) {
+ m.addEventListener('click', this.handleDownload.bind(this, 'svg'))
+ } else if (m.classList.contains('exportPNG')) {
+ m.addEventListener('click', this.handleDownload.bind(this, 'png'))
+ } else if (m.classList.contains('exportCSV')) {
+ m.addEventListener('click', this.handleDownload.bind(this, 'csv'))
+ }
+ })
+ for (let i = 0; i < this.t.customIcons.length; i++) {
+ this.elCustomIcons[i].addEventListener(
+ 'click',
+ this.t.customIcons[i].click.bind(this, this.ctx, this.ctx.w)
+ )
+ }
+ }
+
+ toggleZoomSelection(type) {
+ const charts = this.ctx.getSyncedCharts()
+
+ charts.forEach((ch) => {
+ ch.ctx.toolbar.toggleOtherControls()
+
+ let el =
+ type === 'selection'
+ ? ch.ctx.toolbar.elSelection
+ : ch.ctx.toolbar.elZoom
+ let enabledType =
+ type === 'selection' ? 'selectionEnabled' : 'zoomEnabled'
+
+ ch.w.globals[enabledType] = !ch.w.globals[enabledType]
+
+ if (!el.classList.contains(ch.ctx.toolbar.selectedClass)) {
+ el.classList.add(ch.ctx.toolbar.selectedClass)
+ } else {
+ el.classList.remove(ch.ctx.toolbar.selectedClass)
+ }
+ })
+ }
+
+ getToolbarIconsReference() {
+ const w = this.w
+ if (!this.elZoom) {
+ this.elZoom = w.globals.dom.baseEl.querySelector('.apexcharts-zoom-icon')
+ }
+ if (!this.elPan) {
+ this.elPan = w.globals.dom.baseEl.querySelector('.apexcharts-pan-icon')
+ }
+ if (!this.elSelection) {
+ this.elSelection = w.globals.dom.baseEl.querySelector(
+ '.apexcharts-selection-icon'
+ )
+ }
+ }
+
+ enableZoomPanFromToolbar(type) {
+ this.toggleOtherControls()
+
+ type === 'pan'
+ ? (this.w.globals.panEnabled = true)
+ : (this.w.globals.zoomEnabled = true)
+
+ const el = type === 'pan' ? this.elPan : this.elZoom
+ const el2 = type === 'pan' ? this.elZoom : this.elPan
+ if (el) {
+ el.classList.add(this.selectedClass)
+ }
+ if (el2) {
+ el2.classList.remove(this.selectedClass)
+ }
+ }
+
+ togglePanning() {
+ const charts = this.ctx.getSyncedCharts()
+
+ charts.forEach((ch) => {
+ ch.ctx.toolbar.toggleOtherControls()
+ ch.w.globals.panEnabled = !ch.w.globals.panEnabled
+
+ if (
+ !ch.ctx.toolbar.elPan.classList.contains(ch.ctx.toolbar.selectedClass)
+ ) {
+ ch.ctx.toolbar.elPan.classList.add(ch.ctx.toolbar.selectedClass)
+ } else {
+ ch.ctx.toolbar.elPan.classList.remove(ch.ctx.toolbar.selectedClass)
+ }
+ })
+ }
+
+ toggleOtherControls() {
+ const w = this.w
+ w.globals.panEnabled = false
+ w.globals.zoomEnabled = false
+ w.globals.selectionEnabled = false
+
+ this.getToolbarIconsReference()
+
+ const toggleEls = [this.elPan, this.elSelection, this.elZoom]
+ toggleEls.forEach((el) => {
+ if (el) {
+ el.classList.remove(this.selectedClass)
+ }
+ })
+ }
+
+ handleZoomIn() {
+ const w = this.w
+
+ if (w.globals.isRangeBar) {
+ this.minX = w.globals.minY
+ this.maxX = w.globals.maxY
+ }
+
+ const centerX = (this.minX + this.maxX) / 2
+ let newMinX = (this.minX + centerX) / 2
+ let newMaxX = (this.maxX + centerX) / 2
+
+ const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX)
+
+ if (!w.globals.disableZoomIn) {
+ this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX)
+ }
+ }
+
+ handleZoomOut() {
+ const w = this.w
+
+ if (w.globals.isRangeBar) {
+ this.minX = w.globals.minY
+ this.maxX = w.globals.maxY
+ }
+
+ // avoid zooming out beyond 1000 which may result in NaN values being printed on x-axis
+ if (
+ w.config.xaxis.type === 'datetime' &&
+ new Date(this.minX).getUTCFullYear() < 1000
+ ) {
+ return
+ }
+
+ const centerX = (this.minX + this.maxX) / 2
+ let newMinX = this.minX - (centerX - this.minX)
+ let newMaxX = this.maxX - (centerX - this.maxX)
+
+ const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX)
+
+ if (!w.globals.disableZoomOut) {
+ this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX)
+ }
+ }
+
+ _getNewMinXMaxX(newMinX, newMaxX) {
+ const shouldFloor = this.w.config.xaxis.convertedCatToNumeric
+ return {
+ minX: shouldFloor ? Math.floor(newMinX) : newMinX,
+ maxX: shouldFloor ? Math.floor(newMaxX) : newMaxX,
+ }
+ }
+
+ zoomUpdateOptions(newMinX, newMaxX) {
+ const w = this.w
+
+ if (newMinX === undefined && newMaxX === undefined) {
+ this.handleZoomReset()
+ return
+ }
+
+ if (w.config.xaxis.convertedCatToNumeric) {
+ // in category charts, avoid zooming out beyond min and max
+ if (newMinX < 1) {
+ newMinX = 1
+ newMaxX = w.globals.dataPoints
+ }
+
+ if (newMaxX - newMinX < 2) {
+ return
+ }
+ }
+
+ let xaxis = {
+ min: newMinX,
+ max: newMaxX,
+ }
+
+ const beforeZoomRange = this.getBeforeZoomRange(xaxis)
+ if (beforeZoomRange) {
+ xaxis = beforeZoomRange.xaxis
+ }
+
+ let options = {
+ xaxis,
+ }
+
+ let yaxis = Utils.clone(w.globals.initialConfig.yaxis)
+
+ if (!w.config.chart.group) {
+ // if chart in a group, prevent yaxis update here
+ // fix issue #650
+ options.yaxis = yaxis
+ }
+
+ this.w.globals.zoomed = true
+
+ this.ctx.updateHelpers._updateOptions(
+ options,
+ false,
+ this.w.config.chart.animations.dynamicAnimation.enabled
+ )
+
+ this.zoomCallback(xaxis, yaxis)
+ }
+
+ zoomCallback(xaxis, yaxis) {
+ if (typeof this.ev.zoomed === 'function') {
+ this.ev.zoomed(this.ctx, { xaxis, yaxis })
+ }
+ }
+
+ getBeforeZoomRange(xaxis, yaxis) {
+ let newRange = null
+ if (typeof this.ev.beforeZoom === 'function') {
+ newRange = this.ev.beforeZoom(this, { xaxis, yaxis })
+ }
+
+ return newRange
+ }
+
+ toggleMenu() {
+ window.setTimeout(() => {
+ if (this.elMenu.classList.contains('apexcharts-menu-open')) {
+ this.elMenu.classList.remove('apexcharts-menu-open')
+ } else {
+ this.elMenu.classList.add('apexcharts-menu-open')
+ }
+ }, 0)
+ }
+
+ handleDownload(type) {
+ const w = this.w
+ const exprt = new Exports(this.ctx)
+ switch (type) {
+ case 'svg':
+ exprt.exportToSVG(this.ctx)
+ break
+ case 'png':
+ exprt.exportToPng(this.ctx)
+ break
+ case 'csv':
+ exprt.exportToCSV({
+ series: w.config.series,
+ columnDelimiter: w.config.chart.toolbar.export.csv.columnDelimiter,
+ })
+ break
+ }
+ }
+
+ handleZoomReset(e) {
+ const charts = this.ctx.getSyncedCharts()
+
+ charts.forEach((ch) => {
+ let w = ch.w
+
+ // forget lastXAxis min/max as reset button isn't resetting the x-axis completely if zoomX is called before
+ w.globals.lastXAxis.min = w.globals.initialConfig.xaxis.min
+ w.globals.lastXAxis.max = w.globals.initialConfig.xaxis.max
+
+ ch.updateHelpers.revertDefaultAxisMinMax()
+
+ if (typeof w.config.chart.events.beforeResetZoom === 'function') {
+ // here, user get an option to control xaxis and yaxis when resetZoom is called
+ // at this point, whatever is returned from w.config.chart.events.beforeResetZoom
+ // is set as the new xaxis/yaxis min/max
+ const resetZoomRange = w.config.chart.events.beforeResetZoom(ch, w)
+
+ if (resetZoomRange) {
+ ch.updateHelpers.revertDefaultAxisMinMax(resetZoomRange)
+ }
+ }
+
+ if (typeof w.config.chart.events.zoomed === 'function') {
+ ch.ctx.toolbar.zoomCallback({
+ min: w.config.xaxis.min,
+ max: w.config.xaxis.max,
+ })
+ }
+
+ w.globals.zoomed = false
+
+ // if user has some series collapsed before hitting zoom reset button,
+ // those series should stay collapsed
+ let series = ch.ctx.series.emptyCollapsedSeries(
+ Utils.clone(w.globals.initialSeries)
+ )
+
+ ch.updateHelpers._updateSeries(
+ series,
+ w.config.chart.animations.dynamicAnimation.enabled
+ )
+ })
+ }
+
+ destroy() {
+ this.elZoom = null
+ this.elZoomIn = null
+ this.elZoomOut = null
+ this.elPan = null
+ this.elSelection = null
+ this.elZoomReset = null
+ this.elMenuIcon = null
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/ZoomPanSelection.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/ZoomPanSelection.js
new file mode 100644
index 0000000..8146fb2
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/ZoomPanSelection.js
@@ -0,0 +1,884 @@
+import Graphics from './Graphics'
+import Utils from './../utils/Utils'
+import Toolbar from './Toolbar'
+
+/**
+ * ApexCharts Zoom Class for handling zooming and panning on axes based charts.
+ *
+ * @module ZoomPanSelection
+ **/
+
+export default class ZoomPanSelection extends Toolbar {
+ constructor(ctx) {
+ super(ctx)
+
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.dragged = false
+ this.graphics = new Graphics(this.ctx)
+
+ this.eventList = [
+ 'mousedown',
+ 'mouseleave',
+ 'mousemove',
+ 'touchstart',
+ 'touchmove',
+ 'mouseup',
+ 'touchend',
+ 'wheel',
+ ]
+
+ this.clientX = 0
+ this.clientY = 0
+ this.startX = 0
+ this.endX = 0
+ this.dragX = 0
+ this.startY = 0
+ this.endY = 0
+ this.dragY = 0
+ this.moveDirection = 'none'
+
+ this.debounceTimer = null
+ this.debounceDelay = 100
+ this.wheelDelay = 400
+ }
+
+ init({ xyRatios }) {
+ let w = this.w
+ let me = this
+
+ this.xyRatios = xyRatios
+
+ this.zoomRect = this.graphics.drawRect(0, 0, 0, 0)
+ this.selectionRect = this.graphics.drawRect(0, 0, 0, 0)
+ this.gridRect = w.globals.dom.baseEl.querySelector('.apexcharts-grid')
+
+ this.zoomRect.node.classList.add('apexcharts-zoom-rect')
+ this.selectionRect.node.classList.add('apexcharts-selection-rect')
+ w.globals.dom.elGraphical.add(this.zoomRect)
+ w.globals.dom.elGraphical.add(this.selectionRect)
+
+ if (w.config.chart.selection.type === 'x') {
+ this.slDraggableRect = this.selectionRect
+ .draggable({
+ minX: 0,
+ minY: 0,
+ maxX: w.globals.gridWidth,
+ maxY: w.globals.gridHeight,
+ })
+ .on('dragmove', this.selectionDragging.bind(this, 'dragging'))
+ } else if (w.config.chart.selection.type === 'y') {
+ this.slDraggableRect = this.selectionRect
+ .draggable({
+ minX: 0,
+ maxX: w.globals.gridWidth,
+ })
+ .on('dragmove', this.selectionDragging.bind(this, 'dragging'))
+ } else {
+ this.slDraggableRect = this.selectionRect
+ .draggable()
+ .on('dragmove', this.selectionDragging.bind(this, 'dragging'))
+ }
+ this.preselectedSelection()
+
+ this.hoverArea = w.globals.dom.baseEl.querySelector(
+ `${w.globals.chartClass} .apexcharts-svg`
+ )
+ this.hoverArea.classList.add('apexcharts-zoomable')
+
+ this.eventList.forEach((event) => {
+ this.hoverArea.addEventListener(
+ event,
+ me.svgMouseEvents.bind(me, xyRatios),
+ {
+ capture: false,
+ passive: true,
+ }
+ )
+ })
+
+ if (w.config.chart.zoom.allowMouseWheelZoom) {
+ this.hoverArea.addEventListener('wheel', me.mouseWheelEvent.bind(me), {
+ capture: false,
+ passive: false,
+ })
+ }
+ }
+
+ // remove the event listeners which were previously added on hover area
+ destroy() {
+ if (this.slDraggableRect) {
+ this.slDraggableRect.draggable(false)
+ this.slDraggableRect.off()
+ this.selectionRect.off()
+ }
+
+ this.selectionRect = null
+ this.zoomRect = null
+ this.gridRect = null
+ }
+
+ svgMouseEvents(xyRatios, e) {
+ let w = this.w
+ let me = this
+ const toolbar = this.ctx.toolbar
+
+ let zoomtype = w.globals.zoomEnabled
+ ? w.config.chart.zoom.type
+ : w.config.chart.selection.type
+
+ const autoSelected = w.config.chart.toolbar.autoSelected
+
+ if (e.shiftKey) {
+ this.shiftWasPressed = true
+ toolbar.enableZoomPanFromToolbar(autoSelected === 'pan' ? 'zoom' : 'pan')
+ } else {
+ if (this.shiftWasPressed) {
+ toolbar.enableZoomPanFromToolbar(autoSelected)
+ this.shiftWasPressed = false
+ }
+ }
+
+ if (!e.target) return
+
+ const tc = e.target.classList
+ let pc
+ if (e.target.parentNode && e.target.parentNode !== null) {
+ pc = e.target.parentNode.classList
+ }
+ const falsePositives =
+ tc.contains('apexcharts-selection-rect') ||
+ tc.contains('apexcharts-legend-marker') ||
+ tc.contains('apexcharts-legend-text') ||
+ (pc && pc.contains('apexcharts-toolbar'))
+
+ if (falsePositives) return
+
+ me.clientX =
+ e.type === 'touchmove' || e.type === 'touchstart'
+ ? e.touches[0].clientX
+ : e.type === 'touchend'
+ ? e.changedTouches[0].clientX
+ : e.clientX
+ me.clientY =
+ e.type === 'touchmove' || e.type === 'touchstart'
+ ? e.touches[0].clientY
+ : e.type === 'touchend'
+ ? e.changedTouches[0].clientY
+ : e.clientY
+
+ if (e.type === 'mousedown' && e.which === 1) {
+ let gridRectDim = me.gridRect.getBoundingClientRect()
+
+ me.startX = me.clientX - gridRectDim.left
+ me.startY = me.clientY - gridRectDim.top
+
+ me.dragged = false
+ me.w.globals.mousedown = true
+ }
+
+ if ((e.type === 'mousemove' && e.which === 1) || e.type === 'touchmove') {
+ me.dragged = true
+
+ if (w.globals.panEnabled) {
+ w.globals.selection = null
+ if (me.w.globals.mousedown) {
+ me.panDragging({
+ context: me,
+ zoomtype,
+ xyRatios,
+ })
+ }
+ } else {
+ if (
+ (me.w.globals.mousedown && w.globals.zoomEnabled) ||
+ (me.w.globals.mousedown && w.globals.selectionEnabled)
+ ) {
+ me.selection = me.selectionDrawing({
+ context: me,
+ zoomtype,
+ })
+ }
+ }
+ }
+
+ if (
+ e.type === 'mouseup' ||
+ e.type === 'touchend' ||
+ e.type === 'mouseleave'
+ ) {
+ // we will be calling getBoundingClientRect on each mousedown/mousemove/mouseup
+ let gridRectDim = me.gridRect?.getBoundingClientRect()
+
+ if (gridRectDim && me.w.globals.mousedown) {
+ // user released the drag, now do all the calculations
+ me.endX = me.clientX - gridRectDim.left
+ me.endY = me.clientY - gridRectDim.top
+ me.dragX = Math.abs(me.endX - me.startX)
+ me.dragY = Math.abs(me.endY - me.startY)
+
+ if (w.globals.zoomEnabled || w.globals.selectionEnabled) {
+ me.selectionDrawn({
+ context: me,
+ zoomtype,
+ })
+ }
+
+ if (w.globals.panEnabled && w.config.xaxis.convertedCatToNumeric) {
+ me.delayedPanScrolled()
+ }
+ }
+
+ if (w.globals.zoomEnabled) {
+ me.hideSelectionRect(this.selectionRect)
+ }
+
+ me.dragged = false
+ me.w.globals.mousedown = false
+ }
+
+ this.makeSelectionRectDraggable()
+ }
+
+ mouseWheelEvent(e) {
+ const w = this.w
+ e.preventDefault()
+
+ const now = Date.now()
+
+ // Execute immediately if it's the first action or enough time has passed
+ if (now - w.globals.lastWheelExecution > this.wheelDelay) {
+ this.executeMouseWheelZoom(e)
+ w.globals.lastWheelExecution = now
+ }
+
+ if (this.debounceTimer) clearTimeout(this.debounceTimer)
+
+ this.debounceTimer = setTimeout(() => {
+ if (now - w.globals.lastWheelExecution > this.wheelDelay) {
+ this.executeMouseWheelZoom(e)
+ w.globals.lastWheelExecution = now
+ }
+ }, this.debounceDelay)
+ }
+
+ executeMouseWheelZoom(e) {
+ const w = this.w
+ this.minX = w.globals.isRangeBar ? w.globals.minY : w.globals.minX
+ this.maxX = w.globals.isRangeBar ? w.globals.maxY : w.globals.maxX
+
+ // Calculate the relative position of the mouse on the chart
+ const gridRectDim = this.gridRect?.getBoundingClientRect()
+ if (!gridRectDim) return
+
+ const mouseX = (e.clientX - gridRectDim.left) / gridRectDim.width
+
+ const currentMinX = this.minX
+ const currentMaxX = this.maxX
+ const totalX = currentMaxX - currentMinX
+
+ // Determine zoom factor
+ const zoomFactorIn = 0.5
+ const zoomFactorOut = 1.5
+ let zoomRange
+
+ let newMinX, newMaxX
+ if (e.deltaY < 0) {
+ // Zoom In
+ zoomRange = zoomFactorIn * totalX
+ const midPoint = currentMinX + mouseX * totalX
+ newMinX = midPoint - zoomRange / 2
+ newMaxX = midPoint + zoomRange / 2
+ } else {
+ // Zoom Out
+ zoomRange = zoomFactorOut * totalX
+ newMinX = currentMinX - zoomRange / 2
+ newMaxX = currentMaxX + zoomRange / 2
+ }
+
+ // Constrain within original chart bounds
+ if (!w.globals.isRangeBar) {
+ newMinX = Math.max(newMinX, w.globals.initialMinX)
+ newMaxX = Math.min(newMaxX, w.globals.initialMaxX)
+
+ // Ensure minimum range
+ const minRange = (w.globals.initialMaxX - w.globals.initialMinX) * 0.01
+ if (newMaxX - newMinX < minRange) {
+ const midPoint = (newMinX + newMaxX) / 2
+ newMinX = midPoint - minRange / 2
+ newMaxX = midPoint + minRange / 2
+ }
+ }
+
+ const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX)
+
+ // Apply zoom if valid
+ if (!isNaN(newMinXMaxX.minX) && !isNaN(newMinXMaxX.maxX)) {
+ this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX)
+ }
+ }
+
+ makeSelectionRectDraggable() {
+ const w = this.w
+
+ if (!this.selectionRect) return
+
+ const rectDim = this.selectionRect.node.getBoundingClientRect()
+ if (rectDim.width > 0 && rectDim.height > 0) {
+ this.slDraggableRect
+ .selectize({
+ points: 'l, r',
+ pointSize: 8,
+ pointType: 'rect',
+ })
+ .resize({
+ constraint: {
+ minX: 0,
+ minY: 0,
+ maxX: w.globals.gridWidth,
+ maxY: w.globals.gridHeight,
+ },
+ })
+ .on('resizing', this.selectionDragging.bind(this, 'resizing'))
+ }
+ }
+
+ preselectedSelection() {
+ const w = this.w
+ const xyRatios = this.xyRatios
+
+ if (!w.globals.zoomEnabled) {
+ if (
+ typeof w.globals.selection !== 'undefined' &&
+ w.globals.selection !== null
+ ) {
+ this.drawSelectionRect(w.globals.selection)
+ } else {
+ if (
+ w.config.chart.selection.xaxis.min !== undefined &&
+ w.config.chart.selection.xaxis.max !== undefined
+ ) {
+ let x =
+ (w.config.chart.selection.xaxis.min - w.globals.minX) /
+ xyRatios.xRatio
+ let width =
+ w.globals.gridWidth -
+ (w.globals.maxX - w.config.chart.selection.xaxis.max) /
+ xyRatios.xRatio -
+ x
+ if (w.globals.isRangeBar) {
+ // rangebars put datetime data in y axis
+ x = // calculation: (selection left time - chart left time) / milliseconds per pixel = selection X value in pixels
+ (w.config.chart.selection.xaxis.min -
+ w.globals.yAxisScale[0].niceMin) /
+ xyRatios.invertedYRatio
+ width =
+ (w.config.chart.selection.xaxis.max -
+ w.config.chart.selection.xaxis.min) /
+ xyRatios.invertedYRatio
+ }
+ let selectionRect = {
+ x,
+ y: 0,
+ width,
+ height: w.globals.gridHeight,
+ translateX: 0,
+ translateY: 0,
+ selectionEnabled: true,
+ }
+ this.drawSelectionRect(selectionRect)
+ this.makeSelectionRectDraggable()
+ if (typeof w.config.chart.events.selection === 'function') {
+ w.config.chart.events.selection(this.ctx, {
+ xaxis: {
+ min: w.config.chart.selection.xaxis.min,
+ max: w.config.chart.selection.xaxis.max,
+ },
+ yaxis: {},
+ })
+ }
+ }
+ }
+ }
+ }
+
+ drawSelectionRect({ x, y, width, height, translateX = 0, translateY = 0 }) {
+ const w = this.w
+ const zoomRect = this.zoomRect
+ const selectionRect = this.selectionRect
+ if (this.dragged || w.globals.selection !== null) {
+ let scalingAttrs = {
+ transform: 'translate(' + translateX + ', ' + translateY + ')',
+ }
+
+ // change styles based on zoom or selection
+ // zoom is Enabled and user has dragged, so draw blue rect
+ if (w.globals.zoomEnabled && this.dragged) {
+ if (width < 0) width = 1 // fixes apexcharts.js#1168
+ zoomRect.attr({
+ x,
+ y,
+ width,
+ height,
+ fill: w.config.chart.zoom.zoomedArea.fill.color,
+ 'fill-opacity': w.config.chart.zoom.zoomedArea.fill.opacity,
+ stroke: w.config.chart.zoom.zoomedArea.stroke.color,
+ 'stroke-width': w.config.chart.zoom.zoomedArea.stroke.width,
+ 'stroke-opacity': w.config.chart.zoom.zoomedArea.stroke.opacity,
+ })
+ Graphics.setAttrs(zoomRect.node, scalingAttrs)
+ }
+
+ // selection is enabled
+ if (w.globals.selectionEnabled) {
+ selectionRect.attr({
+ x,
+ y,
+ width: width > 0 ? width : 0,
+ height: height > 0 ? height : 0,
+ fill: w.config.chart.selection.fill.color,
+ 'fill-opacity': w.config.chart.selection.fill.opacity,
+ stroke: w.config.chart.selection.stroke.color,
+ 'stroke-width': w.config.chart.selection.stroke.width,
+ 'stroke-dasharray': w.config.chart.selection.stroke.dashArray,
+ 'stroke-opacity': w.config.chart.selection.stroke.opacity,
+ })
+
+ Graphics.setAttrs(selectionRect.node, scalingAttrs)
+ }
+ }
+ }
+
+ hideSelectionRect(rect) {
+ if (rect) {
+ rect.attr({
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ })
+ }
+ }
+
+ selectionDrawing({ context, zoomtype }) {
+ const w = this.w
+ let me = context
+
+ let gridRectDim = this.gridRect.getBoundingClientRect()
+
+ let startX = me.startX - 1
+ let startY = me.startY
+ let inversedX = false
+ let inversedY = false
+
+ let selectionWidth = me.clientX - gridRectDim.left - startX
+ let selectionHeight = me.clientY - gridRectDim.top - startY
+
+ let selectionRect = {}
+
+ if (Math.abs(selectionWidth + startX) > w.globals.gridWidth) {
+ // user dragged the mouse outside drawing area to the right
+ selectionWidth = w.globals.gridWidth - startX
+ } else if (me.clientX - gridRectDim.left < 0) {
+ // user dragged the mouse outside drawing area to the left
+ selectionWidth = startX
+ }
+
+ // inverse selection X
+ if (startX > me.clientX - gridRectDim.left) {
+ inversedX = true
+ selectionWidth = Math.abs(selectionWidth)
+ }
+
+ // inverse selection Y
+ if (startY > me.clientY - gridRectDim.top) {
+ inversedY = true
+ selectionHeight = Math.abs(selectionHeight)
+ }
+
+ if (zoomtype === 'x') {
+ selectionRect = {
+ x: inversedX ? startX - selectionWidth : startX,
+ y: 0,
+ width: selectionWidth,
+ height: w.globals.gridHeight,
+ }
+ } else if (zoomtype === 'y') {
+ selectionRect = {
+ x: 0,
+ y: inversedY ? startY - selectionHeight : startY,
+ width: w.globals.gridWidth,
+ height: selectionHeight,
+ }
+ } else {
+ selectionRect = {
+ x: inversedX ? startX - selectionWidth : startX,
+ y: inversedY ? startY - selectionHeight : startY,
+ width: selectionWidth,
+ height: selectionHeight,
+ }
+ }
+
+ me.drawSelectionRect(selectionRect)
+ me.selectionDragging('resizing')
+ return selectionRect
+ }
+
+ selectionDragging(type, e) {
+ const w = this.w
+ const xyRatios = this.xyRatios
+
+ const selRect = this.selectionRect
+
+ let timerInterval = 0
+
+ if (type === 'resizing') {
+ timerInterval = 30
+ }
+
+ // update selection when selection rect is dragged
+ const getSelAttr = (attr) => {
+ return parseFloat(selRect.node.getAttribute(attr))
+ }
+ const draggedProps = {
+ x: getSelAttr('x'),
+ y: getSelAttr('y'),
+ width: getSelAttr('width'),
+ height: getSelAttr('height'),
+ }
+ w.globals.selection = draggedProps
+ // update selection ends
+
+ if (
+ typeof w.config.chart.events.selection === 'function' &&
+ w.globals.selectionEnabled
+ ) {
+ // a small debouncer is required when resizing to avoid freezing the chart
+ clearTimeout(this.w.globals.selectionResizeTimer)
+ this.w.globals.selectionResizeTimer = window.setTimeout(() => {
+ const gridRectDim = this.gridRect.getBoundingClientRect()
+ const selectionRect = selRect.node.getBoundingClientRect()
+
+ let minX, maxX, minY, maxY
+
+ if (!w.globals.isRangeBar) {
+ // original code is in the IF. rangeBar exception is in the ELSE.
+ minX =
+ w.globals.xAxisScale.niceMin +
+ (selectionRect.left - gridRectDim.left) * xyRatios.xRatio
+ maxX =
+ w.globals.xAxisScale.niceMin +
+ (selectionRect.right - gridRectDim.left) * xyRatios.xRatio
+
+ minY =
+ w.globals.yAxisScale[0].niceMin +
+ (gridRectDim.bottom - selectionRect.bottom) * xyRatios.yRatio[0]
+ maxY =
+ w.globals.yAxisScale[0].niceMax -
+ (selectionRect.top - gridRectDim.top) * xyRatios.yRatio[0]
+ } else {
+ // rangeBars use x as the category, and y as the datetime data. // find data in y axis and use Y ratio
+ minX =
+ w.globals.yAxisScale[0].niceMin +
+ (selectionRect.left - gridRectDim.left) * xyRatios.invertedYRatio
+ maxX =
+ w.globals.yAxisScale[0].niceMin +
+ (selectionRect.right - gridRectDim.left) * xyRatios.invertedYRatio
+
+ minY = 0 // there is no y min/max with rangebars (it uses categories, not numeric data), so use dummy values
+ maxY = 1
+ }
+
+ const xyAxis = {
+ xaxis: {
+ min: minX,
+ max: maxX,
+ },
+ yaxis: {
+ min: minY,
+ max: maxY,
+ },
+ }
+ w.config.chart.events.selection(this.ctx, xyAxis)
+
+ if (
+ w.config.chart.brush.enabled &&
+ w.config.chart.events.brushScrolled !== undefined
+ ) {
+ w.config.chart.events.brushScrolled(this.ctx, xyAxis)
+ }
+ }, timerInterval)
+ }
+ }
+
+ selectionDrawn({ context, zoomtype }) {
+ const w = this.w
+ const me = context
+ const xyRatios = this.xyRatios
+ const toolbar = this.ctx.toolbar
+
+ if (me.startX > me.endX) {
+ let tempX = me.startX
+ me.startX = me.endX
+ me.endX = tempX
+ }
+ if (me.startY > me.endY) {
+ let tempY = me.startY
+ me.startY = me.endY
+ me.endY = tempY
+ }
+
+ let xLowestValue = undefined
+ let xHighestValue = undefined
+
+ if (!w.globals.isRangeBar) {
+ xLowestValue = w.globals.xAxisScale.niceMin + me.startX * xyRatios.xRatio
+ xHighestValue = w.globals.xAxisScale.niceMin + me.endX * xyRatios.xRatio
+ } else {
+ xLowestValue =
+ w.globals.yAxisScale[0].niceMin + me.startX * xyRatios.invertedYRatio
+ xHighestValue =
+ w.globals.yAxisScale[0].niceMin + me.endX * xyRatios.invertedYRatio
+ }
+
+ // TODO: we will consider the 1st y axis values here for getting highest and lowest y
+ let yHighestValue = []
+ let yLowestValue = []
+
+ w.config.yaxis.forEach((yaxe, index) => {
+ // We can use the index of any series referenced by the Yaxis
+ // because they will all return the same value, so we choose the first.
+ let seriesIndex = w.globals.seriesYAxisMap[index][0]
+ yHighestValue.push(
+ w.globals.yAxisScale[index].niceMax -
+ xyRatios.yRatio[seriesIndex] * me.startY
+ )
+ yLowestValue.push(
+ w.globals.yAxisScale[index].niceMax -
+ xyRatios.yRatio[seriesIndex] * me.endY
+ )
+ })
+
+ if (
+ me.dragged &&
+ (me.dragX > 10 || me.dragY > 10) &&
+ xLowestValue !== xHighestValue
+ ) {
+ if (w.globals.zoomEnabled) {
+ let yaxis = Utils.clone(w.globals.initialConfig.yaxis)
+ let xaxis = Utils.clone(w.globals.initialConfig.xaxis)
+
+ w.globals.zoomed = true
+
+ if (w.config.xaxis.convertedCatToNumeric) {
+ xLowestValue = Math.floor(xLowestValue)
+ xHighestValue = Math.floor(xHighestValue)
+
+ if (xLowestValue < 1) {
+ xLowestValue = 1
+ xHighestValue = w.globals.dataPoints
+ }
+
+ if (xHighestValue - xLowestValue < 2) {
+ xHighestValue = xLowestValue + 1
+ }
+ }
+
+ if (zoomtype === 'xy' || zoomtype === 'x') {
+ xaxis = {
+ min: xLowestValue,
+ max: xHighestValue,
+ }
+ }
+
+ if (zoomtype === 'xy' || zoomtype === 'y') {
+ yaxis.forEach((yaxe, index) => {
+ yaxis[index].min = yLowestValue[index]
+ yaxis[index].max = yHighestValue[index]
+ })
+ }
+
+ if (toolbar) {
+ let beforeZoomRange = toolbar.getBeforeZoomRange(xaxis, yaxis)
+ if (beforeZoomRange) {
+ xaxis = beforeZoomRange.xaxis ? beforeZoomRange.xaxis : xaxis
+ yaxis = beforeZoomRange.yaxis ? beforeZoomRange.yaxis : yaxis
+ }
+ }
+
+ let options = {
+ xaxis,
+ }
+
+ if (!w.config.chart.group) {
+ // if chart in a group, prevent yaxis update here
+ // fix issue #650
+ options.yaxis = yaxis
+ }
+ me.ctx.updateHelpers._updateOptions(
+ options,
+ false,
+ me.w.config.chart.animations.dynamicAnimation.enabled
+ )
+
+ if (typeof w.config.chart.events.zoomed === 'function') {
+ toolbar.zoomCallback(xaxis, yaxis)
+ }
+ } else if (w.globals.selectionEnabled) {
+ let yaxis = null
+ let xaxis = null
+ xaxis = {
+ min: xLowestValue,
+ max: xHighestValue,
+ }
+ if (zoomtype === 'xy' || zoomtype === 'y') {
+ yaxis = Utils.clone(w.config.yaxis)
+ yaxis.forEach((yaxe, index) => {
+ yaxis[index].min = yLowestValue[index]
+ yaxis[index].max = yHighestValue[index]
+ })
+ }
+
+ w.globals.selection = me.selection
+ if (typeof w.config.chart.events.selection === 'function') {
+ w.config.chart.events.selection(me.ctx, {
+ xaxis,
+ yaxis,
+ })
+ }
+ }
+ }
+ }
+
+ panDragging({ context }) {
+ const w = this.w
+ let me = context
+
+ // check to make sure there is data to compare against
+ if (typeof w.globals.lastClientPosition.x !== 'undefined') {
+ // get the change from last position to this position
+ const deltaX = w.globals.lastClientPosition.x - me.clientX
+ const deltaY = w.globals.lastClientPosition.y - me.clientY
+
+ // check which direction had the highest amplitude and then figure out direction by checking if the value is greater or less than zero
+ if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
+ this.moveDirection = 'left'
+ } else if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
+ this.moveDirection = 'right'
+ } else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY > 0) {
+ this.moveDirection = 'up'
+ } else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY < 0) {
+ this.moveDirection = 'down'
+ }
+ }
+
+ // set the new last position to the current for next time (to get the position of drag)
+ w.globals.lastClientPosition = {
+ x: me.clientX,
+ y: me.clientY,
+ }
+
+ let xLowestValue = w.globals.isRangeBar ? w.globals.minY : w.globals.minX
+
+ let xHighestValue = w.globals.isRangeBar ? w.globals.maxY : w.globals.maxX
+
+ // on a category, we don't pan continuosly as it causes bugs
+ if (!w.config.xaxis.convertedCatToNumeric) {
+ me.panScrolled(xLowestValue, xHighestValue)
+ }
+ }
+
+ delayedPanScrolled() {
+ const w = this.w
+
+ let newMinX = w.globals.minX
+ let newMaxX = w.globals.maxX
+ const centerX = (w.globals.maxX - w.globals.minX) / 2
+
+ if (this.moveDirection === 'left') {
+ newMinX = w.globals.minX + centerX
+ newMaxX = w.globals.maxX + centerX
+ } else if (this.moveDirection === 'right') {
+ newMinX = w.globals.minX - centerX
+ newMaxX = w.globals.maxX - centerX
+ }
+
+ newMinX = Math.floor(newMinX)
+ newMaxX = Math.floor(newMaxX)
+ this.updateScrolledChart(
+ { xaxis: { min: newMinX, max: newMaxX } },
+ newMinX,
+ newMaxX
+ )
+ }
+
+ panScrolled(xLowestValue, xHighestValue) {
+ const w = this.w
+
+ const xyRatios = this.xyRatios
+ let yaxis = Utils.clone(w.globals.initialConfig.yaxis)
+
+ let xRatio = xyRatios.xRatio
+ let minX = w.globals.minX
+ let maxX = w.globals.maxX
+ if (w.globals.isRangeBar) {
+ xRatio = xyRatios.invertedYRatio
+ minX = w.globals.minY
+ maxX = w.globals.maxY
+ }
+
+ if (this.moveDirection === 'left') {
+ xLowestValue = minX + (w.globals.gridWidth / 15) * xRatio
+ xHighestValue = maxX + (w.globals.gridWidth / 15) * xRatio
+ } else if (this.moveDirection === 'right') {
+ xLowestValue = minX - (w.globals.gridWidth / 15) * xRatio
+ xHighestValue = maxX - (w.globals.gridWidth / 15) * xRatio
+ }
+
+ if (!w.globals.isRangeBar) {
+ if (
+ xLowestValue < w.globals.initialMinX ||
+ xHighestValue > w.globals.initialMaxX
+ ) {
+ xLowestValue = minX
+ xHighestValue = maxX
+ }
+ }
+
+ let xaxis = {
+ min: xLowestValue,
+ max: xHighestValue,
+ }
+
+ let options = {
+ xaxis: {
+ min: xLowestValue,
+ max: xHighestValue,
+ },
+ }
+
+ if (!w.config.chart.group) {
+ // if chart in a group, prevent yaxis update here
+ // fix issue #650
+ options.yaxis = yaxis
+ }
+
+ this.updateScrolledChart(options, xLowestValue, xHighestValue)
+ }
+
+ updateScrolledChart(options, xLowestValue, xHighestValue) {
+ const w = this.w
+
+ this.ctx.updateHelpers._updateOptions(options, false, false)
+
+ if (typeof w.config.chart.events.scrolled === 'function') {
+ w.config.chart.events.scrolled(this.ctx, {
+ xaxis: {
+ min: xLowestValue,
+ max: xHighestValue,
+ },
+ })
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/Annotations.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/Annotations.js
new file mode 100644
index 0000000..cfe1c84
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/Annotations.js
@@ -0,0 +1,325 @@
+import Graphics from '../../modules/Graphics'
+import Utils from '../../utils/Utils'
+import Helpers from './Helpers'
+import XAxisAnnotations from './XAxisAnnotations'
+import YAxisAnnotations from './YAxisAnnotations'
+import PointsAnnotations from './PointsAnnotations'
+import Options from './../settings/Options'
+
+/**
+ * ApexCharts Annotations Class for drawing lines/rects on both xaxis and yaxis.
+ *
+ * @module Annotations
+ **/
+export default class Annotations {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ this.graphics = new Graphics(this.ctx)
+
+ if (this.w.globals.isBarHorizontal) {
+ this.invertAxis = true
+ }
+
+ this.helpers = new Helpers(this)
+ this.xAxisAnnotations = new XAxisAnnotations(this)
+ this.yAxisAnnotations = new YAxisAnnotations(this)
+ this.pointsAnnotations = new PointsAnnotations(this)
+
+ if (this.w.globals.isBarHorizontal && this.w.config.yaxis[0].reversed) {
+ this.inversedReversedAxis = true
+ }
+
+ this.xDivision = this.w.globals.gridWidth / this.w.globals.dataPoints
+ }
+
+ drawAxesAnnotations() {
+ const w = this.w
+ if (w.globals.axisCharts && w.globals.dataPoints) {
+ // w.globals.dataPoints check added to fix #1832
+ let yAnnotations = this.yAxisAnnotations.drawYAxisAnnotations()
+ let xAnnotations = this.xAxisAnnotations.drawXAxisAnnotations()
+ let pointAnnotations = this.pointsAnnotations.drawPointAnnotations()
+
+ const initialAnim = w.config.chart.animations.enabled
+
+ const annoArray = [yAnnotations, xAnnotations, pointAnnotations]
+ const annoElArray = [
+ xAnnotations.node,
+ yAnnotations.node,
+ pointAnnotations.node,
+ ]
+ for (let i = 0; i < 3; i++) {
+ w.globals.dom.elGraphical.add(annoArray[i])
+ if (initialAnim && !w.globals.resized && !w.globals.dataChanged) {
+ // fixes apexcharts/apexcharts.js#685
+ if (
+ w.config.chart.type !== 'scatter' &&
+ w.config.chart.type !== 'bubble' &&
+ w.globals.dataPoints > 1
+ ) {
+ annoElArray[i].classList.add('apexcharts-element-hidden')
+ }
+ }
+ w.globals.delayedElements.push({ el: annoElArray[i], index: 0 })
+ }
+
+ // background sizes needs to be calculated after text is drawn, so calling them last
+ this.helpers.annotationsBackground()
+ }
+ }
+
+ drawImageAnnos() {
+ const w = this.w
+
+ w.config.annotations.images.map((s, index) => {
+ this.addImage(s, index)
+ })
+ }
+
+ drawTextAnnos() {
+ const w = this.w
+
+ w.config.annotations.texts.map((t, index) => {
+ this.addText(t, index)
+ })
+ }
+
+ addXaxisAnnotation(anno, parent, index) {
+ this.xAxisAnnotations.addXaxisAnnotation(anno, parent, index)
+ }
+
+ addYaxisAnnotation(anno, parent, index) {
+ this.yAxisAnnotations.addYaxisAnnotation(anno, parent, index)
+ }
+
+ addPointAnnotation(anno, parent, index) {
+ this.pointsAnnotations.addPointAnnotation(anno, parent, index)
+ }
+
+ addText(params, index) {
+ const {
+ x,
+ y,
+ text,
+ textAnchor,
+ foreColor,
+ fontSize,
+ fontFamily,
+ fontWeight,
+ cssClass,
+ backgroundColor,
+ borderWidth,
+ strokeDashArray,
+ borderRadius,
+ borderColor,
+ appendTo = '.apexcharts-svg',
+ paddingLeft = 4,
+ paddingRight = 4,
+ paddingBottom = 2,
+ paddingTop = 2,
+ } = params
+
+ const w = this.w
+
+ let elText = this.graphics.drawText({
+ x,
+ y,
+ text,
+ textAnchor: textAnchor || 'start',
+ fontSize: fontSize || '12px',
+ fontWeight: fontWeight || 'regular',
+ fontFamily: fontFamily || w.config.chart.fontFamily,
+ foreColor: foreColor || w.config.chart.foreColor,
+ cssClass: 'apexcharts-text ' + cssClass ? cssClass : '',
+ })
+
+ const parent = w.globals.dom.baseEl.querySelector(appendTo)
+ if (parent) {
+ parent.appendChild(elText.node)
+ }
+
+ const textRect = elText.bbox()
+
+ if (text) {
+ const elRect = this.graphics.drawRect(
+ textRect.x - paddingLeft,
+ textRect.y - paddingTop,
+ textRect.width + paddingLeft + paddingRight,
+ textRect.height + paddingBottom + paddingTop,
+ borderRadius,
+ backgroundColor ? backgroundColor : 'transparent',
+ 1,
+ borderWidth,
+ borderColor,
+ strokeDashArray
+ )
+
+ parent.insertBefore(elRect.node, elText.node)
+ }
+ }
+
+ addImage(params, index) {
+ const w = this.w
+
+ const {
+ path,
+ x = 0,
+ y = 0,
+ width = 20,
+ height = 20,
+ appendTo = '.apexcharts-svg',
+ } = params
+
+ let img = w.globals.dom.Paper.image(path)
+ img.size(width, height).move(x, y)
+
+ const parent = w.globals.dom.baseEl.querySelector(appendTo)
+ if (parent) {
+ parent.appendChild(img.node)
+ }
+
+ return img
+ }
+
+ // The addXaxisAnnotation method requires a parent class, and user calling this method externally on the chart instance may not specify parent, hence a different method
+ addXaxisAnnotationExternal(params, pushToMemory, context) {
+ this.addAnnotationExternal({
+ params,
+ pushToMemory,
+ context,
+ type: 'xaxis',
+ contextMethod: context.addXaxisAnnotation,
+ })
+ return context
+ }
+
+ addYaxisAnnotationExternal(params, pushToMemory, context) {
+ this.addAnnotationExternal({
+ params,
+ pushToMemory,
+ context,
+ type: 'yaxis',
+ contextMethod: context.addYaxisAnnotation,
+ })
+ return context
+ }
+
+ addPointAnnotationExternal(params, pushToMemory, context) {
+ if (typeof this.invertAxis === 'undefined') {
+ this.invertAxis = context.w.globals.isBarHorizontal
+ }
+
+ this.addAnnotationExternal({
+ params,
+ pushToMemory,
+ context,
+ type: 'point',
+ contextMethod: context.addPointAnnotation,
+ })
+ return context
+ }
+
+ addAnnotationExternal({
+ params,
+ pushToMemory,
+ context,
+ type,
+ contextMethod,
+ }) {
+ const me = context
+ const w = me.w
+ const parent = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-${type}-annotations`
+ )
+ const index = parent.childNodes.length + 1
+
+ const options = new Options()
+ const axesAnno = Object.assign(
+ {},
+ type === 'xaxis'
+ ? options.xAxisAnnotation
+ : type === 'yaxis'
+ ? options.yAxisAnnotation
+ : options.pointAnnotation
+ )
+
+ const anno = Utils.extend(axesAnno, params)
+
+ switch (type) {
+ case 'xaxis':
+ this.addXaxisAnnotation(anno, parent, index)
+ break
+ case 'yaxis':
+ this.addYaxisAnnotation(anno, parent, index)
+ break
+ case 'point':
+ this.addPointAnnotation(anno, parent, index)
+ break
+ }
+
+ // add background
+ let axesAnnoLabel = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-${type}-annotations .apexcharts-${type}-annotation-label[rel='${index}']`
+ )
+ const elRect = this.helpers.addBackgroundToAnno(axesAnnoLabel, anno)
+ if (elRect) {
+ parent.insertBefore(elRect.node, axesAnnoLabel)
+ }
+
+ if (pushToMemory) {
+ w.globals.memory.methodsToExec.push({
+ context: me,
+ id: anno.id ? anno.id : Utils.randomId(),
+ method: contextMethod,
+ label: 'addAnnotation',
+ params,
+ })
+ }
+
+ return context
+ }
+
+ clearAnnotations(ctx) {
+ const w = ctx.w
+ let annos = w.globals.dom.baseEl.querySelectorAll(
+ '.apexcharts-yaxis-annotations, .apexcharts-xaxis-annotations, .apexcharts-point-annotations'
+ )
+
+ // annotations added externally should be cleared out too
+ for (let i = w.globals.memory.methodsToExec.length - 1; i >= 0; i--) {
+ if (
+ w.globals.memory.methodsToExec[i].label === 'addText' ||
+ w.globals.memory.methodsToExec[i].label === 'addAnnotation'
+ ) {
+ w.globals.memory.methodsToExec.splice(i, 1)
+ }
+ }
+
+ annos = Utils.listToArray(annos)
+
+ // delete the DOM elements
+ Array.prototype.forEach.call(annos, (a) => {
+ while (a.firstChild) {
+ a.removeChild(a.firstChild)
+ }
+ })
+ }
+
+ removeAnnotation(ctx, id) {
+ const w = ctx.w
+ let annos = w.globals.dom.baseEl.querySelectorAll(`.${id}`)
+
+ if (annos) {
+ w.globals.memory.methodsToExec.map((m, i) => {
+ if (m.id === id) {
+ w.globals.memory.methodsToExec.splice(i, 1)
+ }
+ })
+
+ Array.prototype.forEach.call(annos, (a) => {
+ a.parentElement.removeChild(a)
+ })
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/Helpers.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/Helpers.js
new file mode 100644
index 0000000..ac06db4
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/Helpers.js
@@ -0,0 +1,258 @@
+import CoreUtils from '../CoreUtils'
+
+export default class Helpers {
+ constructor(annoCtx) {
+ this.w = annoCtx.w
+ this.annoCtx = annoCtx
+ }
+
+ setOrientations(anno, annoIndex = null) {
+ const w = this.w
+
+ if (anno.label.orientation === 'vertical') {
+ const i = annoIndex !== null ? annoIndex : 0
+ const xAnno = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-xaxis-annotations .apexcharts-xaxis-annotation-label[rel='${i}']`
+ )
+
+ if (xAnno !== null) {
+ const xAnnoCoord = xAnno.getBoundingClientRect()
+ xAnno.setAttribute(
+ 'x',
+ parseFloat(xAnno.getAttribute('x')) - xAnnoCoord.height + 4
+ )
+
+ const yOffset =
+ anno.label.position === 'top' ? xAnnoCoord.width : -xAnnoCoord.width
+ xAnno.setAttribute('y', parseFloat(xAnno.getAttribute('y')) + yOffset)
+
+ const { x, y } = this.annoCtx.graphics.rotateAroundCenter(xAnno)
+ xAnno.setAttribute('transform', `rotate(-90 ${x} ${y})`)
+ }
+ }
+ }
+
+ addBackgroundToAnno(annoEl, anno) {
+ const w = this.w
+
+ if (!annoEl || !anno.label.text || !String(anno.label.text).trim()) {
+ return null
+ }
+
+ const elGridRect = w.globals.dom.baseEl
+ .querySelector('.apexcharts-grid')
+ .getBoundingClientRect()
+
+ const coords = annoEl.getBoundingClientRect()
+
+ let {
+ left: pleft,
+ right: pright,
+ top: ptop,
+ bottom: pbottom,
+ } = anno.label.style.padding
+
+ if (anno.label.orientation === 'vertical') {
+ ;[ptop, pbottom, pleft, pright] = [pleft, pright, ptop, pbottom]
+ }
+
+ const x1 = coords.left - elGridRect.left - pleft
+ const y1 = coords.top - elGridRect.top - ptop
+ const elRect = this.annoCtx.graphics.drawRect(
+ x1 - w.globals.barPadForNumericAxis,
+ y1,
+ coords.width + pleft + pright,
+ coords.height + ptop + pbottom,
+ anno.label.borderRadius,
+ anno.label.style.background,
+ 1,
+ anno.label.borderWidth,
+ anno.label.borderColor,
+ 0
+ )
+
+ if (anno.id) {
+ elRect.node.classList.add(anno.id)
+ }
+
+ return elRect
+ }
+
+ annotationsBackground() {
+ const w = this.w
+
+ const add = (anno, i, type) => {
+ const annoLabel = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-${type}-annotations .apexcharts-${type}-annotation-label[rel='${i}']`
+ )
+
+ if (annoLabel) {
+ const parent = annoLabel.parentNode
+ const elRect = this.addBackgroundToAnno(annoLabel, anno)
+
+ if (elRect) {
+ parent.insertBefore(elRect.node, annoLabel)
+
+ if (anno.label.mouseEnter) {
+ elRect.node.addEventListener(
+ 'mouseenter',
+ anno.label.mouseEnter.bind(this, anno)
+ )
+ }
+ if (anno.label.mouseLeave) {
+ elRect.node.addEventListener(
+ 'mouseleave',
+ anno.label.mouseLeave.bind(this, anno)
+ )
+ }
+ if (anno.label.click) {
+ elRect.node.addEventListener(
+ 'click',
+ anno.label.click.bind(this, anno)
+ )
+ }
+ }
+ }
+ }
+
+ w.config.annotations.xaxis.forEach((anno, i) => add(anno, i, 'xaxis'))
+ w.config.annotations.yaxis.forEach((anno, i) => add(anno, i, 'yaxis'))
+ w.config.annotations.points.forEach((anno, i) => add(anno, i, 'point'))
+ }
+
+ getY1Y2(type, anno) {
+ const w = this.w
+ let y = type === 'y1' ? anno.y : anno.y2
+ let yP
+ let clipped = false
+
+ if (this.annoCtx.invertAxis) {
+ const labels = w.config.xaxis.convertedCatToNumeric
+ ? w.globals.categoryLabels
+ : w.globals.labels
+ const catIndex = labels.indexOf(y)
+ const xLabel = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-yaxis-texts-g text:nth-child(${catIndex + 1})`
+ )
+
+ yP = xLabel
+ ? parseFloat(xLabel.getAttribute('y'))
+ : (w.globals.gridHeight / labels.length - 1) * (catIndex + 1) -
+ w.globals.barHeight
+
+ if (anno.seriesIndex !== undefined && w.globals.barHeight) {
+ yP -=
+ (w.globals.barHeight / 2) * (w.globals.series.length - 1) -
+ w.globals.barHeight * anno.seriesIndex
+ }
+ } else {
+ const seriesIndex = w.globals.seriesYAxisMap[anno.yAxisIndex][0]
+ const yPos = w.config.yaxis[anno.yAxisIndex].logarithmic
+ ? new CoreUtils(this.annoCtx.ctx).getLogVal(
+ w.config.yaxis[anno.yAxisIndex].logBase,
+ y,
+ seriesIndex
+ ) / w.globals.yLogRatio[seriesIndex]
+ : (y - w.globals.minYArr[seriesIndex]) /
+ (w.globals.yRange[seriesIndex] / w.globals.gridHeight)
+
+ yP =
+ w.globals.gridHeight - Math.min(Math.max(yPos, 0), w.globals.gridHeight)
+ clipped = yPos > w.globals.gridHeight || yPos < 0
+
+ if (anno.marker && (anno.y === undefined || anno.y === null)) {
+ yP = 0
+ }
+
+ if (w.config.yaxis[anno.yAxisIndex]?.reversed) {
+ yP = yPos
+ }
+ }
+
+ if (typeof y === 'string' && y.includes('px')) {
+ yP = parseFloat(y)
+ }
+
+ return { yP, clipped }
+ }
+
+ getX1X2(type, anno) {
+ const w = this.w
+ const x = type === 'x1' ? anno.x : anno.x2
+ const min = this.annoCtx.invertAxis ? w.globals.minY : w.globals.minX
+ const max = this.annoCtx.invertAxis ? w.globals.maxY : w.globals.maxX
+ const range = this.annoCtx.invertAxis
+ ? w.globals.yRange[0]
+ : w.globals.xRange
+ let clipped = false
+
+ let xP = this.annoCtx.inversedReversedAxis
+ ? (max - x) / (range / w.globals.gridWidth)
+ : (x - min) / (range / w.globals.gridWidth)
+
+ if (
+ (w.config.xaxis.type === 'category' ||
+ w.config.xaxis.convertedCatToNumeric) &&
+ !this.annoCtx.invertAxis &&
+ !w.globals.dataFormatXNumeric
+ ) {
+ if (!w.config.chart.sparkline.enabled) {
+ xP = this.getStringX(x)
+ }
+ }
+
+ if (typeof x === 'string' && x.includes('px')) {
+ xP = parseFloat(x)
+ }
+
+ if ((x === undefined || x === null) && anno.marker) {
+ xP = w.globals.gridWidth
+ }
+
+ if (
+ anno.seriesIndex !== undefined &&
+ w.globals.barWidth &&
+ !this.annoCtx.invertAxis
+ ) {
+ xP -=
+ (w.globals.barWidth / 2) * (w.globals.series.length - 1) -
+ w.globals.barWidth * anno.seriesIndex
+ }
+
+ if (xP > w.globals.gridWidth) {
+ xP = w.globals.gridWidth
+ clipped = true
+ } else if (xP < 0) {
+ xP = 0
+ clipped = true
+ }
+
+ return { x: xP, clipped }
+ }
+
+ getStringX(x) {
+ const w = this.w
+ let rX = x
+
+ if (
+ w.config.xaxis.convertedCatToNumeric &&
+ w.globals.categoryLabels.length
+ ) {
+ x = w.globals.categoryLabels.indexOf(x) + 1
+ }
+
+ const catIndex = w.globals.labels
+ .map((item) => (Array.isArray(item) ? item.join(' ') : item))
+ .indexOf(x)
+
+ const xLabel = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-xaxis-texts-g text:nth-child(${catIndex + 1})`
+ )
+
+ if (xLabel) {
+ rX = parseFloat(xLabel.getAttribute('x'))
+ }
+
+ return rX
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/PointsAnnotations.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/PointsAnnotations.js
new file mode 100644
index 0000000..72c45e7
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/PointsAnnotations.js
@@ -0,0 +1,136 @@
+import Utils from '../../utils/Utils'
+import Helpers from './Helpers'
+
+export default class PointAnnotations {
+ constructor(annoCtx) {
+ this.w = annoCtx.w
+ this.annoCtx = annoCtx
+ this.helpers = new Helpers(this.annoCtx)
+ }
+
+ addPointAnnotation(anno, parent, index) {
+ const w = this.w
+
+ if (w.globals.collapsedSeriesIndices.indexOf(anno.seriesIndex) > -1) {
+ return
+ }
+
+ let result = this.helpers.getX1X2('x1', anno)
+ let x = result.x
+ let clipX = result.clipped
+ result = this.helpers.getY1Y2('y1', anno)
+ let y = result.yP
+ let clipY = result.clipped
+
+ if (!Utils.isNumber(x)) return
+
+ if (!(clipY || clipX)) {
+ let optsPoints = {
+ pSize: anno.marker.size,
+ pointStrokeWidth: anno.marker.strokeWidth,
+ pointFillColor: anno.marker.fillColor,
+ pointStrokeColor: anno.marker.strokeColor,
+ shape: anno.marker.shape,
+ pRadius: anno.marker.radius,
+ class: `apexcharts-point-annotation-marker ${anno.marker.cssClass} ${
+ anno.id ? anno.id : ''
+ }`,
+ }
+
+ let point = this.annoCtx.graphics.drawMarker(
+ x + anno.marker.offsetX,
+ y + anno.marker.offsetY,
+ optsPoints
+ )
+
+ parent.appendChild(point.node)
+
+ const text = anno.label.text ? anno.label.text : ''
+
+ let elText = this.annoCtx.graphics.drawText({
+ x: x + anno.label.offsetX,
+ y:
+ y +
+ anno.label.offsetY -
+ anno.marker.size -
+ parseFloat(anno.label.style.fontSize) / 1.6,
+ text,
+ textAnchor: anno.label.textAnchor,
+ fontSize: anno.label.style.fontSize,
+ fontFamily: anno.label.style.fontFamily,
+ fontWeight: anno.label.style.fontWeight,
+ foreColor: anno.label.style.color,
+ cssClass: `apexcharts-point-annotation-label ${
+ anno.label.style.cssClass
+ } ${anno.id ? anno.id : ''}`,
+ })
+
+ elText.attr({
+ rel: index,
+ })
+
+ parent.appendChild(elText.node)
+
+ // TODO: deprecate this as we will use custom
+ if (anno.customSVG.SVG) {
+ let g = this.annoCtx.graphics.group({
+ class:
+ 'apexcharts-point-annotations-custom-svg ' + anno.customSVG.cssClass,
+ })
+
+ g.attr({
+ transform: `translate(${x + anno.customSVG.offsetX}, ${
+ y + anno.customSVG.offsetY
+ })`,
+ })
+
+ g.node.innerHTML = anno.customSVG.SVG
+ parent.appendChild(g.node)
+ }
+
+ if (anno.image.path) {
+ let imgWidth = anno.image.width ? anno.image.width : 20
+ let imgHeight = anno.image.height ? anno.image.height : 20
+
+ point = this.annoCtx.addImage({
+ x: x + anno.image.offsetX - imgWidth / 2,
+ y: y + anno.image.offsetY - imgHeight / 2,
+ width: imgWidth,
+ height: imgHeight,
+ path: anno.image.path,
+ appendTo: '.apexcharts-point-annotations',
+ })
+ }
+
+ if (anno.mouseEnter) {
+ point.node.addEventListener(
+ 'mouseenter',
+ anno.mouseEnter.bind(this, anno)
+ )
+ }
+ if (anno.mouseLeave) {
+ point.node.addEventListener(
+ 'mouseleave',
+ anno.mouseLeave.bind(this, anno)
+ )
+ }
+ if (anno.click) {
+ point.node.addEventListener('click', anno.click.bind(this, anno))
+ }
+ }
+ }
+
+ drawPointAnnotations() {
+ let w = this.w
+
+ let elg = this.annoCtx.graphics.group({
+ class: 'apexcharts-point-annotations',
+ })
+
+ w.config.annotations.points.map((anno, index) => {
+ this.addPointAnnotation(anno, elg.node, index)
+ })
+
+ return elg
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/XAxisAnnotations.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/XAxisAnnotations.js
new file mode 100644
index 0000000..8390360
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/XAxisAnnotations.js
@@ -0,0 +1,135 @@
+import Utils from '../../utils/Utils'
+import Helpers from './Helpers'
+
+export default class XAnnotations {
+ constructor(annoCtx) {
+ this.w = annoCtx.w
+ this.annoCtx = annoCtx
+
+ this.invertAxis = this.annoCtx.invertAxis
+
+ this.helpers = new Helpers(this.annoCtx)
+ }
+
+ addXaxisAnnotation(anno, parent, index) {
+ let w = this.w
+
+ let result = this.helpers.getX1X2('x1', anno)
+ let x1 = result.x
+ let clipX1 = result.clipped
+ let clipX2 = true
+ let x2
+
+ const text = anno.label.text
+
+ let strokeDashArray = anno.strokeDashArray
+
+ if (!Utils.isNumber(x1)) return
+
+ if (anno.x2 === null || typeof anno.x2 === 'undefined') {
+ if (!clipX1) {
+ let line = this.annoCtx.graphics.drawLine(
+ x1 + anno.offsetX, // x1
+ 0 + anno.offsetY, // y1
+ x1 + anno.offsetX, // x2
+ w.globals.gridHeight + anno.offsetY, // y2
+ anno.borderColor, // lineColor
+ strokeDashArray, //dashArray
+ anno.borderWidth
+ )
+ parent.appendChild(line.node)
+ if (anno.id) {
+ line.node.classList.add(anno.id)
+ }
+ }
+ } else {
+ let result = this.helpers.getX1X2('x2', anno)
+ x2 = result.x
+ clipX2 = result.clipped
+
+ if (!(clipX1 && clipX2)) {
+ if (x2 < x1) {
+ let temp = x1
+ x1 = x2
+ x2 = temp
+ }
+
+ let rect = this.annoCtx.graphics.drawRect(
+ x1 + anno.offsetX, // x1
+ 0 + anno.offsetY, // y1
+ x2 - x1, // x2
+ w.globals.gridHeight + anno.offsetY, // y2
+ 0, // radius
+ anno.fillColor, // color
+ anno.opacity, // opacity,
+ 1, // strokeWidth
+ anno.borderColor, // strokeColor
+ strokeDashArray // stokeDashArray
+ )
+ rect.node.classList.add('apexcharts-annotation-rect')
+ rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
+ parent.appendChild(rect.node)
+ if (anno.id) {
+ rect.node.classList.add(anno.id)
+ }
+ }
+ }
+
+ if (!(clipX1 && clipX2)) {
+ let textRects = this.annoCtx.graphics.getTextRects(
+ text,
+ parseFloat(anno.label.style.fontSize)
+ )
+ let textY =
+ anno.label.position === 'top'
+ ? 4
+ : anno.label.position === 'center'
+ ? w.globals.gridHeight / 2 +
+ (anno.label.orientation === 'vertical' ? textRects.width / 2 : 0)
+ : w.globals.gridHeight
+
+ let elText = this.annoCtx.graphics.drawText({
+ x: x1 + anno.label.offsetX,
+ y:
+ textY +
+ anno.label.offsetY -
+ (anno.label.orientation === 'vertical'
+ ? anno.label.position === 'top'
+ ? textRects.width / 2 - 12
+ : -textRects.width / 2
+ : 0),
+ text,
+ textAnchor: anno.label.textAnchor,
+ fontSize: anno.label.style.fontSize,
+ fontFamily: anno.label.style.fontFamily,
+ fontWeight: anno.label.style.fontWeight,
+ foreColor: anno.label.style.color,
+ cssClass: `apexcharts-xaxis-annotation-label ${
+ anno.label.style.cssClass
+ } ${anno.id ? anno.id : ''}`
+ })
+
+ elText.attr({
+ rel: index
+ })
+
+ parent.appendChild(elText.node)
+
+ // after placing the annotations on svg, set any vertically placed annotations
+ this.annoCtx.helpers.setOrientations(anno, index)
+ }
+ }
+ drawXAxisAnnotations() {
+ let w = this.w
+
+ let elg = this.annoCtx.graphics.group({
+ class: 'apexcharts-xaxis-annotations'
+ })
+
+ w.config.annotations.xaxis.map((anno, index) => {
+ this.addXaxisAnnotation(anno, elg.node, index)
+ })
+
+ return elg
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/YAxisAnnotations.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/YAxisAnnotations.js
new file mode 100644
index 0000000..687f738
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/annotations/YAxisAnnotations.js
@@ -0,0 +1,140 @@
+import Helpers from './Helpers'
+import AxesUtils from '../axes/AxesUtils'
+
+export default class YAnnotations {
+ constructor(annoCtx) {
+ this.w = annoCtx.w
+ this.annoCtx = annoCtx
+
+ this.helpers = new Helpers(this.annoCtx)
+ this.axesUtils = new AxesUtils(this.annoCtx)
+
+ }
+
+ addYaxisAnnotation(anno, parent, index) {
+ let w = this.w
+
+ let strokeDashArray = anno.strokeDashArray
+
+ let result = this.helpers.getY1Y2('y1', anno)
+ let y1 = result.yP
+ let clipY1 = result.clipped
+ let y2
+ let clipY2 = true
+ let drawn = false
+
+ const text = anno.label.text
+
+ if (anno.y2 === null || typeof anno.y2 === 'undefined') {
+ if (!clipY1) {
+ drawn = true
+ let line = this.annoCtx.graphics.drawLine(
+ 0 + anno.offsetX, // x1
+ y1 + anno.offsetY, // y1
+ this._getYAxisAnnotationWidth(anno), // x2
+ y1 + anno.offsetY, // y2
+ anno.borderColor, // lineColor
+ strokeDashArray, // dashArray
+ anno.borderWidth
+ )
+ parent.appendChild(line.node)
+ if (anno.id) {
+ line.node.classList.add(anno.id)
+ }
+ }
+ } else {
+ result = this.helpers.getY1Y2('y2', anno)
+ y2 = result.yP
+ clipY2 = result.clipped
+
+ if (y2 > y1) {
+ let temp = y1
+ y1 = y2
+ y2 = temp
+ }
+
+ if (!(clipY1 && clipY2)) {
+ drawn = true
+ let rect = this.annoCtx.graphics.drawRect(
+ 0 + anno.offsetX, // x1
+ y2 + anno.offsetY, // y1
+ this._getYAxisAnnotationWidth(anno), // x2
+ y1 - y2, // y2
+ 0, // radius
+ anno.fillColor, // color
+ anno.opacity, // opacity,
+ 1, // strokeWidth
+ anno.borderColor, // strokeColor
+ strokeDashArray // stokeDashArray
+ )
+ rect.node.classList.add('apexcharts-annotation-rect')
+ rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
+
+ parent.appendChild(rect.node)
+ if (anno.id) {
+ rect.node.classList.add(anno.id)
+ }
+ }
+ }
+ if (drawn) {
+ let textX =
+ anno.label.position === 'right'
+ ? w.globals.gridWidth
+ : anno.label.position === 'center'
+ ? w.globals.gridWidth / 2
+ : 0
+
+ let elText = this.annoCtx.graphics.drawText({
+ x: textX + anno.label.offsetX,
+ y: (y2 != null ? y2 : y1) + anno.label.offsetY - 3,
+ text,
+ textAnchor: anno.label.textAnchor,
+ fontSize: anno.label.style.fontSize,
+ fontFamily: anno.label.style.fontFamily,
+ fontWeight: anno.label.style.fontWeight,
+ foreColor: anno.label.style.color,
+ cssClass: `apexcharts-yaxis-annotation-label ${
+ anno.label.style.cssClass
+ } ${anno.id ? anno.id : ''}`
+ })
+
+ elText.attr({
+ rel: index
+ })
+
+ parent.appendChild(elText.node)
+ }
+ }
+
+ _getYAxisAnnotationWidth(anno) {
+ // issue apexcharts.js#2009
+ const w = this.w
+ let width = w.globals.gridWidth
+ if (anno.width.indexOf('%') > -1) {
+ width = (w.globals.gridWidth * parseInt(anno.width, 10)) / 100
+ } else {
+ width = parseInt(anno.width, 10)
+ }
+ return width + anno.offsetX
+ }
+
+ drawYAxisAnnotations() {
+ const w = this.w
+
+ let elg = this.annoCtx.graphics.group({
+ class: 'apexcharts-yaxis-annotations'
+ })
+
+ w.config.annotations.yaxis.forEach((anno, index) => {
+ anno.yAxisIndex = this.axesUtils.translateYAxisIndex(anno.yAxisIndex)
+ if (
+ !(this.axesUtils.isYAxisHidden(anno.yAxisIndex)
+ && this.axesUtils.yAxisAllSeriesCollapsed(anno.yAxisIndex))
+ ) {
+ this.addYaxisAnnotation(anno, elg.node, index)
+ }
+ })
+
+ return elg
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/Axes.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/Axes.js
new file mode 100644
index 0000000..07bd668
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/Axes.js
@@ -0,0 +1,45 @@
+import XAxis from './XAxis'
+import YAxis from './YAxis'
+
+export default class Axes {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ drawAxis(type, elgrid) {
+ let gl = this.w.globals
+ let cnf = this.w.config
+
+ let xAxis = new XAxis(this.ctx, elgrid)
+ let yAxis = new YAxis(this.ctx, elgrid)
+
+ if (gl.axisCharts && type !== 'radar') {
+ let elXaxis, elYaxis
+
+ if (gl.isBarHorizontal) {
+ elYaxis = yAxis.drawYaxisInversed(0)
+ elXaxis = xAxis.drawXaxisInversed(0)
+
+ gl.dom.elGraphical.add(elXaxis)
+ gl.dom.elGraphical.add(elYaxis)
+ } else {
+ elXaxis = xAxis.drawXaxis()
+ gl.dom.elGraphical.add(elXaxis)
+
+ cnf.yaxis.map((yaxe, index) => {
+ if (gl.ignoreYAxisIndexes.indexOf(index) === -1) {
+ elYaxis = yAxis.drawYaxis(index)
+ gl.dom.Paper.add(elYaxis)
+
+ if (this.w.config.grid.position === 'back') {
+ const inner = gl.dom.Paper.children()[1]
+ inner.remove()
+ gl.dom.Paper.add(inner)
+ }
+ }
+ })
+ }
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/AxesUtils.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/AxesUtils.js
new file mode 100644
index 0000000..68165da
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/AxesUtils.js
@@ -0,0 +1,271 @@
+import Formatters from '../Formatters'
+import Graphics from '../Graphics'
+import CoreUtils from '../CoreUtils'
+import DateTime from '../../utils/DateTime'
+
+export default class AxesUtils {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ // Based on the formatter function, get the label text and position
+ getLabel(
+ labels,
+ timescaleLabels,
+ x,
+ i,
+ drawnLabels = [],
+ fontSize = '12px',
+ isLeafGroup = true
+ ) {
+ const w = this.w
+ let rawLabel = typeof labels[i] === 'undefined' ? '' : labels[i]
+ let label = rawLabel
+
+ let xlbFormatter = w.globals.xLabelFormatter
+ let customFormatter = w.config.xaxis.labels.formatter
+
+ let isBold = false
+
+ let xFormat = new Formatters(this.ctx)
+ let timestamp = rawLabel
+
+ if (isLeafGroup) {
+ label = xFormat.xLabelFormat(xlbFormatter, rawLabel, timestamp, {
+ i,
+ dateFormatter: new DateTime(this.ctx).formatDate,
+ w,
+ })
+
+ if (customFormatter !== undefined) {
+ label = customFormatter(rawLabel, labels[i], {
+ i,
+ dateFormatter: new DateTime(this.ctx).formatDate,
+ w,
+ })
+ }
+ }
+
+ const determineHighestUnit = (unit) => {
+ let highestUnit = null
+ timescaleLabels.forEach((t) => {
+ if (t.unit === 'month') {
+ highestUnit = 'year'
+ } else if (t.unit === 'day') {
+ highestUnit = 'month'
+ } else if (t.unit === 'hour') {
+ highestUnit = 'day'
+ } else if (t.unit === 'minute') {
+ highestUnit = 'hour'
+ }
+ })
+
+ return highestUnit === unit
+ }
+ if (timescaleLabels.length > 0) {
+ isBold = determineHighestUnit(timescaleLabels[i].unit)
+ x = timescaleLabels[i].position
+ label = timescaleLabels[i].value
+ } else {
+ if (w.config.xaxis.type === 'datetime' && customFormatter === undefined) {
+ label = ''
+ }
+ }
+
+ if (typeof label === 'undefined') label = ''
+
+ label = Array.isArray(label) ? label : label.toString()
+
+ let graphics = new Graphics(this.ctx)
+ let textRect = {}
+ if (w.globals.rotateXLabels && isLeafGroup) {
+ textRect = graphics.getTextRects(
+ label,
+ parseInt(fontSize, 10),
+ null,
+ `rotate(${w.config.xaxis.labels.rotate} 0 0)`,
+ false
+ )
+ } else {
+ textRect = graphics.getTextRects(label, parseInt(fontSize, 10))
+ }
+
+ const allowDuplicatesInTimeScale =
+ !w.config.xaxis.labels.showDuplicates && this.ctx.timeScale
+
+ if (
+ !Array.isArray(label) &&
+ (String(label) === 'NaN' ||
+ (drawnLabels.indexOf(label) >= 0 && allowDuplicatesInTimeScale))
+ ) {
+ label = ''
+ }
+
+ return {
+ x,
+ text: label,
+ textRect,
+ isBold,
+ }
+ }
+
+ checkLabelBasedOnTickamount(i, label, labelsLen) {
+ const w = this.w
+
+ let ticks = w.config.xaxis.tickAmount
+ if (ticks === 'dataPoints') ticks = Math.round(w.globals.gridWidth / 120)
+
+ if (ticks > labelsLen) return label
+ let tickMultiple = Math.round(labelsLen / (ticks + 1))
+
+ if (i % tickMultiple === 0) {
+ return label
+ } else {
+ label.text = ''
+ }
+
+ return label
+ }
+
+ checkForOverflowingLabels(
+ i,
+ label,
+ labelsLen,
+ drawnLabels,
+ drawnLabelsRects
+ ) {
+ const w = this.w
+
+ if (i === 0) {
+ // check if first label is being truncated
+ if (w.globals.skipFirstTimelinelabel) {
+ label.text = ''
+ }
+ }
+
+ if (i === labelsLen - 1) {
+ // check if last label is being truncated
+ if (w.globals.skipLastTimelinelabel) {
+ label.text = ''
+ }
+ }
+
+ if (w.config.xaxis.labels.hideOverlappingLabels && drawnLabels.length > 0) {
+ const prev = drawnLabelsRects[drawnLabelsRects.length - 1]
+ if (
+ label.x <
+ prev.textRect.width /
+ (w.globals.rotateXLabels
+ ? Math.abs(w.config.xaxis.labels.rotate) / 12
+ : 1.01) +
+ prev.x
+ ) {
+ label.text = ''
+ }
+ }
+
+ return label
+ }
+
+ checkForReversedLabels(i, labels) {
+ const w = this.w
+ if (w.config.yaxis[i] && w.config.yaxis[i].reversed) {
+ labels.reverse()
+ }
+ return labels
+ }
+
+ yAxisAllSeriesCollapsed(index) {
+ const gl = this.w.globals
+
+ return !gl.seriesYAxisMap[index].some((si) => {
+ return gl.collapsedSeriesIndices.indexOf(si) === -1
+ })
+
+ }
+
+ // Method to translate annotation.yAxisIndex values from
+ // seriesName-as-a-string values to seriesName-as-an-array values (old style
+ // series mapping to new style).
+ translateYAxisIndex(index) {
+ const w = this.w
+ const gl = w.globals
+ const yaxis = w.config.yaxis
+ let newStyle =
+ gl.series.length > yaxis.length
+ || yaxis.some((a) => Array.isArray(a.seriesName))
+ if (newStyle) {
+ return index
+ } else {
+ return gl.seriesYAxisReverseMap[index]
+ }
+ }
+
+ isYAxisHidden(index) {
+ const w = this.w
+ const yaxis = w.config.yaxis[index]
+
+ if (!yaxis.show || this.yAxisAllSeriesCollapsed(index)
+ ) {
+ return true
+ }
+ if (!yaxis.showForNullSeries) {
+ const seriesIndices = w.globals.seriesYAxisMap[index]
+ const coreUtils = new CoreUtils(this.ctx)
+ return seriesIndices.every((si) => coreUtils.isSeriesNull(si))
+ }
+ return false
+ }
+
+ // get the label color for y-axis
+ // realIndex is the actual series index, while i is the tick Index
+ getYAxisForeColor(yColors, realIndex) {
+ const w = this.w
+ if (Array.isArray(yColors) && w.globals.yAxisScale[realIndex]) {
+ this.ctx.theme.pushExtraColors(
+ yColors,
+ w.globals.yAxisScale[realIndex].result.length,
+ false
+ )
+ }
+ return yColors
+ }
+
+ drawYAxisTicks(
+ x,
+ tickAmount,
+ axisBorder,
+ axisTicks,
+ realIndex,
+ labelsDivider,
+ elYaxis
+ ) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+
+ // initial label position = 0;
+ let tY = w.globals.translateY + w.config.yaxis[realIndex].labels.offsetY
+ if (w.globals.isBarHorizontal) {
+ tY = 0
+ } else if (w.config.chart.type === 'heatmap') {
+ tY += labelsDivider / 2
+ }
+
+ if (axisTicks.show && tickAmount > 0) {
+ if (w.config.yaxis[realIndex].opposite === true) x = x + axisTicks.width
+
+ for (let i = tickAmount; i >= 0; i--) {
+ let elTick = graphics.drawLine(
+ x + axisBorder.offsetX - axisTicks.width + axisTicks.offsetX,
+ tY + axisTicks.offsetY,
+ x + axisBorder.offsetX + axisTicks.offsetX,
+ tY + axisTicks.offsetY,
+ axisTicks.color
+ )
+ elYaxis.add(elTick)
+ tY += labelsDivider
+ }
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/Grid.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/Grid.js
new file mode 100644
index 0000000..99373a3
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/Grid.js
@@ -0,0 +1,512 @@
+import Graphics from '../Graphics'
+import XAxis from './XAxis'
+import AxesUtils from './AxesUtils'
+
+/**
+ * ApexCharts Grid Class for drawing Cartesian Grid.
+ *
+ * @module Grid
+ **/
+
+class Grid {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ const w = this.w
+ this.xaxisLabels = w.globals.labels.slice()
+ this.axesUtils = new AxesUtils(ctx)
+
+ this.isRangeBar = w.globals.seriesRange.length && w.globals.isBarHorizontal
+
+ if (w.globals.timescaleLabels.length > 0) {
+ // timescaleLabels labels are there
+ this.xaxisLabels = w.globals.timescaleLabels.slice()
+ }
+ }
+
+ drawGridArea(elGrid = null) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+
+ if (!elGrid) {
+ elGrid = graphics.group({ class: 'apexcharts-grid' })
+ }
+
+ const elVerticalLine = graphics.drawLine(
+ w.globals.padHorizontal,
+ 1,
+ w.globals.padHorizontal,
+ w.globals.gridHeight,
+ 'transparent'
+ )
+
+ const elHorzLine = graphics.drawLine(
+ w.globals.padHorizontal,
+ w.globals.gridHeight,
+ w.globals.gridWidth,
+ w.globals.gridHeight,
+ 'transparent'
+ )
+
+ elGrid.add(elHorzLine)
+ elGrid.add(elVerticalLine)
+
+ return elGrid
+ }
+
+ drawGrid() {
+ const gl = this.w.globals
+
+ if (gl.axisCharts) {
+ const elgrid = this.renderGrid()
+ this.drawGridArea(elgrid.el)
+ return elgrid
+ }
+ return null
+ }
+
+ createGridMask() {
+ const w = this.w
+ const gl = w.globals
+ const graphics = new Graphics(this.ctx)
+
+ const strokeSize = Array.isArray(w.config.stroke.width)
+ ? Math.max(...w.config.stroke.width)
+ : w.config.stroke.width
+
+ const createClipPath = (id) => {
+ const clipPath = document.createElementNS(gl.SVGNS, 'clipPath')
+ clipPath.setAttribute('id', id)
+ return clipPath
+ }
+
+ gl.dom.elGridRectMask = createClipPath(`gridRectMask${gl.cuid}`)
+ gl.dom.elGridRectBarMask = createClipPath(`gridRectBarMask${gl.cuid}`)
+ gl.dom.elGridRectMarkerMask = createClipPath(`gridRectMarkerMask${gl.cuid}`)
+ gl.dom.elForecastMask = createClipPath(`forecastMask${gl.cuid}`)
+ gl.dom.elNonForecastMask = createClipPath(`nonForecastMask${gl.cuid}`)
+
+ const hasBar =
+ ['bar', 'rangeBar', 'candlestick', 'boxPlot'].includes(
+ w.config.chart.type
+ ) || w.globals.comboBarCount > 0
+
+ let barWidthLeft = 0
+ let barWidthRight = 0
+ if (hasBar && w.globals.isXNumeric && !w.globals.isBarHorizontal) {
+ barWidthLeft = Math.max(
+ w.config.grid.padding.left,
+ gl.barPadForNumericAxis
+ )
+ barWidthRight = Math.max(
+ w.config.grid.padding.right,
+ gl.barPadForNumericAxis
+ )
+ }
+
+ gl.dom.elGridRect = graphics.drawRect(
+ 0,
+ 0,
+ gl.gridWidth,
+ gl.gridHeight,
+ 0,
+ '#fff'
+ )
+
+ gl.dom.elGridRectBar = graphics.drawRect(
+ -strokeSize / 2 - barWidthLeft - 2,
+ -strokeSize / 2 - 2,
+ gl.gridWidth + strokeSize + barWidthRight + barWidthLeft + 4,
+ gl.gridHeight + strokeSize + 4,
+ 0,
+ '#fff'
+ )
+
+ const markerSize = w.globals.markers.largestSize
+
+ gl.dom.elGridRectMarker = graphics.drawRect(
+ -markerSize,
+ -markerSize,
+ gl.gridWidth + markerSize * 2,
+ gl.gridHeight + markerSize * 2,
+ 0,
+ '#fff'
+ )
+
+ gl.dom.elGridRectMask.appendChild(gl.dom.elGridRect.node)
+ gl.dom.elGridRectBarMask.appendChild(gl.dom.elGridRectBar.node)
+ gl.dom.elGridRectMarkerMask.appendChild(gl.dom.elGridRectMarker.node)
+
+ const defs = gl.dom.baseEl.querySelector('defs')
+ defs.appendChild(gl.dom.elGridRectMask)
+ defs.appendChild(gl.dom.elGridRectBarMask)
+ defs.appendChild(gl.dom.elGridRectMarkerMask)
+ defs.appendChild(gl.dom.elForecastMask)
+ defs.appendChild(gl.dom.elNonForecastMask)
+ }
+
+ _drawGridLines({ i, x1, y1, x2, y2, xCount, parent }) {
+ const w = this.w
+
+ const shouldDraw = () => {
+ if (i === 0 && w.globals.skipFirstTimelinelabel) return false
+ if (
+ i === xCount - 1 &&
+ w.globals.skipLastTimelinelabel &&
+ !w.config.xaxis.labels.formatter
+ )
+ return false
+ if (w.config.chart.type === 'radar') return false
+ return true
+ }
+
+ if (shouldDraw()) {
+ if (w.config.grid.xaxis.lines.show) {
+ this._drawGridLine({ i, x1, y1, x2, y2, xCount, parent })
+ }
+
+ let y_2 = 0
+ if (
+ w.globals.hasXaxisGroups &&
+ w.config.xaxis.tickPlacement === 'between'
+ ) {
+ const groups = w.globals.groups
+ if (groups) {
+ let gacc = 0
+ for (let gi = 0; gacc < i && gi < groups.length; gi++) {
+ gacc += groups[gi].cols
+ }
+ if (gacc === i) {
+ y_2 = w.globals.xAxisLabelsHeight * 0.6
+ }
+ }
+ }
+
+ const xAxis = new XAxis(this.ctx)
+ xAxis.drawXaxisTicks(x1, y_2, w.globals.dom.elGraphical)
+ }
+ }
+
+ _drawGridLine({ i, x1, y1, x2, y2, xCount, parent }) {
+ const w = this.w
+ const isHorzLine = parent.node.classList.contains(
+ 'apexcharts-gridlines-horizontal'
+ )
+ const offX = w.globals.barPadForNumericAxis
+
+ const excludeBorders =
+ (y1 === 0 && y2 === 0) ||
+ (x1 === 0 && x2 === 0) ||
+ (y1 === w.globals.gridHeight && y2 === w.globals.gridHeight) ||
+ (w.globals.isBarHorizontal && (i === 0 || i === xCount - 1))
+
+ const graphics = new Graphics(this)
+ const line = graphics.drawLine(
+ x1 - (isHorzLine ? offX : 0),
+ y1,
+ x2 + (isHorzLine ? offX : 0),
+ y2,
+ w.config.grid.borderColor,
+ w.config.grid.strokeDashArray
+ )
+ line.node.classList.add('apexcharts-gridline')
+
+ if (excludeBorders && w.config.grid.show) {
+ this.elGridBorders.add(line)
+ } else {
+ parent.add(line)
+ }
+ }
+
+ _drawGridBandRect({ c, x1, y1, x2, y2, type }) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+ const offX = w.globals.barPadForNumericAxis
+
+ const color = w.config.grid[type].colors[c]
+
+ const rect = graphics.drawRect(
+ x1 - (type === 'row' ? offX : 0),
+ y1,
+ x2 + (type === 'row' ? offX * 2 : 0),
+ y2,
+ 0,
+ color,
+ w.config.grid[type].opacity
+ )
+ this.elg.add(rect)
+ rect.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
+ rect.node.classList.add(`apexcharts-grid-${type}`)
+ }
+
+ _drawXYLines({ xCount, tickAmount }) {
+ const w = this.w
+
+ const datetimeLines = ({ xC, x1, y1, x2, y2 }) => {
+ for (let i = 0; i < xC; i++) {
+ x1 = this.xaxisLabels[i].position
+ x2 = this.xaxisLabels[i].position
+
+ this._drawGridLines({
+ i,
+ x1,
+ y1,
+ x2,
+ y2,
+ xCount,
+ parent: this.elgridLinesV,
+ })
+ }
+ }
+
+ const categoryLines = ({ xC, x1, y1, x2, y2 }) => {
+ for (let i = 0; i < xC + (w.globals.isXNumeric ? 0 : 1); i++) {
+ if (i === 0 && xC === 1 && w.globals.dataPoints === 1) {
+ x1 = w.globals.gridWidth / 2
+ x2 = x1
+ }
+ this._drawGridLines({
+ i,
+ x1,
+ y1,
+ x2,
+ y2,
+ xCount,
+ parent: this.elgridLinesV,
+ })
+
+ x1 += w.globals.gridWidth / (w.globals.isXNumeric ? xC - 1 : xC)
+ x2 = x1
+ }
+ }
+
+ if (w.config.grid.xaxis.lines.show || w.config.xaxis.axisTicks.show) {
+ let x1 = w.globals.padHorizontal
+ let y1 = 0
+ let x2
+ let y2 = w.globals.gridHeight
+
+ if (w.globals.timescaleLabels.length) {
+ datetimeLines({ xC: xCount, x1, y1, x2, y2 })
+ } else {
+ if (w.globals.isXNumeric) {
+ xCount = w.globals.xAxisScale.result.length
+ }
+ categoryLines({ xC: xCount, x1, y1, x2, y2 })
+ }
+ }
+
+ if (w.config.grid.yaxis.lines.show) {
+ let x1 = 0
+ let y1 = 0
+ let y2 = 0
+ let x2 = w.globals.gridWidth
+ let tA = tickAmount + 1
+
+ if (this.isRangeBar) {
+ tA = w.globals.labels.length
+ }
+
+ for (let i = 0; i < tA + (this.isRangeBar ? 1 : 0); i++) {
+ this._drawGridLine({
+ i,
+ xCount: tA + (this.isRangeBar ? 1 : 0),
+ x1,
+ y1,
+ x2,
+ y2,
+ parent: this.elgridLinesH,
+ })
+
+ y1 += w.globals.gridHeight / (this.isRangeBar ? tA : tickAmount)
+ y2 = y1
+ }
+ }
+ }
+
+ _drawInvertedXYLines({ xCount }) {
+ const w = this.w
+
+ if (w.config.grid.xaxis.lines.show || w.config.xaxis.axisTicks.show) {
+ let x1 = w.globals.padHorizontal
+ let y1 = 0
+ let x2
+ let y2 = w.globals.gridHeight
+ for (let i = 0; i < xCount + 1; i++) {
+ if (w.config.grid.xaxis.lines.show) {
+ this._drawGridLine({
+ i,
+ xCount: xCount + 1,
+ x1,
+ y1,
+ x2,
+ y2,
+ parent: this.elgridLinesV,
+ })
+ }
+
+ const xAxis = new XAxis(this.ctx)
+ xAxis.drawXaxisTicks(x1, 0, w.globals.dom.elGraphical)
+ x1 += w.globals.gridWidth / xCount
+ x2 = x1
+ }
+ }
+
+ if (w.config.grid.yaxis.lines.show) {
+ let x1 = 0
+ let y1 = 0
+ let y2 = 0
+ let x2 = w.globals.gridWidth
+
+ for (let i = 0; i < w.globals.dataPoints + 1; i++) {
+ this._drawGridLine({
+ i,
+ xCount: w.globals.dataPoints + 1,
+ x1,
+ y1,
+ x2,
+ y2,
+ parent: this.elgridLinesH,
+ })
+
+ y1 += w.globals.gridHeight / w.globals.dataPoints
+ y2 = y1
+ }
+ }
+ }
+
+ renderGrid() {
+ const w = this.w
+ const gl = w.globals
+ const graphics = new Graphics(this.ctx)
+
+ this.elg = graphics.group({ class: 'apexcharts-grid' })
+ this.elgridLinesH = graphics.group({
+ class: 'apexcharts-gridlines-horizontal',
+ })
+ this.elgridLinesV = graphics.group({
+ class: 'apexcharts-gridlines-vertical',
+ })
+ this.elGridBorders = graphics.group({ class: 'apexcharts-grid-borders' })
+
+ this.elg.add(this.elgridLinesH)
+ this.elg.add(this.elgridLinesV)
+
+ if (!w.config.grid.show) {
+ this.elgridLinesV.hide()
+ this.elgridLinesH.hide()
+ this.elGridBorders.hide()
+ }
+
+ let gridAxisIndex = 0
+ while (
+ gridAxisIndex < gl.seriesYAxisMap.length &&
+ gl.ignoreYAxisIndexes.includes(gridAxisIndex)
+ ) {
+ gridAxisIndex++
+ }
+ if (gridAxisIndex === gl.seriesYAxisMap.length) {
+ gridAxisIndex = 0
+ }
+
+ let yTickAmount = gl.yAxisScale[gridAxisIndex].result.length - 1
+
+ let xCount
+
+ if (!gl.isBarHorizontal || this.isRangeBar) {
+ xCount = this.xaxisLabels.length
+
+ if (this.isRangeBar) {
+ yTickAmount = gl.labels.length
+
+ if (w.config.xaxis.tickAmount && w.config.xaxis.labels.formatter) {
+ xCount = w.config.xaxis.tickAmount
+ }
+ if (
+ gl.yAxisScale?.[gridAxisIndex]?.result?.length > 0 &&
+ w.config.xaxis.type !== 'datetime'
+ ) {
+ xCount = gl.yAxisScale[gridAxisIndex].result.length - 1
+ }
+ }
+
+ this._drawXYLines({ xCount, tickAmount: yTickAmount })
+ } else {
+ xCount = yTickAmount
+
+ // for horizontal bar chart, get the xaxis tickamount
+ yTickAmount = gl.xTickAmount
+ this._drawInvertedXYLines({ xCount, tickAmount: yTickAmount })
+ }
+
+ this.drawGridBands(xCount, yTickAmount)
+ return {
+ el: this.elg,
+ elGridBorders: this.elGridBorders,
+ xAxisTickWidth: gl.gridWidth / xCount,
+ }
+ }
+
+ drawGridBands(xCount, tickAmount) {
+ const w = this.w
+
+ const drawBands = (type, count, x1, y1, x2, y2) => {
+ for (let i = 0, c = 0; i < count; i++, c++) {
+ if (c >= w.config.grid[type].colors.length) {
+ c = 0
+ }
+ this._drawGridBandRect({ c, x1, y1, x2, y2, type })
+ y1 += w.globals.gridHeight / tickAmount
+ }
+ }
+
+ if (w.config.grid.row.colors?.length > 0) {
+ drawBands(
+ 'row',
+ tickAmount,
+ 0,
+ 0,
+ w.globals.gridWidth,
+ w.globals.gridHeight / tickAmount
+ )
+ }
+
+ if (w.config.grid.column.colors?.length > 0) {
+ let xc =
+ !w.globals.isBarHorizontal &&
+ w.config.xaxis.tickPlacement === 'on' &&
+ (w.config.xaxis.type === 'category' ||
+ w.config.xaxis.convertedCatToNumeric)
+ ? xCount - 1
+ : xCount
+
+ if (w.globals.isXNumeric) {
+ xc = w.globals.xAxisScale.result.length - 1
+ }
+
+ let x1 = w.globals.padHorizontal
+ let y1 = 0
+ let x2 = w.globals.padHorizontal + w.globals.gridWidth / xc
+ let y2 = w.globals.gridHeight
+
+ for (let i = 0, c = 0; i < xCount; i++, c++) {
+ if (c >= w.config.grid.column.colors.length) {
+ c = 0
+ }
+
+ if (w.config.xaxis.type === 'datetime') {
+ x1 = this.xaxisLabels[i].position
+ x2 =
+ (this.xaxisLabels[i + 1]?.position || w.globals.gridWidth) -
+ this.xaxisLabels[i].position
+ }
+
+ this._drawGridBandRect({ c, x1, y1, x2, y2, type: 'column' })
+ x1 += w.globals.gridWidth / xc
+ }
+ }
+ }
+}
+
+export default Grid
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/XAxis.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/XAxis.js
new file mode 100644
index 0000000..c6f1e7e
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/XAxis.js
@@ -0,0 +1,683 @@
+import Graphics from '../Graphics'
+import AxesUtils from './AxesUtils'
+
+/**
+ * ApexCharts XAxis Class for drawing X-Axis.
+ *
+ * @module XAxis
+ **/
+
+export default class XAxis {
+ constructor(ctx, elgrid) {
+ this.ctx = ctx
+ this.elgrid = elgrid
+ this.w = ctx.w
+
+ const w = this.w
+ this.axesUtils = new AxesUtils(ctx)
+
+ this.xaxisLabels = w.globals.labels.slice()
+ if (w.globals.timescaleLabels.length > 0 && !w.globals.isBarHorizontal) {
+ // timeline labels are there and chart is not rangeabr timeline
+ this.xaxisLabels = w.globals.timescaleLabels.slice()
+ }
+
+ if (w.config.xaxis.overwriteCategories) {
+ this.xaxisLabels = w.config.xaxis.overwriteCategories
+ }
+ this.drawnLabels = []
+ this.drawnLabelsRects = []
+
+ if (w.config.xaxis.position === 'top') {
+ this.offY = 0
+ } else {
+ this.offY = w.globals.gridHeight
+ }
+ this.offY = this.offY + w.config.xaxis.axisBorder.offsetY
+ this.isCategoryBarHorizontal =
+ w.config.chart.type === 'bar' && w.config.plotOptions.bar.horizontal
+
+ this.xaxisFontSize = w.config.xaxis.labels.style.fontSize
+ this.xaxisFontFamily = w.config.xaxis.labels.style.fontFamily
+ this.xaxisForeColors = w.config.xaxis.labels.style.colors
+ this.xaxisBorderWidth = w.config.xaxis.axisBorder.width
+ if (this.isCategoryBarHorizontal) {
+ this.xaxisBorderWidth = w.config.yaxis[0].axisBorder.width.toString()
+ }
+
+ if (this.xaxisBorderWidth.indexOf('%') > -1) {
+ this.xaxisBorderWidth =
+ (w.globals.gridWidth * parseInt(this.xaxisBorderWidth, 10)) / 100
+ } else {
+ this.xaxisBorderWidth = parseInt(this.xaxisBorderWidth, 10)
+ }
+ this.xaxisBorderHeight = w.config.xaxis.axisBorder.height
+
+ // For bars, we will only consider single y xais,
+ // as we are not providing multiple yaxis for bar charts
+ this.yaxis = w.config.yaxis[0]
+ }
+
+ drawXaxis() {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+
+ let elXaxis = graphics.group({
+ class: 'apexcharts-xaxis',
+ transform: `translate(${w.config.xaxis.offsetX}, ${w.config.xaxis.offsetY})`,
+ })
+
+ let elXaxisTexts = graphics.group({
+ class: 'apexcharts-xaxis-texts-g',
+ transform: `translate(${w.globals.translateXAxisX}, ${w.globals.translateXAxisY})`,
+ })
+
+ elXaxis.add(elXaxisTexts)
+
+ let labels = []
+
+ for (let i = 0; i < this.xaxisLabels.length; i++) {
+ labels.push(this.xaxisLabels[i])
+ }
+
+ this.drawXAxisLabelAndGroup(
+ true,
+ graphics,
+ elXaxisTexts,
+ labels,
+ w.globals.isXNumeric,
+ (i, colWidth) => colWidth
+ )
+
+ if (w.globals.hasXaxisGroups) {
+ let labelsGroup = w.globals.groups
+
+ labels = []
+ for (let i = 0; i < labelsGroup.length; i++) {
+ labels.push(labelsGroup[i].title)
+ }
+
+ let overwriteStyles = {}
+ if (w.config.xaxis.group.style) {
+ overwriteStyles.xaxisFontSize = w.config.xaxis.group.style.fontSize
+ overwriteStyles.xaxisFontFamily = w.config.xaxis.group.style.fontFamily
+ overwriteStyles.xaxisForeColors = w.config.xaxis.group.style.colors
+ overwriteStyles.fontWeight = w.config.xaxis.group.style.fontWeight
+ overwriteStyles.cssClass = w.config.xaxis.group.style.cssClass
+ }
+
+ this.drawXAxisLabelAndGroup(
+ false,
+ graphics,
+ elXaxisTexts,
+ labels,
+ false,
+ (i, colWidth) => labelsGroup[i].cols * colWidth,
+ overwriteStyles
+ )
+ }
+
+ if (w.config.xaxis.title.text !== undefined) {
+ let elXaxisTitle = graphics.group({
+ class: 'apexcharts-xaxis-title',
+ })
+
+ let elXAxisTitleText = graphics.drawText({
+ x: w.globals.gridWidth / 2 + w.config.xaxis.title.offsetX,
+ y:
+ this.offY +
+ parseFloat(this.xaxisFontSize) +
+ (w.config.xaxis.position === 'bottom'
+ ? w.globals.xAxisLabelsHeight
+ : -w.globals.xAxisLabelsHeight - 10) +
+ w.config.xaxis.title.offsetY,
+ text: w.config.xaxis.title.text,
+ textAnchor: 'middle',
+ fontSize: w.config.xaxis.title.style.fontSize,
+ fontFamily: w.config.xaxis.title.style.fontFamily,
+ fontWeight: w.config.xaxis.title.style.fontWeight,
+ foreColor: w.config.xaxis.title.style.color,
+ cssClass:
+ 'apexcharts-xaxis-title-text ' + w.config.xaxis.title.style.cssClass,
+ })
+
+ elXaxisTitle.add(elXAxisTitleText)
+
+ elXaxis.add(elXaxisTitle)
+ }
+
+ if (w.config.xaxis.axisBorder.show) {
+ const offX = w.globals.barPadForNumericAxis
+ let elHorzLine = graphics.drawLine(
+ w.globals.padHorizontal + w.config.xaxis.axisBorder.offsetX - offX,
+ this.offY,
+ this.xaxisBorderWidth + offX,
+ this.offY,
+ w.config.xaxis.axisBorder.color,
+ 0,
+ this.xaxisBorderHeight
+ )
+ if (this.elgrid && this.elgrid.elGridBorders && w.config.grid.show) {
+ this.elgrid.elGridBorders.add(elHorzLine)
+ } else {
+ elXaxis.add(elHorzLine)
+ }
+ }
+
+ return elXaxis
+ }
+
+ drawXAxisLabelAndGroup(
+ isLeafGroup,
+ graphics,
+ elXaxisTexts,
+ labels,
+ isXNumeric,
+ colWidthCb,
+ overwriteStyles = {}
+ ) {
+ let drawnLabels = []
+ let drawnLabelsRects = []
+ let w = this.w
+
+ const xaxisFontSize = overwriteStyles.xaxisFontSize || this.xaxisFontSize
+ const xaxisFontFamily =
+ overwriteStyles.xaxisFontFamily || this.xaxisFontFamily
+ const xaxisForeColors =
+ overwriteStyles.xaxisForeColors || this.xaxisForeColors
+ const fontWeight =
+ overwriteStyles.fontWeight || w.config.xaxis.labels.style.fontWeight
+ const cssClass =
+ overwriteStyles.cssClass || w.config.xaxis.labels.style.cssClass
+
+ let colWidth
+
+ // initial x Position (keep adding column width in the loop)
+ let xPos = w.globals.padHorizontal
+
+ let labelsLen = labels.length
+
+ /**
+ * labelsLen can be different (whether you are drawing x-axis labels or x-axis group labels)
+ * hence, we introduce dataPoints to be consistent.
+ * Also, in datetime/numeric xaxis, dataPoints can be misleading, so we resort to labelsLen for such xaxis type
+ */
+ let dataPoints =
+ w.config.xaxis.type === 'category' ? w.globals.dataPoints : labelsLen
+
+ // when all series are collapsed, fixes #3381
+ if (dataPoints === 0 && labelsLen > dataPoints) dataPoints = labelsLen
+
+ if (isXNumeric) {
+ let len = dataPoints > 1 ? dataPoints - 1 : dataPoints
+ colWidth = w.globals.gridWidth / Math.min(len, labelsLen - 1)
+
+ xPos = xPos + colWidthCb(0, colWidth) / 2 + w.config.xaxis.labels.offsetX
+ } else {
+ colWidth = w.globals.gridWidth / dataPoints
+ xPos = xPos + colWidthCb(0, colWidth) + w.config.xaxis.labels.offsetX
+ }
+
+ for (let i = 0; i <= labelsLen - 1; i++) {
+ let x = xPos - colWidthCb(i, colWidth) / 2 + w.config.xaxis.labels.offsetX
+
+ if (
+ i === 0 &&
+ labelsLen === 1 &&
+ colWidth / 2 === xPos &&
+ dataPoints === 1
+ ) {
+ // single datapoint
+ x = w.globals.gridWidth / 2
+ }
+ let label = this.axesUtils.getLabel(
+ labels,
+ w.globals.timescaleLabels,
+ x,
+ i,
+ drawnLabels,
+ xaxisFontSize,
+ isLeafGroup
+ )
+
+ let offsetYCorrection = 28
+ if (w.globals.rotateXLabels && isLeafGroup) {
+ offsetYCorrection = 22
+ }
+
+ if (w.config.xaxis.title.text && w.config.xaxis.position === 'top') {
+ offsetYCorrection += parseFloat(w.config.xaxis.title.style.fontSize) + 2
+ }
+
+ if (!isLeafGroup) {
+ offsetYCorrection =
+ offsetYCorrection +
+ parseFloat(xaxisFontSize) +
+ (w.globals.xAxisLabelsHeight - w.globals.xAxisGroupLabelsHeight) +
+ (w.globals.rotateXLabels ? 10 : 0)
+ }
+
+ const isCategoryTickAmounts =
+ typeof w.config.xaxis.tickAmount !== 'undefined' &&
+ w.config.xaxis.tickAmount !== 'dataPoints' &&
+ w.config.xaxis.type !== 'datetime'
+
+ if (isCategoryTickAmounts) {
+ label = this.axesUtils.checkLabelBasedOnTickamount(i, label, labelsLen)
+ } else {
+ label = this.axesUtils.checkForOverflowingLabels(
+ i,
+ label,
+ labelsLen,
+ drawnLabels,
+ drawnLabelsRects
+ )
+ }
+
+ const getCatForeColor = () => {
+ return isLeafGroup && w.config.xaxis.convertedCatToNumeric
+ ? xaxisForeColors[w.globals.minX + i - 1]
+ : xaxisForeColors[i]
+ }
+
+ if (w.config.xaxis.labels.show) {
+ let elText = graphics.drawText({
+ x: label.x,
+ y:
+ this.offY +
+ w.config.xaxis.labels.offsetY +
+ offsetYCorrection -
+ (w.config.xaxis.position === 'top'
+ ? w.globals.xAxisHeight + w.config.xaxis.axisTicks.height - 2
+ : 0),
+ text: label.text,
+ textAnchor: 'middle',
+ fontWeight: label.isBold ? 600 : fontWeight,
+ fontSize: xaxisFontSize,
+ fontFamily: xaxisFontFamily,
+ foreColor: Array.isArray(xaxisForeColors)
+ ? getCatForeColor()
+ : xaxisForeColors,
+ isPlainText: false,
+ cssClass:
+ (isLeafGroup
+ ? 'apexcharts-xaxis-label '
+ : 'apexcharts-xaxis-group-label ') + cssClass,
+ })
+ elXaxisTexts.add(elText)
+
+ elText.on('click', (e) => {
+ if (typeof w.config.chart.events.xAxisLabelClick === 'function') {
+ const opts = Object.assign({}, w, {
+ labelIndex: i,
+ })
+
+ w.config.chart.events.xAxisLabelClick(e, this.ctx, opts)
+ }
+ })
+
+ if (isLeafGroup) {
+ let elTooltipTitle = document.createElementNS(
+ w.globals.SVGNS,
+ 'title'
+ )
+ elTooltipTitle.textContent = Array.isArray(label.text)
+ ? label.text.join(' ')
+ : label.text
+ elText.node.appendChild(elTooltipTitle)
+ if (label.text !== '') {
+ drawnLabels.push(label.text)
+ drawnLabelsRects.push(label)
+ }
+ }
+ }
+ if (i < labelsLen - 1) {
+ xPos = xPos + colWidthCb(i + 1, colWidth)
+ }
+ }
+ }
+
+ // this actually becomes the vertical axis (for bar charts)
+ drawXaxisInversed(realIndex) {
+ let w = this.w
+ let graphics = new Graphics(this.ctx)
+
+ let translateYAxisX = w.config.yaxis[0].opposite
+ ? w.globals.translateYAxisX[realIndex]
+ : 0
+
+ let elYaxis = graphics.group({
+ class: 'apexcharts-yaxis apexcharts-xaxis-inversed',
+ rel: realIndex,
+ })
+
+ let elYaxisTexts = graphics.group({
+ class: 'apexcharts-yaxis-texts-g apexcharts-xaxis-inversed-texts-g',
+ transform: 'translate(' + translateYAxisX + ', 0)',
+ })
+
+ elYaxis.add(elYaxisTexts)
+
+ let colHeight
+
+ // initial x Position (keep adding column width in the loop)
+ let yPos
+ let labels = []
+
+ if (w.config.yaxis[realIndex].show) {
+ for (let i = 0; i < this.xaxisLabels.length; i++) {
+ labels.push(this.xaxisLabels[i])
+ }
+ }
+
+ colHeight = w.globals.gridHeight / labels.length
+ yPos = -(colHeight / 2.2)
+
+ let lbFormatter = w.globals.yLabelFormatters[0]
+
+ const ylabels = w.config.yaxis[0].labels
+
+ if (ylabels.show) {
+ for (let i = 0; i <= labels.length - 1; i++) {
+ let label = typeof labels[i] === 'undefined' ? '' : labels[i]
+
+ label = lbFormatter(label, {
+ seriesIndex: realIndex,
+ dataPointIndex: i,
+ w,
+ })
+
+ const yColors = this.axesUtils.getYAxisForeColor(
+ ylabels.style.colors,
+ realIndex
+ )
+ const getForeColor = () => {
+ return Array.isArray(yColors) ? yColors[i] : yColors
+ }
+
+ let multiY = 0
+ if (Array.isArray(label)) {
+ multiY = (label.length / 2) * parseInt(ylabels.style.fontSize, 10)
+ }
+
+ let offsetX = ylabels.offsetX - 15
+ let textAnchor = 'end'
+ if (this.yaxis.opposite) {
+ textAnchor = 'start'
+ }
+ if (w.config.yaxis[0].labels.align === 'left') {
+ offsetX = ylabels.offsetX
+ textAnchor = 'start'
+ } else if (w.config.yaxis[0].labels.align === 'center') {
+ offsetX = ylabels.offsetX
+ textAnchor = 'middle'
+ } else if (w.config.yaxis[0].labels.align === 'right') {
+ textAnchor = 'end'
+ }
+
+ let elLabel = graphics.drawText({
+ x: offsetX,
+ y: yPos + colHeight + ylabels.offsetY - multiY,
+ text: label,
+ textAnchor,
+ foreColor: getForeColor(),
+ fontSize: ylabels.style.fontSize,
+ fontFamily: ylabels.style.fontFamily,
+ fontWeight: ylabels.style.fontWeight,
+ isPlainText: false,
+ cssClass: 'apexcharts-yaxis-label ' + ylabels.style.cssClass,
+ maxWidth: ylabels.maxWidth,
+ })
+
+ elYaxisTexts.add(elLabel)
+
+ elLabel.on('click', (e) => {
+ if (typeof w.config.chart.events.xAxisLabelClick === 'function') {
+ const opts = Object.assign({}, w, {
+ labelIndex: i,
+ })
+
+ w.config.chart.events.xAxisLabelClick(e, this.ctx, opts)
+ }
+ })
+
+ let elTooltipTitle = document.createElementNS(w.globals.SVGNS, 'title')
+ elTooltipTitle.textContent = Array.isArray(label)
+ ? label.join(' ')
+ : label
+ elLabel.node.appendChild(elTooltipTitle)
+
+ if (w.config.yaxis[realIndex].labels.rotate !== 0) {
+ let labelRotatingCenter = graphics.rotateAroundCenter(elLabel.node)
+ elLabel.node.setAttribute(
+ 'transform',
+ `rotate(${w.config.yaxis[realIndex].labels.rotate} 0 ${labelRotatingCenter.y})`
+ )
+ }
+ yPos = yPos + colHeight
+ }
+ }
+
+ if (w.config.yaxis[0].title.text !== undefined) {
+ let elXaxisTitle = graphics.group({
+ class: 'apexcharts-yaxis-title apexcharts-xaxis-title-inversed',
+ transform: 'translate(' + translateYAxisX + ', 0)',
+ })
+
+ let elXAxisTitleText = graphics.drawText({
+ x: w.config.yaxis[0].title.offsetX,
+ y: w.globals.gridHeight / 2 + w.config.yaxis[0].title.offsetY,
+ text: w.config.yaxis[0].title.text,
+ textAnchor: 'middle',
+ foreColor: w.config.yaxis[0].title.style.color,
+ fontSize: w.config.yaxis[0].title.style.fontSize,
+ fontWeight: w.config.yaxis[0].title.style.fontWeight,
+ fontFamily: w.config.yaxis[0].title.style.fontFamily,
+ cssClass:
+ 'apexcharts-yaxis-title-text ' +
+ w.config.yaxis[0].title.style.cssClass,
+ })
+
+ elXaxisTitle.add(elXAxisTitleText)
+
+ elYaxis.add(elXaxisTitle)
+ }
+
+ let offX = 0
+ if (this.isCategoryBarHorizontal && w.config.yaxis[0].opposite) {
+ offX = w.globals.gridWidth
+ }
+ const axisBorder = w.config.xaxis.axisBorder
+ if (axisBorder.show) {
+ let elVerticalLine = graphics.drawLine(
+ w.globals.padHorizontal + axisBorder.offsetX + offX,
+ 1 + axisBorder.offsetY,
+ w.globals.padHorizontal + axisBorder.offsetX + offX,
+ w.globals.gridHeight + axisBorder.offsetY,
+ axisBorder.color,
+ 0
+ )
+
+ if (this.elgrid && this.elgrid.elGridBorders && w.config.grid.show) {
+ this.elgrid.elGridBorders.add(elVerticalLine)
+ } else {
+ elYaxis.add(elVerticalLine)
+ }
+ }
+
+ if (w.config.yaxis[0].axisTicks.show) {
+ this.axesUtils.drawYAxisTicks(
+ offX,
+ labels.length,
+ w.config.yaxis[0].axisBorder,
+ w.config.yaxis[0].axisTicks,
+ 0,
+ colHeight,
+ elYaxis
+ )
+ }
+
+ return elYaxis
+ }
+
+ drawXaxisTicks(x1, y2, appendToElement) {
+ let w = this.w
+ let x2 = x1
+
+ if (x1 < 0 || x1 - 2 > w.globals.gridWidth) return
+
+ let y1 = this.offY + w.config.xaxis.axisTicks.offsetY
+ y2 = y2 + y1 + w.config.xaxis.axisTicks.height
+ if (w.config.xaxis.position === 'top') {
+ y2 = y1 - w.config.xaxis.axisTicks.height
+ }
+
+ if (w.config.xaxis.axisTicks.show) {
+ let graphics = new Graphics(this.ctx)
+
+ let line = graphics.drawLine(
+ x1 + w.config.xaxis.axisTicks.offsetX,
+ y1 + w.config.xaxis.offsetY,
+ x2 + w.config.xaxis.axisTicks.offsetX,
+ y2 + w.config.xaxis.offsetY,
+ w.config.xaxis.axisTicks.color
+ )
+
+ // we are not returning anything, but appending directly to the element passed in param
+ appendToElement.add(line)
+ line.node.classList.add('apexcharts-xaxis-tick')
+ }
+ }
+
+ getXAxisTicksPositions() {
+ const w = this.w
+ let xAxisTicksPositions = []
+
+ const xCount = this.xaxisLabels.length
+ let x1 = w.globals.padHorizontal
+
+ if (w.globals.timescaleLabels.length > 0) {
+ for (let i = 0; i < xCount; i++) {
+ x1 = this.xaxisLabels[i].position
+ xAxisTicksPositions.push(x1)
+ }
+ } else {
+ let xCountForCategoryCharts = xCount
+ for (let i = 0; i < xCountForCategoryCharts; i++) {
+ let x1Count = xCountForCategoryCharts
+ if (w.globals.isXNumeric && w.config.chart.type !== 'bar') {
+ x1Count -= 1
+ }
+ x1 = x1 + w.globals.gridWidth / x1Count
+ xAxisTicksPositions.push(x1)
+ }
+ }
+
+ return xAxisTicksPositions
+ }
+
+ // to rotate x-axis labels or to put ... for longer text in xaxis
+ xAxisLabelCorrections() {
+ let w = this.w
+
+ let graphics = new Graphics(this.ctx)
+
+ let xAxis = w.globals.dom.baseEl.querySelector('.apexcharts-xaxis-texts-g')
+
+ let xAxisTexts = w.globals.dom.baseEl.querySelectorAll(
+ '.apexcharts-xaxis-texts-g text:not(.apexcharts-xaxis-group-label)'
+ )
+ let yAxisTextsInversed = w.globals.dom.baseEl.querySelectorAll(
+ '.apexcharts-yaxis-inversed text'
+ )
+ let xAxisTextsInversed = w.globals.dom.baseEl.querySelectorAll(
+ '.apexcharts-xaxis-inversed-texts-g text tspan'
+ )
+
+ if (w.globals.rotateXLabels || w.config.xaxis.labels.rotateAlways) {
+ for (let xat = 0; xat < xAxisTexts.length; xat++) {
+ let textRotatingCenter = graphics.rotateAroundCenter(xAxisTexts[xat])
+ textRotatingCenter.y = textRotatingCenter.y - 1 // + tickWidth/4;
+ textRotatingCenter.x = textRotatingCenter.x + 1
+
+ xAxisTexts[xat].setAttribute(
+ 'transform',
+ `rotate(${w.config.xaxis.labels.rotate} ${textRotatingCenter.x} ${textRotatingCenter.y})`
+ )
+
+ xAxisTexts[xat].setAttribute('text-anchor', `end`)
+
+ let offsetHeight = 10
+
+ xAxis.setAttribute('transform', `translate(0, ${-offsetHeight})`)
+
+ let tSpan = xAxisTexts[xat].childNodes
+
+ if (w.config.xaxis.labels.trim) {
+ Array.prototype.forEach.call(tSpan, (ts) => {
+ graphics.placeTextWithEllipsis(
+ ts,
+ ts.textContent,
+ w.globals.xAxisLabelsHeight -
+ (w.config.legend.position === 'bottom' ? 20 : 10)
+ )
+ })
+ }
+ }
+ } else {
+ let width = w.globals.gridWidth / (w.globals.labels.length + 1)
+
+ for (let xat = 0; xat < xAxisTexts.length; xat++) {
+ let tSpan = xAxisTexts[xat].childNodes
+
+ if (w.config.xaxis.labels.trim && w.config.xaxis.type !== 'datetime') {
+ Array.prototype.forEach.call(tSpan, (ts) => {
+ graphics.placeTextWithEllipsis(ts, ts.textContent, width)
+ })
+ }
+ }
+ }
+
+ if (yAxisTextsInversed.length > 0) {
+ // truncate rotated y axis in bar chart (x axis)
+ let firstLabelPosX =
+ yAxisTextsInversed[yAxisTextsInversed.length - 1].getBBox()
+ let lastLabelPosX = yAxisTextsInversed[0].getBBox()
+
+ if (firstLabelPosX.x < -20) {
+ yAxisTextsInversed[
+ yAxisTextsInversed.length - 1
+ ].parentNode.removeChild(
+ yAxisTextsInversed[yAxisTextsInversed.length - 1]
+ )
+ }
+
+ if (
+ lastLabelPosX.x + lastLabelPosX.width > w.globals.gridWidth &&
+ !w.globals.isBarHorizontal
+ ) {
+ yAxisTextsInversed[0].parentNode.removeChild(yAxisTextsInversed[0])
+ }
+
+ // truncate rotated x axis in bar chart (y axis)
+ for (let xat = 0; xat < xAxisTextsInversed.length; xat++) {
+ graphics.placeTextWithEllipsis(
+ xAxisTextsInversed[xat],
+ xAxisTextsInversed[xat].textContent,
+ w.config.yaxis[0].labels.maxWidth -
+ (w.config.yaxis[0].title.text
+ ? parseFloat(w.config.yaxis[0].title.style.fontSize) * 2
+ : 0) -
+ 15
+ )
+ }
+ }
+ }
+
+ // renderXAxisBands() {
+ // let w = this.w;
+
+ // let plotBand = document.createElementNS(w.globals.SVGNS, 'rect')
+ // w.globals.dom.elGraphical.add(plotBand)
+ // }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/YAxis.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/YAxis.js
new file mode 100644
index 0000000..b97dd7d
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/axes/YAxis.js
@@ -0,0 +1,506 @@
+import Graphics from '../Graphics'
+import Utils from '../../utils/Utils'
+import AxesUtils from './AxesUtils'
+
+/**
+ * ApexCharts YAxis Class for drawing Y-Axis.
+ *
+ * @module YAxis
+ **/
+
+export default class YAxis {
+ constructor(ctx, elgrid) {
+ this.ctx = ctx
+ this.elgrid = elgrid
+ this.w = ctx.w
+ const w = this.w
+
+ this.xaxisFontSize = w.config.xaxis.labels.style.fontSize
+ this.axisFontFamily = w.config.xaxis.labels.style.fontFamily
+ this.xaxisForeColors = w.config.xaxis.labels.style.colors
+ this.isCategoryBarHorizontal =
+ w.config.chart.type === 'bar' && w.config.plotOptions.bar.horizontal
+ this.xAxisoffX =
+ w.config.xaxis.position === 'bottom' ? w.globals.gridHeight : 0
+ this.drawnLabels = []
+ this.axesUtils = new AxesUtils(ctx)
+ }
+
+ drawYaxis(realIndex) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+ const yaxisStyle = w.config.yaxis[realIndex].labels.style
+ const {
+ fontSize: yaxisFontSize,
+ fontFamily: yaxisFontFamily,
+ fontWeight: yaxisFontWeight,
+ } = yaxisStyle
+
+ const elYaxis = graphics.group({
+ class: 'apexcharts-yaxis',
+ rel: realIndex,
+ transform: `translate(${w.globals.translateYAxisX[realIndex]}, 0)`,
+ })
+
+ if (this.axesUtils.isYAxisHidden(realIndex)) return elYaxis
+
+ const elYaxisTexts = graphics.group({ class: 'apexcharts-yaxis-texts-g' })
+ elYaxis.add(elYaxisTexts)
+
+ const tickAmount = w.globals.yAxisScale[realIndex].result.length - 1
+ const labelsDivider = w.globals.gridHeight / tickAmount
+ const lbFormatter = w.globals.yLabelFormatters[realIndex]
+ let labels = this.axesUtils.checkForReversedLabels(
+ realIndex,
+ w.globals.yAxisScale[realIndex].result.slice()
+ )
+
+ if (w.config.yaxis[realIndex].labels.show) {
+ let lY = w.globals.translateY + w.config.yaxis[realIndex].labels.offsetY
+ if (w.globals.isBarHorizontal) lY = 0
+ else if (w.config.chart.type === 'heatmap') lY -= labelsDivider / 2
+ lY += parseInt(yaxisFontSize, 10) / 3
+
+ for (let i = tickAmount; i >= 0; i--) {
+ let val = lbFormatter(labels[i], i, w)
+ let xPad = w.config.yaxis[realIndex].labels.padding
+ if (w.config.yaxis[realIndex].opposite && w.config.yaxis.length !== 0)
+ xPad *= -1
+
+ const textAnchor = this.getTextAnchor(
+ w.config.yaxis[realIndex].labels.align,
+ w.config.yaxis[realIndex].opposite
+ )
+ const yColors = this.axesUtils.getYAxisForeColor(
+ yaxisStyle.colors,
+ realIndex
+ )
+ const foreColor = Array.isArray(yColors) ? yColors[i] : yColors
+
+ const existingYLabels = Utils.listToArray(
+ w.globals.dom.baseEl.querySelectorAll(
+ `.apexcharts-yaxis[rel='${realIndex}'] .apexcharts-yaxis-label tspan`
+ )
+ ).map((label) => label.textContent)
+
+ const label = graphics.drawText({
+ x: xPad,
+ y: lY,
+ text:
+ existingYLabels.includes(val) &&
+ !w.config.yaxis[realIndex].labels.showDuplicates
+ ? ''
+ : val,
+ textAnchor,
+ fontSize: yaxisFontSize,
+ fontFamily: yaxisFontFamily,
+ fontWeight: yaxisFontWeight,
+ maxWidth: w.config.yaxis[realIndex].labels.maxWidth,
+ foreColor,
+ isPlainText: false,
+ cssClass: `apexcharts-yaxis-label ${yaxisStyle.cssClass}`,
+ })
+
+ elYaxisTexts.add(label)
+ this.addTooltip(label, val)
+
+ if (w.config.yaxis[realIndex].labels.rotate !== 0) {
+ this.rotateLabel(
+ graphics,
+ label,
+ firstLabel,
+ w.config.yaxis[realIndex].labels.rotate
+ )
+ }
+
+ lY += labelsDivider
+ }
+ }
+
+ this.addYAxisTitle(graphics, elYaxis, realIndex)
+ this.addAxisBorder(graphics, elYaxis, realIndex, tickAmount, labelsDivider)
+
+ return elYaxis
+ }
+
+ getTextAnchor(align, opposite) {
+ if (align === 'left') return 'start'
+ if (align === 'center') return 'middle'
+ if (align === 'right') return 'end'
+ return opposite ? 'start' : 'end'
+ }
+
+ addTooltip(label, val) {
+ const elTooltipTitle = document.createElementNS(
+ this.w.globals.SVGNS,
+ 'title'
+ )
+ elTooltipTitle.textContent = Array.isArray(val) ? val.join(' ') : val
+ label.node.appendChild(elTooltipTitle)
+ }
+
+ rotateLabel(graphics, label, firstLabel, rotate) {
+ const firstLabelCenter = graphics.rotateAroundCenter(firstLabel.node)
+ const labelCenter = graphics.rotateAroundCenter(label.node)
+ label.node.setAttribute(
+ 'transform',
+ `rotate(${rotate} ${firstLabelCenter.x} ${labelCenter.y})`
+ )
+ }
+
+ addYAxisTitle(graphics, elYaxis, realIndex) {
+ const w = this.w
+ if (w.config.yaxis[realIndex].title.text !== undefined) {
+ const elYaxisTitle = graphics.group({ class: 'apexcharts-yaxis-title' })
+ const x = w.config.yaxis[realIndex].opposite
+ ? w.globals.translateYAxisX[realIndex]
+ : 0
+ const elYAxisTitleText = graphics.drawText({
+ x,
+ y:
+ w.globals.gridHeight / 2 +
+ w.globals.translateY +
+ w.config.yaxis[realIndex].title.offsetY,
+ text: w.config.yaxis[realIndex].title.text,
+ textAnchor: 'end',
+ foreColor: w.config.yaxis[realIndex].title.style.color,
+ fontSize: w.config.yaxis[realIndex].title.style.fontSize,
+ fontWeight: w.config.yaxis[realIndex].title.style.fontWeight,
+ fontFamily: w.config.yaxis[realIndex].title.style.fontFamily,
+ cssClass: `apexcharts-yaxis-title-text ${w.config.yaxis[realIndex].title.style.cssClass}`,
+ })
+ elYaxisTitle.add(elYAxisTitleText)
+ elYaxis.add(elYaxisTitle)
+ }
+ }
+
+ addAxisBorder(graphics, elYaxis, realIndex, tickAmount, labelsDivider) {
+ const w = this.w
+ const axisBorder = w.config.yaxis[realIndex].axisBorder
+ let x = 31 + axisBorder.offsetX
+ if (w.config.yaxis[realIndex].opposite) x = -31 - axisBorder.offsetX
+
+ if (axisBorder.show) {
+ const elVerticalLine = graphics.drawLine(
+ x,
+ w.globals.translateY + axisBorder.offsetY - 2,
+ x,
+ w.globals.gridHeight + w.globals.translateY + axisBorder.offsetY + 2,
+ axisBorder.color,
+ 0,
+ axisBorder.width
+ )
+ elYaxis.add(elVerticalLine)
+ }
+
+ if (w.config.yaxis[realIndex].axisTicks.show) {
+ this.axesUtils.drawYAxisTicks(
+ x,
+ tickAmount,
+ axisBorder,
+ w.config.yaxis[realIndex].axisTicks,
+ realIndex,
+ labelsDivider,
+ elYaxis
+ )
+ }
+ }
+
+ drawYaxisInversed(realIndex) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+
+ const elXaxis = graphics.group({
+ class: 'apexcharts-xaxis apexcharts-yaxis-inversed',
+ })
+
+ const elXaxisTexts = graphics.group({
+ class: 'apexcharts-xaxis-texts-g',
+ transform: `translate(${w.globals.translateXAxisX}, ${w.globals.translateXAxisY})`,
+ })
+
+ elXaxis.add(elXaxisTexts)
+
+ let tickAmount = w.globals.yAxisScale[realIndex].result.length - 1
+ const labelsDivider = w.globals.gridWidth / tickAmount + 0.1
+ let l = labelsDivider + w.config.xaxis.labels.offsetX
+ const lbFormatter = w.globals.xLabelFormatter
+ let labels = this.axesUtils.checkForReversedLabels(
+ realIndex,
+ w.globals.yAxisScale[realIndex].result.slice()
+ )
+ const timescaleLabels = w.globals.timescaleLabels
+
+ if (timescaleLabels.length > 0) {
+ this.xaxisLabels = timescaleLabels.slice()
+ labels = timescaleLabels.slice()
+ tickAmount = labels.length
+ }
+
+ if (w.config.xaxis.labels.show) {
+ for (
+ let i = timescaleLabels.length ? 0 : tickAmount;
+ timescaleLabels.length ? i < timescaleLabels.length : i >= 0;
+ timescaleLabels.length ? i++ : i--
+ ) {
+ let val = lbFormatter(labels[i], i, w)
+ let x =
+ w.globals.gridWidth +
+ w.globals.padHorizontal -
+ (l - labelsDivider + w.config.xaxis.labels.offsetX)
+
+ if (timescaleLabels.length) {
+ const label = this.axesUtils.getLabel(
+ labels,
+ timescaleLabels,
+ x,
+ i,
+ this.drawnLabels,
+ this.xaxisFontSize
+ )
+ x = label.x
+ val = label.text
+ this.drawnLabels.push(label.text)
+ if (i === 0 && w.globals.skipFirstTimelinelabel) val = ''
+ if (i === labels.length - 1 && w.globals.skipLastTimelinelabel)
+ val = ''
+ }
+
+ const elTick = graphics.drawText({
+ x,
+ y:
+ this.xAxisoffX +
+ w.config.xaxis.labels.offsetY +
+ 30 -
+ (w.config.xaxis.position === 'top'
+ ? w.globals.xAxisHeight + w.config.xaxis.axisTicks.height - 2
+ : 0),
+ text: val,
+ textAnchor: 'middle',
+ foreColor: Array.isArray(this.xaxisForeColors)
+ ? this.xaxisForeColors[realIndex]
+ : this.xaxisForeColors,
+ fontSize: this.xaxisFontSize,
+ fontFamily: this.xaxisFontFamily,
+ fontWeight: w.config.xaxis.labels.style.fontWeight,
+ isPlainText: false,
+ cssClass: `apexcharts-xaxis-label ${w.config.xaxis.labels.style.cssClass}`,
+ })
+
+ elXaxisTexts.add(elTick)
+ elTick.tspan(val)
+ this.addTooltip(elTick, val)
+ l += labelsDivider
+ }
+ }
+
+ this.inversedYAxisTitleText(elXaxis)
+ this.inversedYAxisBorder(elXaxis)
+
+ return elXaxis
+ }
+
+ inversedYAxisBorder(parent) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+ const axisBorder = w.config.xaxis.axisBorder
+
+ if (axisBorder.show) {
+ let lineCorrection = 0
+ if (w.config.chart.type === 'bar' && w.globals.isXNumeric)
+ lineCorrection -= 15
+
+ const elHorzLine = graphics.drawLine(
+ w.globals.padHorizontal + lineCorrection + axisBorder.offsetX,
+ this.xAxisoffX,
+ w.globals.gridWidth,
+ this.xAxisoffX,
+ axisBorder.color,
+ 0,
+ axisBorder.height
+ )
+
+ if (this.elgrid && this.elgrid.elGridBorders && w.config.grid.show) {
+ this.elgrid.elGridBorders.add(elHorzLine)
+ } else {
+ parent.add(elHorzLine)
+ }
+ }
+ }
+
+ inversedYAxisTitleText(parent) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+
+ if (w.config.xaxis.title.text !== undefined) {
+ const elYaxisTitle = graphics.group({
+ class: 'apexcharts-xaxis-title apexcharts-yaxis-title-inversed',
+ })
+ const elYAxisTitleText = graphics.drawText({
+ x: w.globals.gridWidth / 2 + w.config.xaxis.title.offsetX,
+ y:
+ this.xAxisoffX +
+ parseFloat(this.xaxisFontSize) +
+ parseFloat(w.config.xaxis.title.style.fontSize) +
+ w.config.xaxis.title.offsetY +
+ 20,
+ text: w.config.xaxis.title.text,
+ textAnchor: 'middle',
+ fontSize: w.config.xaxis.title.style.fontSize,
+ fontFamily: w.config.xaxis.title.style.fontFamily,
+ fontWeight: w.config.xaxis.title.style.fontWeight,
+ foreColor: w.config.xaxis.title.style.color,
+ cssClass: `apexcharts-xaxis-title-text ${w.config.xaxis.title.style.cssClass}`,
+ })
+
+ elYaxisTitle.add(elYAxisTitleText)
+ parent.add(elYaxisTitle)
+ }
+ }
+
+ yAxisTitleRotate(realIndex, yAxisOpposite) {
+ const w = this.w
+ const graphics = new Graphics(this.ctx)
+ const elYAxisLabelsWrap = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-yaxis[rel='${realIndex}'] .apexcharts-yaxis-texts-g`
+ )
+ const yAxisLabelsCoord = elYAxisLabelsWrap
+ ? elYAxisLabelsWrap.getBoundingClientRect()
+ : { width: 0, height: 0 }
+ const yAxisTitle = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-yaxis[rel='${realIndex}'] .apexcharts-yaxis-title text`
+ )
+ const yAxisTitleCoord = yAxisTitle
+ ? yAxisTitle.getBoundingClientRect()
+ : { width: 0, height: 0 }
+
+ if (yAxisTitle) {
+ const x = this.xPaddingForYAxisTitle(
+ realIndex,
+ yAxisLabelsCoord,
+ yAxisTitleCoord,
+ yAxisOpposite
+ )
+ yAxisTitle.setAttribute('x', x.xPos - (yAxisOpposite ? 10 : 0))
+ const titleRotatingCenter = graphics.rotateAroundCenter(yAxisTitle)
+ yAxisTitle.setAttribute(
+ 'transform',
+ `rotate(${
+ yAxisOpposite
+ ? w.config.yaxis[realIndex].title.rotate * -1
+ : w.config.yaxis[realIndex].title.rotate
+ } ${titleRotatingCenter.x} ${titleRotatingCenter.y})`
+ )
+ }
+ }
+
+ xPaddingForYAxisTitle(
+ realIndex,
+ yAxisLabelsCoord,
+ yAxisTitleCoord,
+ yAxisOpposite
+ ) {
+ const w = this.w
+ let x = 0
+ let padd = 10
+
+ if (w.config.yaxis[realIndex].title.text === undefined || realIndex < 0) {
+ return { xPos: x, padd: 0 }
+ }
+
+ if (yAxisOpposite) {
+ x =
+ yAxisLabelsCoord.width +
+ w.config.yaxis[realIndex].title.offsetX +
+ yAxisTitleCoord.width / 2 +
+ padd / 2
+ } else {
+ x =
+ yAxisLabelsCoord.width * -1 +
+ w.config.yaxis[realIndex].title.offsetX +
+ padd / 2 +
+ yAxisTitleCoord.width / 2
+ if (w.globals.isBarHorizontal) {
+ padd = 25
+ x =
+ yAxisLabelsCoord.width * -1 -
+ w.config.yaxis[realIndex].title.offsetX -
+ padd
+ }
+ }
+
+ return { xPos: x, padd }
+ }
+
+ setYAxisXPosition(yaxisLabelCoords, yTitleCoords) {
+ const w = this.w
+ let xLeft = 0
+ let xRight = 0
+ let leftOffsetX = 18
+ let rightOffsetX = 1
+
+ if (w.config.yaxis.length > 1) this.multipleYs = true
+
+ w.config.yaxis.forEach((yaxe, index) => {
+ const shouldNotDrawAxis =
+ w.globals.ignoreYAxisIndexes.includes(index) ||
+ !yaxe.show ||
+ yaxe.floating ||
+ yaxisLabelCoords[index].width === 0
+ const axisWidth =
+ yaxisLabelCoords[index].width + yTitleCoords[index].width
+
+ if (!yaxe.opposite) {
+ xLeft = w.globals.translateX - leftOffsetX
+ if (!shouldNotDrawAxis) leftOffsetX += axisWidth + 20
+ w.globals.translateYAxisX[index] = xLeft + yaxe.labels.offsetX
+ } else {
+ if (w.globals.isBarHorizontal) {
+ xRight = w.globals.gridWidth + w.globals.translateX - 1
+ w.globals.translateYAxisX[index] = xRight - yaxe.labels.offsetX
+ } else {
+ xRight = w.globals.gridWidth + w.globals.translateX + rightOffsetX
+ if (!shouldNotDrawAxis) rightOffsetX += axisWidth + 20
+ w.globals.translateYAxisX[index] = xRight - yaxe.labels.offsetX + 20
+ }
+ }
+ })
+ }
+
+ setYAxisTextAlignments() {
+ const w = this.w
+ const yaxis = Utils.listToArray(
+ w.globals.dom.baseEl.getElementsByClassName('apexcharts-yaxis')
+ )
+
+ yaxis.forEach((y, index) => {
+ const yaxe = w.config.yaxis[index]
+ if (yaxe && !yaxe.floating && yaxe.labels.align !== undefined) {
+ const yAxisInner = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-yaxis[rel='${index}'] .apexcharts-yaxis-texts-g`
+ )
+ const yAxisTexts = Utils.listToArray(
+ w.globals.dom.baseEl.querySelectorAll(
+ `.apexcharts-yaxis[rel='${index}'] .apexcharts-yaxis-label`
+ )
+ )
+ const rect = yAxisInner.getBoundingClientRect()
+
+ yAxisTexts.forEach((label) => {
+ label.setAttribute('text-anchor', yaxe.labels.align)
+ })
+
+ if (yaxe.labels.align === 'left' && !yaxe.opposite) {
+ yAxisInner.setAttribute('transform', `translate(-${rect.width}, 0)`)
+ } else if (yaxe.labels.align === 'center') {
+ yAxisInner.setAttribute(
+ 'transform',
+ `translate(${(rect.width / 2) * (!yaxe.opposite ? -1 : 1)}, 0)`
+ )
+ } else if (yaxe.labels.align === 'right' && yaxe.opposite) {
+ yAxisInner.setAttribute('transform', `translate(${rect.width}, 0)`)
+ }
+ }
+ })
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/Dimensions.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/Dimensions.js
new file mode 100644
index 0000000..a1e8e4f
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/Dimensions.js
@@ -0,0 +1,360 @@
+import YAxis from '../axes/YAxis'
+import Helpers from './Helpers'
+import DimXAxis from './XAxis'
+import DimYAxis from './YAxis'
+import Grid from './Grid'
+
+/**
+ * ApexCharts Dimensions Class for calculating rects of all elements that are drawn and will be drawn.
+ *
+ * @module Dimensions
+ **/
+
+export default class Dimensions {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ this.lgRect = {}
+ this.yAxisWidth = 0
+ this.yAxisWidthLeft = 0
+ this.yAxisWidthRight = 0
+ this.xAxisHeight = 0
+ this.isSparkline = this.w.config.chart.sparkline.enabled
+
+ this.dimHelpers = new Helpers(this)
+ this.dimYAxis = new DimYAxis(this)
+ this.dimXAxis = new DimXAxis(this)
+ this.dimGrid = new Grid(this)
+ this.lgWidthForSideLegends = 0
+ this.gridPad = this.w.config.grid.padding
+ this.xPadRight = 0
+ this.xPadLeft = 0
+ }
+
+ /**
+ * @memberof Dimensions
+ * @param {object} w - chart context
+ **/
+ plotCoords() {
+ let w = this.w
+ let gl = w.globals
+
+ this.lgRect = this.dimHelpers.getLegendsRect()
+ this.datalabelsCoords = { width: 0, height: 0 }
+
+ const maxStrokeWidth = Array.isArray(w.config.stroke.width)
+ ? Math.max(...w.config.stroke.width)
+ : w.config.stroke.width
+
+ if (this.isSparkline) {
+ if (w.config.markers.discrete.length > 0 || w.config.markers.size > 0) {
+ Object.entries(this.gridPad).forEach(([k, v]) => {
+ this.gridPad[k] = Math.max(
+ v,
+ this.w.globals.markers.largestSize / 1.5
+ )
+ })
+ }
+
+ this.gridPad.top = Math.max(maxStrokeWidth / 2, this.gridPad.top)
+ this.gridPad.bottom = Math.max(maxStrokeWidth / 2, this.gridPad.bottom)
+ }
+
+ if (gl.axisCharts) {
+ // for line / area / scatter / column
+ this.setDimensionsForAxisCharts()
+ } else {
+ // for pie / donuts / circle
+ this.setDimensionsForNonAxisCharts()
+ }
+
+ this.dimGrid.gridPadFortitleSubtitle()
+
+ // after calculating everything, apply padding set by user
+ gl.gridHeight = gl.gridHeight - this.gridPad.top - this.gridPad.bottom
+
+ gl.gridWidth =
+ gl.gridWidth -
+ this.gridPad.left -
+ this.gridPad.right -
+ this.xPadRight -
+ this.xPadLeft
+
+ let barWidth = this.dimGrid.gridPadForColumnsInNumericAxis(gl.gridWidth)
+
+ gl.gridWidth = gl.gridWidth - barWidth * 2
+
+ gl.translateX =
+ gl.translateX +
+ this.gridPad.left +
+ this.xPadLeft +
+ (barWidth > 0 ? barWidth : 0)
+ gl.translateY = gl.translateY + this.gridPad.top
+ }
+
+ setDimensionsForAxisCharts() {
+ let w = this.w
+ let gl = w.globals
+
+ let yaxisLabelCoords = this.dimYAxis.getyAxisLabelsCoords()
+ let yTitleCoords = this.dimYAxis.getyAxisTitleCoords()
+
+ if (gl.isSlopeChart) {
+ this.datalabelsCoords = this.dimHelpers.getDatalabelsRect()
+ }
+
+ w.globals.yLabelsCoords = []
+ w.globals.yTitleCoords = []
+ w.config.yaxis.map((yaxe, index) => {
+ // store the labels and titles coords in global vars
+ w.globals.yLabelsCoords.push({
+ width: yaxisLabelCoords[index].width,
+ index,
+ })
+ w.globals.yTitleCoords.push({
+ width: yTitleCoords[index].width,
+ index,
+ })
+ })
+
+ this.yAxisWidth = this.dimYAxis.getTotalYAxisWidth()
+
+ let xaxisLabelCoords = this.dimXAxis.getxAxisLabelsCoords()
+ let xaxisGroupLabelCoords = this.dimXAxis.getxAxisGroupLabelsCoords()
+ let xtitleCoords = this.dimXAxis.getxAxisTitleCoords()
+
+ this.conditionalChecksForAxisCoords(
+ xaxisLabelCoords,
+ xtitleCoords,
+ xaxisGroupLabelCoords
+ )
+
+ gl.translateXAxisY = w.globals.rotateXLabels ? this.xAxisHeight / 8 : -4
+ gl.translateXAxisX =
+ w.globals.rotateXLabels &&
+ w.globals.isXNumeric &&
+ w.config.xaxis.labels.rotate <= -45
+ ? -this.xAxisWidth / 4
+ : 0
+
+ if (w.globals.isBarHorizontal) {
+ gl.rotateXLabels = false
+ gl.translateXAxisY =
+ -1 * (parseInt(w.config.xaxis.labels.style.fontSize, 10) / 1.5)
+ }
+
+ gl.translateXAxisY = gl.translateXAxisY + w.config.xaxis.labels.offsetY
+ gl.translateXAxisX = gl.translateXAxisX + w.config.xaxis.labels.offsetX
+
+ let yAxisWidth = this.yAxisWidth
+ let xAxisHeight = this.xAxisHeight
+ gl.xAxisLabelsHeight = this.xAxisHeight - xtitleCoords.height
+ gl.xAxisGroupLabelsHeight = gl.xAxisLabelsHeight - xaxisLabelCoords.height
+ gl.xAxisLabelsWidth = this.xAxisWidth
+ gl.xAxisHeight = this.xAxisHeight
+ let translateY = 10
+
+ if (w.config.chart.type === 'radar' || this.isSparkline) {
+ yAxisWidth = 0
+ xAxisHeight = 0
+ }
+
+ if (this.isSparkline) {
+ this.lgRect = {
+ height: 0,
+ width: 0,
+ }
+ }
+
+ if (this.isSparkline || w.config.chart.type === 'treemap') {
+ yAxisWidth = 0
+ xAxisHeight = 0
+ translateY = 0
+ }
+
+ if (!this.isSparkline && w.config.chart.type !== 'treemap') {
+ this.dimXAxis.additionalPaddingXLabels(xaxisLabelCoords)
+ }
+
+ const legendTopBottom = () => {
+ gl.translateX = yAxisWidth + this.datalabelsCoords.width
+ gl.gridHeight =
+ gl.svgHeight -
+ this.lgRect.height -
+ xAxisHeight -
+ (!this.isSparkline && w.config.chart.type !== 'treemap'
+ ? w.globals.rotateXLabels
+ ? 10
+ : 15
+ : 0)
+ gl.gridWidth = gl.svgWidth - yAxisWidth - this.datalabelsCoords.width * 2
+ }
+
+ if (w.config.xaxis.position === 'top')
+ translateY = gl.xAxisHeight - w.config.xaxis.axisTicks.height - 5
+
+ switch (w.config.legend.position) {
+ case 'bottom':
+ gl.translateY = translateY
+ legendTopBottom()
+ break
+ case 'top':
+ gl.translateY = this.lgRect.height + translateY
+ legendTopBottom()
+ break
+ case 'left':
+ gl.translateY = translateY
+ gl.translateX =
+ this.lgRect.width + yAxisWidth + this.datalabelsCoords.width
+ gl.gridHeight = gl.svgHeight - xAxisHeight - 12
+ gl.gridWidth =
+ gl.svgWidth -
+ this.lgRect.width -
+ yAxisWidth -
+ this.datalabelsCoords.width * 2
+ break
+ case 'right':
+ gl.translateY = translateY
+ gl.translateX = yAxisWidth + this.datalabelsCoords.width
+ gl.gridHeight = gl.svgHeight - xAxisHeight - 12
+ gl.gridWidth =
+ gl.svgWidth -
+ this.lgRect.width -
+ yAxisWidth -
+ this.datalabelsCoords.width * 2 -
+ 5
+ break
+ default:
+ throw new Error('Legend position not supported')
+ }
+
+ this.dimGrid.setGridXPosForDualYAxis(yTitleCoords, yaxisLabelCoords)
+
+ // after drawing everything, set the Y axis positions
+ let objyAxis = new YAxis(this.ctx)
+ objyAxis.setYAxisXPosition(yaxisLabelCoords, yTitleCoords)
+ }
+
+ setDimensionsForNonAxisCharts() {
+ let w = this.w
+ let gl = w.globals
+ let cnf = w.config
+ let xPad = 0
+
+ if (w.config.legend.show && !w.config.legend.floating) {
+ xPad = 20
+ }
+
+ const type =
+ cnf.chart.type === 'pie' ||
+ cnf.chart.type === 'polarArea' ||
+ cnf.chart.type === 'donut'
+ ? 'pie'
+ : 'radialBar'
+
+ let offY = cnf.plotOptions[type].offsetY
+ let offX = cnf.plotOptions[type].offsetX
+
+ if (!cnf.legend.show || cnf.legend.floating) {
+ gl.gridHeight = gl.svgHeight
+
+ const maxWidth = gl.dom.elWrap.getBoundingClientRect().width
+ gl.gridWidth = Math.min(maxWidth, gl.gridHeight)
+
+ gl.translateY = offY
+ gl.translateX = offX + (gl.svgWidth - gl.gridWidth) / 2
+ return
+ }
+
+ switch (cnf.legend.position) {
+ case 'bottom':
+ gl.gridHeight = gl.svgHeight - this.lgRect.height
+ gl.gridWidth = gl.svgWidth
+ gl.translateY = offY - 10
+ gl.translateX = offX + (gl.svgWidth - gl.gridWidth) / 2
+ break
+ case 'top':
+ gl.gridHeight = gl.svgHeight - this.lgRect.height
+ gl.gridWidth = gl.svgWidth
+ gl.translateY = this.lgRect.height + offY + 10
+ gl.translateX = offX + (gl.svgWidth - gl.gridWidth) / 2
+ break
+ case 'left':
+ gl.gridWidth = gl.svgWidth - this.lgRect.width - xPad
+ gl.gridHeight =
+ cnf.chart.height !== 'auto' ? gl.svgHeight : gl.gridWidth
+ gl.translateY = offY
+ gl.translateX = offX + this.lgRect.width + xPad
+ break
+ case 'right':
+ gl.gridWidth = gl.svgWidth - this.lgRect.width - xPad - 5
+ gl.gridHeight =
+ cnf.chart.height !== 'auto' ? gl.svgHeight : gl.gridWidth
+ gl.translateY = offY
+ gl.translateX = offX + 10
+ break
+ default:
+ throw new Error('Legend position not supported')
+ }
+ }
+
+ conditionalChecksForAxisCoords(
+ xaxisLabelCoords,
+ xtitleCoords,
+ xaxisGroupLabelCoords
+ ) {
+ const w = this.w
+
+ const xAxisNum = w.globals.hasXaxisGroups ? 2 : 1
+
+ const baseXAxisHeight =
+ xaxisGroupLabelCoords.height +
+ xaxisLabelCoords.height +
+ xtitleCoords.height
+ const xAxisHeightMultiplicate = w.globals.isMultiLineX
+ ? 1.2
+ : w.globals.LINE_HEIGHT_RATIO
+ const rotatedXAxisOffset = w.globals.rotateXLabels ? 22 : 10
+ const rotatedXAxisLegendOffset =
+ w.globals.rotateXLabels && w.config.legend.position === 'bottom'
+ const additionalOffset = rotatedXAxisLegendOffset ? 10 : 0
+
+ this.xAxisHeight =
+ baseXAxisHeight * xAxisHeightMultiplicate +
+ xAxisNum * rotatedXAxisOffset +
+ additionalOffset
+
+ this.xAxisWidth = xaxisLabelCoords.width
+
+ if (
+ this.xAxisHeight - xtitleCoords.height >
+ w.config.xaxis.labels.maxHeight
+ ) {
+ this.xAxisHeight = w.config.xaxis.labels.maxHeight
+ }
+
+ if (
+ w.config.xaxis.labels.minHeight &&
+ this.xAxisHeight < w.config.xaxis.labels.minHeight
+ ) {
+ this.xAxisHeight = w.config.xaxis.labels.minHeight
+ }
+
+ if (w.config.xaxis.floating) {
+ this.xAxisHeight = 0
+ }
+
+ let minYAxisWidth = 0
+ let maxYAxisWidth = 0
+ w.config.yaxis.forEach((y) => {
+ minYAxisWidth += y.labels.minWidth
+ maxYAxisWidth += y.labels.maxWidth
+ })
+ if (this.yAxisWidth < minYAxisWidth) {
+ this.yAxisWidth = minYAxisWidth
+ }
+ if (this.yAxisWidth > maxYAxisWidth) {
+ this.yAxisWidth = maxYAxisWidth
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/Grid.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/Grid.js
new file mode 100644
index 0000000..9345059
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/Grid.js
@@ -0,0 +1,143 @@
+import AxesUtils from '../axes/AxesUtils'
+
+export default class DimGrid {
+ constructor(dCtx) {
+ this.w = dCtx.w
+ this.dCtx = dCtx
+ }
+
+ gridPadForColumnsInNumericAxis(gridWidth) {
+ const { w } = this
+ const { config: cnf, globals: gl } = w
+
+ if (
+ gl.noData ||
+ gl.collapsedSeries.length + gl.ancillaryCollapsedSeries.length ===
+ cnf.series.length
+ ) {
+ return 0
+ }
+
+ const hasBar = (type) =>
+ ['bar', 'rangeBar', 'candlestick', 'boxPlot'].includes(type)
+
+ const type = cnf.chart.type
+ let barWidth = 0
+ let seriesLen = hasBar(type) ? cnf.series.length : 1
+
+ if (gl.comboBarCount > 0) {
+ seriesLen = gl.comboBarCount
+ }
+
+ gl.collapsedSeries.forEach((c) => {
+ if (hasBar(c.type)) {
+ seriesLen -= 1
+ }
+ })
+
+ if (cnf.chart.stacked) {
+ seriesLen = 1
+ }
+
+ const barsPresent = hasBar(type) || gl.comboBarCount > 0
+ let xRange = Math.abs(gl.initialMaxX - gl.initialMinX)
+
+ if (
+ barsPresent &&
+ gl.isXNumeric &&
+ !gl.isBarHorizontal &&
+ seriesLen > 0 &&
+ xRange !== 0
+ ) {
+ if (xRange <= 3) {
+ xRange = gl.dataPoints
+ }
+
+ const xRatio = xRange / gridWidth
+ let xDivision =
+ gl.minXDiff && gl.minXDiff / xRatio > 0 ? gl.minXDiff / xRatio : 0
+
+ if (xDivision > gridWidth / 2) {
+ xDivision /= 2
+ }
+ // Here, barWidth is assumed to be the width occupied by a group of bars.
+ // There will be one bar in the group for each series plotted.
+ // Note: This version of the following math is different to that over in
+ // Helpers.js. Don't assume they should be the same. Over there,
+ // xDivision is computed differently and it's used on different charts.
+ // They were the same, but the solution to
+ // https://github.com/apexcharts/apexcharts.js/issues/4178
+ // was to remove the division by seriesLen.
+ barWidth =
+ (xDivision * parseInt(cnf.plotOptions.bar.columnWidth, 10)) / 100
+
+ if (barWidth < 1) {
+ barWidth = 1
+ }
+
+ gl.barPadForNumericAxis = barWidth
+ }
+
+ return barWidth
+ }
+
+ gridPadFortitleSubtitle() {
+ const { w } = this
+ const { globals: gl } = w
+ let gridShrinkOffset = this.dCtx.isSparkline || !gl.axisCharts ? 0 : 10
+
+ const titleSubtitle = ['title', 'subtitle']
+
+ titleSubtitle.forEach((t) => {
+ if (w.config[t].text !== undefined) {
+ gridShrinkOffset += w.config[t].margin
+ } else {
+ gridShrinkOffset += this.dCtx.isSparkline || !gl.axisCharts ? 0 : 5
+ }
+ })
+
+ if (
+ w.config.legend.show &&
+ w.config.legend.position === 'bottom' &&
+ !w.config.legend.floating &&
+ !gl.axisCharts
+ ) {
+ gridShrinkOffset += 10
+ }
+
+ const titleCoords = this.dCtx.dimHelpers.getTitleSubtitleCoords('title')
+ const subtitleCoords =
+ this.dCtx.dimHelpers.getTitleSubtitleCoords('subtitle')
+
+ gl.gridHeight -=
+ titleCoords.height + subtitleCoords.height + gridShrinkOffset
+ gl.translateY +=
+ titleCoords.height + subtitleCoords.height + gridShrinkOffset
+ }
+
+ setGridXPosForDualYAxis(yTitleCoords, yaxisLabelCoords) {
+ const { w } = this
+ const axesUtils = new AxesUtils(this.dCtx.ctx)
+
+ w.config.yaxis.forEach((yaxe, index) => {
+ if (
+ w.globals.ignoreYAxisIndexes.indexOf(index) === -1 &&
+ !yaxe.floating &&
+ !axesUtils.isYAxisHidden(index)
+ ) {
+ if (yaxe.opposite) {
+ w.globals.translateX -=
+ yaxisLabelCoords[index].width +
+ yTitleCoords[index].width +
+ parseInt(yaxe.labels.style.fontSize, 10) / 1.2 +
+ 12
+ }
+
+ // fixes apexcharts.js#1599
+ if (w.globals.translateX < 2) {
+ w.globals.translateX = 2
+ }
+ }
+ })
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/Helpers.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/Helpers.js
new file mode 100644
index 0000000..30656ca
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/Helpers.js
@@ -0,0 +1,144 @@
+import Utils from '../../utils/Utils'
+import Graphics from '../Graphics'
+
+export default class Helpers {
+ constructor(dCtx) {
+ this.w = dCtx.w
+ this.dCtx = dCtx
+ }
+
+ /**
+ * Get Chart Title/Subtitle Dimensions
+ * @memberof Dimensions
+ * @return {{width, height}}
+ **/
+ getTitleSubtitleCoords(type) {
+ let w = this.w
+ let width = 0
+ let height = 0
+
+ const floating =
+ type === 'title' ? w.config.title.floating : w.config.subtitle.floating
+
+ let el = w.globals.dom.baseEl.querySelector(`.apexcharts-${type}-text`)
+
+ if (el !== null && !floating) {
+ let coord = el.getBoundingClientRect()
+ width = coord.width
+ height = w.globals.axisCharts ? coord.height + 5 : coord.height
+ }
+
+ return {
+ width,
+ height,
+ }
+ }
+
+ getLegendsRect() {
+ let w = this.w
+
+ let elLegendWrap = w.globals.dom.elLegendWrap
+
+ if (
+ !w.config.legend.height &&
+ (w.config.legend.position === 'top' ||
+ w.config.legend.position === 'bottom')
+ ) {
+ // avoid legend to take up all the space
+ elLegendWrap.style.maxHeight = w.globals.svgHeight / 2 + 'px'
+ }
+
+ let lgRect = Object.assign({}, Utils.getBoundingClientRect(elLegendWrap))
+
+ if (
+ elLegendWrap !== null &&
+ !w.config.legend.floating &&
+ w.config.legend.show
+ ) {
+ this.dCtx.lgRect = {
+ x: lgRect.x,
+ y: lgRect.y,
+ height: lgRect.height,
+ width: lgRect.height === 0 ? 0 : lgRect.width,
+ }
+ } else {
+ this.dCtx.lgRect = {
+ x: 0,
+ y: 0,
+ height: 0,
+ width: 0,
+ }
+ }
+
+ // if legend takes up all of the chart space, we need to restrict it.
+ if (
+ w.config.legend.position === 'left' ||
+ w.config.legend.position === 'right'
+ ) {
+ if (this.dCtx.lgRect.width * 1.5 > w.globals.svgWidth) {
+ this.dCtx.lgRect.width = w.globals.svgWidth / 1.5
+ }
+ }
+
+ return this.dCtx.lgRect
+ }
+
+ /**
+ * Get Y Axis Dimensions
+ * @memberof Dimensions
+ * @return {{width, height}}
+ **/
+ getDatalabelsRect() {
+ let w = this.w
+
+ let allLabels = []
+
+ w.config.series.forEach((serie, seriesIndex) => {
+ serie.data.forEach((datum, dataPointIndex) => {
+ const getText = (v) => {
+ return w.config.dataLabels.formatter(v, {
+ ctx: this.dCtx.ctx,
+ seriesIndex,
+ dataPointIndex,
+ w,
+ })
+ }
+
+ val = getText(w.globals.series[seriesIndex][dataPointIndex])
+
+ allLabels.push(val)
+ })
+ })
+
+ let val = Utils.getLargestStringFromArr(allLabels)
+
+ let graphics = new Graphics(this.dCtx.ctx)
+ const dataLabelsStyle = w.config.dataLabels.style
+ let labelrect = graphics.getTextRects(
+ val,
+ parseInt(dataLabelsStyle.fontSize),
+ dataLabelsStyle.fontFamily
+ )
+
+ return {
+ width: labelrect.width * 1.05,
+ height: labelrect.height,
+ }
+ }
+
+ getLargestStringFromMultiArr(val, arr) {
+ const w = this.w
+ let valArr = val
+ if (w.globals.isMultiLineX) {
+ // if the xaxis labels has multiline texts (array)
+ let maxArrs = arr.map((xl, idx) => {
+ return Array.isArray(xl) ? xl.length : 1
+ })
+ let maxArrLen = Math.max(...maxArrs)
+ let maxArrIndex = maxArrs.indexOf(maxArrLen)
+ valArr = arr[maxArrIndex]
+ }
+
+ return valArr
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/XAxis.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/XAxis.js
new file mode 100644
index 0000000..f9a8c89
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/XAxis.js
@@ -0,0 +1,370 @@
+import Formatters from '../Formatters'
+import Graphics from '../Graphics'
+import Utils from '../../utils/Utils'
+import DateTime from '../../utils/DateTime'
+
+export default class DimXAxis {
+ constructor(dCtx) {
+ this.w = dCtx.w
+ this.dCtx = dCtx
+ }
+
+ /**
+ * Get X Axis Dimensions
+ * @memberof Dimensions
+ * @return {{width, height}}
+ **/
+ getxAxisLabelsCoords() {
+ let w = this.w
+
+ let xaxisLabels = w.globals.labels.slice()
+ if (w.config.xaxis.convertedCatToNumeric && xaxisLabels.length === 0) {
+ xaxisLabels = w.globals.categoryLabels
+ }
+
+ let rect
+
+ if (w.globals.timescaleLabels.length > 0) {
+ const coords = this.getxAxisTimeScaleLabelsCoords()
+ rect = {
+ width: coords.width,
+ height: coords.height,
+ }
+ w.globals.rotateXLabels = false
+ } else {
+ this.dCtx.lgWidthForSideLegends =
+ (w.config.legend.position === 'left' ||
+ w.config.legend.position === 'right') &&
+ !w.config.legend.floating
+ ? this.dCtx.lgRect.width
+ : 0
+
+ // get the longest string from the labels array and also apply label formatter
+ let xlbFormatter = w.globals.xLabelFormatter
+ // prevent changing xaxisLabels to avoid issues in multi-yaxes - fix #522
+ let val = Utils.getLargestStringFromArr(xaxisLabels)
+ let valArr = this.dCtx.dimHelpers.getLargestStringFromMultiArr(
+ val,
+ xaxisLabels
+ )
+
+ // the labels gets changed for bar charts
+ if (w.globals.isBarHorizontal) {
+ val = w.globals.yAxisScale[0].result.reduce(
+ (a, b) => (a.length > b.length ? a : b),
+ 0
+ )
+ valArr = val
+ }
+
+ let xFormat = new Formatters(this.dCtx.ctx)
+ let timestamp = val
+ val = xFormat.xLabelFormat(xlbFormatter, val, timestamp, {
+ i: undefined,
+ dateFormatter: new DateTime(this.dCtx.ctx).formatDate,
+ w,
+ })
+ valArr = xFormat.xLabelFormat(xlbFormatter, valArr, timestamp, {
+ i: undefined,
+ dateFormatter: new DateTime(this.dCtx.ctx).formatDate,
+ w,
+ })
+
+ if (
+ (w.config.xaxis.convertedCatToNumeric && typeof val === 'undefined') ||
+ String(val).trim() === ''
+ ) {
+ val = '1'
+ valArr = val
+ }
+
+ let graphics = new Graphics(this.dCtx.ctx)
+ let xLabelrect = graphics.getTextRects(
+ val,
+ w.config.xaxis.labels.style.fontSize
+ )
+ let xArrLabelrect = xLabelrect
+ if (val !== valArr) {
+ xArrLabelrect = graphics.getTextRects(
+ valArr,
+ w.config.xaxis.labels.style.fontSize
+ )
+ }
+
+ rect = {
+ width:
+ xLabelrect.width >= xArrLabelrect.width
+ ? xLabelrect.width
+ : xArrLabelrect.width,
+ height:
+ xLabelrect.height >= xArrLabelrect.height
+ ? xLabelrect.height
+ : xArrLabelrect.height,
+ }
+
+ if (
+ (rect.width * xaxisLabels.length >
+ w.globals.svgWidth -
+ this.dCtx.lgWidthForSideLegends -
+ this.dCtx.yAxisWidth -
+ this.dCtx.gridPad.left -
+ this.dCtx.gridPad.right &&
+ w.config.xaxis.labels.rotate !== 0) ||
+ w.config.xaxis.labels.rotateAlways
+ ) {
+ if (!w.globals.isBarHorizontal) {
+ w.globals.rotateXLabels = true
+ const getRotatedTextRects = (text) => {
+ return graphics.getTextRects(
+ text,
+ w.config.xaxis.labels.style.fontSize,
+ w.config.xaxis.labels.style.fontFamily,
+ `rotate(${w.config.xaxis.labels.rotate} 0 0)`,
+ false
+ )
+ }
+ xLabelrect = getRotatedTextRects(val)
+ if (val !== valArr) {
+ xArrLabelrect = getRotatedTextRects(valArr)
+ }
+
+ rect.height =
+ (xLabelrect.height > xArrLabelrect.height
+ ? xLabelrect.height
+ : xArrLabelrect.height) / 1.5
+ rect.width =
+ xLabelrect.width > xArrLabelrect.width
+ ? xLabelrect.width
+ : xArrLabelrect.width
+ }
+ } else {
+ w.globals.rotateXLabels = false
+ }
+ }
+
+ if (!w.config.xaxis.labels.show) {
+ rect = {
+ width: 0,
+ height: 0,
+ }
+ }
+
+ return {
+ width: rect.width,
+ height: rect.height,
+ }
+ }
+
+ /**
+ * Get X Axis Label Group height
+ * @memberof Dimensions
+ * @return {{width, height}}
+ */
+ getxAxisGroupLabelsCoords() {
+ let w = this.w
+
+ if (!w.globals.hasXaxisGroups) {
+ return { width: 0, height: 0 }
+ }
+
+ const fontSize =
+ w.config.xaxis.group.style?.fontSize ||
+ w.config.xaxis.labels.style.fontSize
+
+ let xaxisLabels = w.globals.groups.map((g) => g.title)
+
+ let rect
+
+ // prevent changing xaxisLabels to avoid issues in multi-yaxes - fix #522
+ let val = Utils.getLargestStringFromArr(xaxisLabels)
+ let valArr = this.dCtx.dimHelpers.getLargestStringFromMultiArr(
+ val,
+ xaxisLabels
+ )
+
+ let graphics = new Graphics(this.dCtx.ctx)
+ let xLabelrect = graphics.getTextRects(val, fontSize)
+ let xArrLabelrect = xLabelrect
+ if (val !== valArr) {
+ xArrLabelrect = graphics.getTextRects(valArr, fontSize)
+ }
+
+ rect = {
+ width:
+ xLabelrect.width >= xArrLabelrect.width
+ ? xLabelrect.width
+ : xArrLabelrect.width,
+ height:
+ xLabelrect.height >= xArrLabelrect.height
+ ? xLabelrect.height
+ : xArrLabelrect.height,
+ }
+
+ if (!w.config.xaxis.labels.show) {
+ rect = {
+ width: 0,
+ height: 0,
+ }
+ }
+
+ return {
+ width: rect.width,
+ height: rect.height,
+ }
+ }
+
+ /**
+ * Get X Axis Title Dimensions
+ * @memberof Dimensions
+ * @return {{width, height}}
+ **/
+ getxAxisTitleCoords() {
+ let w = this.w
+ let width = 0
+ let height = 0
+
+ if (w.config.xaxis.title.text !== undefined) {
+ let graphics = new Graphics(this.dCtx.ctx)
+
+ let rect = graphics.getTextRects(
+ w.config.xaxis.title.text,
+ w.config.xaxis.title.style.fontSize
+ )
+
+ width = rect.width
+ height = rect.height
+ }
+
+ return {
+ width,
+ height,
+ }
+ }
+
+ getxAxisTimeScaleLabelsCoords() {
+ let w = this.w
+ let rect
+
+ this.dCtx.timescaleLabels = w.globals.timescaleLabels.slice()
+
+ let labels = this.dCtx.timescaleLabels.map((label) => label.value)
+
+ // get the longest string from the labels array and also apply label formatter to it
+ let val = labels.reduce((a, b) => {
+ // if undefined, maybe user didn't pass the datetime(x) values
+ if (typeof a === 'undefined') {
+ console.error(
+ 'You have possibly supplied invalid Date format. Please supply a valid JavaScript Date'
+ )
+ return 0
+ } else {
+ return a.length > b.length ? a : b
+ }
+ }, 0)
+
+ let graphics = new Graphics(this.dCtx.ctx)
+ rect = graphics.getTextRects(val, w.config.xaxis.labels.style.fontSize)
+
+ let totalWidthRotated = rect.width * 1.05 * labels.length
+
+ if (
+ totalWidthRotated > w.globals.gridWidth &&
+ w.config.xaxis.labels.rotate !== 0
+ ) {
+ w.globals.overlappingXLabels = true
+ }
+
+ return rect
+ }
+
+ // In certain cases, the last labels gets cropped in xaxis.
+ // Hence, we add some additional padding based on the label length to avoid the last label being cropped or we don't draw it at all
+ additionalPaddingXLabels(xaxisLabelCoords) {
+ const w = this.w
+ const gl = w.globals
+ const cnf = w.config
+ const xtype = cnf.xaxis.type
+
+ let lbWidth = xaxisLabelCoords.width
+
+ gl.skipLastTimelinelabel = false
+ gl.skipFirstTimelinelabel = false
+ const isBarOpposite =
+ w.config.yaxis[0].opposite && w.globals.isBarHorizontal
+
+ const isCollapsed = (i) => gl.collapsedSeriesIndices.indexOf(i) !== -1
+
+ const rightPad = (yaxe) => {
+ if (this.dCtx.timescaleLabels && this.dCtx.timescaleLabels.length) {
+ // for timeline labels, we take the last label and check if it exceeds gridWidth
+ const firstimescaleLabel = this.dCtx.timescaleLabels[0]
+ const lastTimescaleLabel =
+ this.dCtx.timescaleLabels[this.dCtx.timescaleLabels.length - 1]
+
+ const lastLabelPosition =
+ lastTimescaleLabel.position +
+ lbWidth / 1.75 -
+ this.dCtx.yAxisWidthRight
+
+ const firstLabelPosition =
+ firstimescaleLabel.position -
+ lbWidth / 1.75 +
+ this.dCtx.yAxisWidthLeft
+
+ let lgRightRectWidth =
+ w.config.legend.position === 'right' && this.dCtx.lgRect.width > 0
+ ? this.dCtx.lgRect.width
+ : 0
+ if (
+ lastLabelPosition >
+ gl.svgWidth - gl.translateX - lgRightRectWidth
+ ) {
+ gl.skipLastTimelinelabel = true
+ }
+
+ if (
+ firstLabelPosition <
+ -((!yaxe.show || yaxe.floating) &&
+ (cnf.chart.type === 'bar' ||
+ cnf.chart.type === 'candlestick' ||
+ cnf.chart.type === 'rangeBar' ||
+ cnf.chart.type === 'boxPlot')
+ ? lbWidth / 1.75
+ : 10)
+ ) {
+ gl.skipFirstTimelinelabel = true
+ }
+ } else if (xtype === 'datetime') {
+ // If user has enabled DateTime, but uses own's formatter
+ if (this.dCtx.gridPad.right < lbWidth && !gl.rotateXLabels) {
+ gl.skipLastTimelinelabel = true
+ }
+ } else if (xtype !== 'datetime') {
+ if (
+ this.dCtx.gridPad.right < lbWidth / 2 - this.dCtx.yAxisWidthRight &&
+ !gl.rotateXLabels &&
+ !w.config.xaxis.labels.trim
+ ) {
+ this.dCtx.xPadRight = lbWidth / 2 + 1
+ }
+ }
+ }
+
+ const padYAxe = (yaxe, i) => {
+ if (cnf.yaxis.length > 1 && isCollapsed(i)) return
+
+ rightPad(yaxe)
+ }
+
+ cnf.yaxis.forEach((yaxe, i) => {
+ if (isBarOpposite) {
+ if (this.dCtx.gridPad.left < lbWidth) {
+ this.dCtx.xPadLeft = lbWidth / 2 + 1
+ }
+ this.dCtx.xPadRight = lbWidth / 2 + 1
+ } else {
+ padYAxe(yaxe, i)
+ }
+ })
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/YAxis.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/YAxis.js
new file mode 100644
index 0000000..a09329f
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/dimensions/YAxis.js
@@ -0,0 +1,211 @@
+import Graphics from '../Graphics'
+import Utils from '../../utils/Utils'
+import AxesUtils from '../axes/AxesUtils'
+
+export default class DimYAxis {
+ constructor(dCtx) {
+ this.w = dCtx.w
+ this.dCtx = dCtx
+ }
+
+ /**
+ * Get Y Axis Dimensions
+ * @memberof Dimensions
+ * @return {{width, height}}
+ **/
+ getyAxisLabelsCoords() {
+ let w = this.w
+
+ let width = 0
+ let height = 0
+ let ret = []
+ let labelPad = 10
+ const axesUtils = new AxesUtils(this.dCtx.ctx)
+
+ w.config.yaxis.map((yaxe, index) => {
+ const formatterArgs = {
+ seriesIndex: index,
+ dataPointIndex: -1,
+ w,
+ }
+ const yS = w.globals.yAxisScale[index]
+ let yAxisMinWidth = 0
+ if (
+ !axesUtils.isYAxisHidden(index) &&
+ yaxe.labels.show &&
+ yaxe.labels.minWidth !== undefined
+ )
+ yAxisMinWidth = yaxe.labels.minWidth
+
+ if (
+ !axesUtils.isYAxisHidden(index) &&
+ yaxe.labels.show &&
+ yS.result.length
+ ) {
+ let lbFormatter = w.globals.yLabelFormatters[index]
+ let minV = yS.niceMin === Number.MIN_VALUE ? 0 : yS.niceMin
+ let val = yS.result.reduce((acc, curr) => {
+ return String(lbFormatter(acc, formatterArgs))?.length >
+ String(lbFormatter(curr, formatterArgs))?.length
+ ? acc
+ : curr
+ }, minV)
+
+ val = lbFormatter(val, formatterArgs)
+
+ // the second parameter -1 is the index of tick which user can use in the formatter
+ let valArr = val
+
+ // if user has specified a custom formatter, and the result is null or empty, we need to discard the formatter and take the value as it is.
+ if (typeof val === 'undefined' || val.length === 0) {
+ val = yS.niceMax
+ }
+
+ if (w.globals.isBarHorizontal) {
+ labelPad = 0
+
+ let barYaxisLabels = w.globals.labels.slice()
+
+ // get the longest string from the labels array and also apply label formatter to it
+ val = Utils.getLargestStringFromArr(barYaxisLabels)
+
+ val = lbFormatter(val, { seriesIndex: index, dataPointIndex: -1, w })
+ valArr = this.dCtx.dimHelpers.getLargestStringFromMultiArr(
+ val,
+ barYaxisLabels
+ )
+ }
+
+ let graphics = new Graphics(this.dCtx.ctx)
+
+ let rotateStr = 'rotate('.concat(yaxe.labels.rotate, ' 0 0)')
+ let rect = graphics.getTextRects(
+ val,
+ yaxe.labels.style.fontSize,
+ yaxe.labels.style.fontFamily,
+ rotateStr,
+ false
+ )
+
+ let arrLabelrect = rect
+
+ if (val !== valArr) {
+ arrLabelrect = graphics.getTextRects(
+ valArr,
+ yaxe.labels.style.fontSize,
+ yaxe.labels.style.fontFamily,
+ rotateStr,
+ false
+ )
+ }
+
+ ret.push({
+ width:
+ (yAxisMinWidth > arrLabelrect.width || yAxisMinWidth > rect.width
+ ? yAxisMinWidth
+ : arrLabelrect.width > rect.width
+ ? arrLabelrect.width
+ : rect.width) + labelPad,
+ height:
+ arrLabelrect.height > rect.height
+ ? arrLabelrect.height
+ : rect.height,
+ })
+ } else {
+ ret.push({
+ width,
+ height,
+ })
+ }
+ })
+
+ return ret
+ }
+
+ /**
+ * Get Y Axis Dimensions
+ * @memberof Dimensions
+ * @return {{width, height}}
+ **/
+ getyAxisTitleCoords() {
+ let w = this.w
+ let ret = []
+
+ w.config.yaxis.map((yaxe, index) => {
+ if (yaxe.show && yaxe.title.text !== undefined) {
+ let graphics = new Graphics(this.dCtx.ctx)
+ let rotateStr = 'rotate('.concat(yaxe.title.rotate, ' 0 0)')
+ let rect = graphics.getTextRects(
+ yaxe.title.text,
+ yaxe.title.style.fontSize,
+ yaxe.title.style.fontFamily,
+ rotateStr,
+ false
+ )
+
+ ret.push({
+ width: rect.width,
+ height: rect.height,
+ })
+ } else {
+ ret.push({
+ width: 0,
+ height: 0,
+ })
+ }
+ })
+
+ return ret
+ }
+
+ getTotalYAxisWidth() {
+ let w = this.w
+ let yAxisWidth = 0
+ let yAxisWidthLeft = 0
+ let yAxisWidthRight = 0
+ let padding = w.globals.yAxisScale.length > 1 ? 10 : 0
+ const axesUtils = new AxesUtils(this.dCtx.ctx)
+
+ const isHiddenYAxis = function (index) {
+ return w.globals.ignoreYAxisIndexes.indexOf(index) > -1
+ }
+
+ const padForLabelTitle = (coord, index) => {
+ let floating = w.config.yaxis[index].floating
+ let width = 0
+
+ if (coord.width > 0 && !floating) {
+ width = coord.width + padding
+ if (isHiddenYAxis(index)) {
+ width = width - coord.width - padding
+ }
+ } else {
+ width = floating || axesUtils.isYAxisHidden(index) ? 0 : 5
+ }
+
+ w.config.yaxis[index].opposite
+ ? (yAxisWidthRight = yAxisWidthRight + width)
+ : (yAxisWidthLeft = yAxisWidthLeft + width)
+
+ yAxisWidth = yAxisWidth + width
+ }
+
+ w.globals.yLabelsCoords.map((yLabelCoord, index) => {
+ padForLabelTitle(yLabelCoord, index)
+ })
+
+ w.globals.yTitleCoords.map((yTitleCoord, index) => {
+ padForLabelTitle(yTitleCoord, index)
+ })
+
+ if (w.globals.isBarHorizontal && !w.config.yaxis[0].floating) {
+ yAxisWidth =
+ w.globals.yLabelsCoords[0].width + w.globals.yTitleCoords[0].width + 15
+ }
+
+ this.dCtx.yAxisWidthLeft = yAxisWidthLeft
+ this.dCtx.yAxisWidthRight = yAxisWidthRight
+
+ return yAxisWidth
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/Destroy.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/Destroy.js
new file mode 100644
index 0000000..29491ef
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/Destroy.js
@@ -0,0 +1,89 @@
+export default class Destroy {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ clear({ isUpdating }) {
+ if (this.ctx.zoomPanSelection) {
+ this.ctx.zoomPanSelection.destroy()
+ }
+ if (this.ctx.toolbar) {
+ this.ctx.toolbar.destroy()
+ }
+
+ this.ctx.animations = null
+ this.ctx.axes = null
+ this.ctx.annotations = null
+ this.ctx.core = null
+ this.ctx.data = null
+ this.ctx.grid = null
+ this.ctx.series = null
+ this.ctx.responsive = null
+ this.ctx.theme = null
+ this.ctx.formatters = null
+ this.ctx.titleSubtitle = null
+ this.ctx.legend = null
+ this.ctx.dimensions = null
+ this.ctx.options = null
+ this.ctx.crosshairs = null
+ this.ctx.zoomPanSelection = null
+ this.ctx.updateHelpers = null
+ this.ctx.toolbar = null
+ this.ctx.localization = null
+ this.ctx.w.globals.tooltip = null
+ this.clearDomElements({ isUpdating })
+ }
+
+ killSVG(draw) {
+ draw.each(function () {
+ this.removeClass('*')
+ this.off()
+ this.stop()
+ }, true)
+ draw.ungroup()
+ draw.clear()
+ }
+
+ clearDomElements({ isUpdating }) {
+ const elSVG = this.w.globals.dom.Paper.node
+ // fixes apexcharts.js#1654 & vue-apexcharts#256
+ if (elSVG.parentNode && elSVG.parentNode.parentNode && !isUpdating) {
+ elSVG.parentNode.parentNode.style.minHeight = 'unset'
+ }
+
+ // detach root event
+ const baseEl = this.w.globals.dom.baseEl
+ if (baseEl) {
+ // see https://github.com/apexcharts/vue-apexcharts/issues/275
+ this.ctx.eventList.forEach((event) => {
+ baseEl.removeEventListener(event, this.ctx.events.documentEvent)
+ })
+ }
+
+ const domEls = this.w.globals.dom
+
+ if (this.ctx.el !== null) {
+ // remove all child elements - resetting the whole chart
+ while (this.ctx.el.firstChild) {
+ this.ctx.el.removeChild(this.ctx.el.firstChild)
+ }
+ }
+
+ this.killSVG(domEls.Paper)
+ domEls.Paper.remove()
+
+ domEls.elWrap = null
+ domEls.elGraphical = null
+ domEls.elLegendWrap = null
+ domEls.elLegendForeign = null
+ domEls.baseEl = null
+ domEls.elGridRect = null
+ domEls.elGridRectMask = null
+ domEls.elGridRectBarMask = null
+ domEls.elGridRectMarkerMask = null
+ domEls.elForecastMask = null
+ domEls.elNonForecastMask = null
+ domEls.elDefs = null
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/InitCtxVariables.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/InitCtxVariables.js
new file mode 100644
index 0000000..3e7f817
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/InitCtxVariables.js
@@ -0,0 +1,110 @@
+import Events from '../Events'
+import Localization from './Localization'
+import Animations from '../Animations'
+import Axes from '../axes/Axes'
+import Config from '../settings/Config'
+import CoreUtils from '../CoreUtils'
+import Crosshairs from '../Crosshairs'
+import Grid from '../axes/Grid'
+import Graphics from '../Graphics'
+import Exports from '../Exports'
+import Fill from '../Fill.js'
+import Options from '../settings/Options'
+import Responsive from '../Responsive'
+import Series from '../Series'
+import Theme from '../Theme'
+import Formatters from '../Formatters'
+import TitleSubtitle from '../TitleSubtitle'
+import Legend from '../legend/Legend'
+import Toolbar from '../Toolbar'
+import Dimensions from '../dimensions/Dimensions'
+import ZoomPanSelection from '../ZoomPanSelection'
+import Tooltip from '../tooltip/Tooltip'
+import Core from '../Core'
+import Data from '../Data'
+import UpdateHelpers from './UpdateHelpers'
+
+import '../../svgjs/svg.js'
+import 'svg.filter.js'
+import 'svg.pathmorphing.js'
+import 'svg.draggable.js'
+import 'svg.select.js'
+import 'svg.resize.js'
+
+// global Apex object which user can use to override chart's defaults globally
+if (typeof window.Apex === 'undefined') {
+ window.Apex = {}
+}
+
+export default class InitCtxVariables {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ initModules() {
+ this.ctx.publicMethods = [
+ 'updateOptions',
+ 'updateSeries',
+ 'appendData',
+ 'appendSeries',
+ 'isSeriesHidden',
+ 'highlightSeries',
+ 'toggleSeries',
+ 'showSeries',
+ 'hideSeries',
+ 'setLocale',
+ 'resetSeries',
+ 'zoomX',
+ 'toggleDataPointSelection',
+ 'dataURI',
+ 'exportToCSV',
+ 'addXaxisAnnotation',
+ 'addYaxisAnnotation',
+ 'addPointAnnotation',
+ 'clearAnnotations',
+ 'removeAnnotation',
+ 'paper',
+ 'destroy',
+ ]
+
+ this.ctx.eventList = [
+ 'click',
+ 'mousedown',
+ 'mousemove',
+ 'mouseleave',
+ 'touchstart',
+ 'touchmove',
+ 'touchleave',
+ 'mouseup',
+ 'touchend',
+ ]
+
+ this.ctx.animations = new Animations(this.ctx)
+ this.ctx.axes = new Axes(this.ctx)
+ this.ctx.core = new Core(this.ctx.el, this.ctx)
+ this.ctx.config = new Config({})
+ this.ctx.data = new Data(this.ctx)
+ this.ctx.grid = new Grid(this.ctx)
+ this.ctx.graphics = new Graphics(this.ctx)
+ this.ctx.coreUtils = new CoreUtils(this.ctx)
+ this.ctx.crosshairs = new Crosshairs(this.ctx)
+ this.ctx.events = new Events(this.ctx)
+ this.ctx.exports = new Exports(this.ctx)
+ this.ctx.fill = new Fill(this.ctx)
+ this.ctx.localization = new Localization(this.ctx)
+ this.ctx.options = new Options()
+ this.ctx.responsive = new Responsive(this.ctx)
+ this.ctx.series = new Series(this.ctx)
+ this.ctx.theme = new Theme(this.ctx)
+ this.ctx.formatters = new Formatters(this.ctx)
+ this.ctx.titleSubtitle = new TitleSubtitle(this.ctx)
+ this.ctx.legend = new Legend(this.ctx)
+ this.ctx.toolbar = new Toolbar(this.ctx)
+ this.ctx.tooltip = new Tooltip(this.ctx)
+ this.ctx.dimensions = new Dimensions(this.ctx)
+ this.ctx.updateHelpers = new UpdateHelpers(this.ctx)
+ this.ctx.zoomPanSelection = new ZoomPanSelection(this.ctx)
+ this.ctx.w.globals.tooltip = new Tooltip(this.ctx)
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/Localization.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/Localization.js
new file mode 100644
index 0000000..c06e862
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/Localization.js
@@ -0,0 +1,39 @@
+import Utils from '../../utils/Utils'
+
+import en from '../../locales/en.json'
+
+export default class Localization {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ setCurrentLocaleValues(localeName) {
+ let locales = this.w.config.chart.locales
+
+ // check if user has specified locales in global Apex variable
+ // if yes - then extend those with local chart's locale
+ if (
+ window.Apex.chart &&
+ window.Apex.chart.locales &&
+ window.Apex.chart.locales.length > 0
+ ) {
+ locales = this.w.config.chart.locales.concat(window.Apex.chart.locales)
+ }
+
+ // find the locale from the array of locales which user has set (either by chart.defaultLocale or by calling setLocale() method.)
+ const selectedLocale = locales.filter((c) => c.name === localeName)[0]
+
+ if (selectedLocale) {
+ // create a complete locale object by extending defaults so you don't get undefined errors.
+ let ret = Utils.extend(en, selectedLocale)
+
+ // store these locale options in global var for ease access
+ this.w.globals.locale = ret.options
+ } else {
+ throw new Error(
+ 'Wrong locale name provided. Please make sure you set the correct locale name in options'
+ )
+ }
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/UpdateHelpers.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/UpdateHelpers.js
new file mode 100644
index 0000000..e58b891
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/helpers/UpdateHelpers.js
@@ -0,0 +1,304 @@
+import Defaults from '../settings/Defaults'
+import Config from '../settings/Config'
+import CoreUtils from '../CoreUtils'
+import Graphics from '../Graphics'
+import Utils from '../../utils/Utils'
+
+export default class UpdateHelpers {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+ }
+
+ /**
+ * private method to update Options.
+ *
+ * @param {object} options - A new config object can be passed which will be merged with the existing config object
+ * @param {boolean} redraw - should redraw from beginning or should use existing paths and redraw from there
+ * @param {boolean} animate - should animate or not on updating Options
+ * @param {boolean} overwriteInitialConfig - should update the initial config or not
+ */
+ _updateOptions(
+ options,
+ redraw = false,
+ animate = true,
+ updateSyncedCharts = true,
+ overwriteInitialConfig = false
+ ) {
+ return new Promise((resolve) => {
+ let charts = [this.ctx]
+ if (updateSyncedCharts) {
+ charts = this.ctx.getSyncedCharts()
+ }
+
+ if (this.ctx.w.globals.isExecCalled) {
+ // If the user called exec method, we don't want to get grouped charts as user specifically provided a chartID to update
+ charts = [this.ctx]
+ this.ctx.w.globals.isExecCalled = false
+ }
+
+ charts.forEach((ch, chartIndex) => {
+ let w = ch.w
+
+ w.globals.shouldAnimate = animate
+
+ if (!redraw) {
+ w.globals.resized = true
+ w.globals.dataChanged = true
+
+ if (animate) {
+ ch.series.getPreviousPaths()
+ }
+ }
+
+ if (options && typeof options === 'object') {
+ ch.config = new Config(options)
+ options = CoreUtils.extendArrayProps(ch.config, options, w)
+
+ // fixes #914, #623
+ if (ch.w.globals.chartID !== this.ctx.w.globals.chartID) {
+ // don't overwrite series of synchronized charts
+ delete options.series
+ }
+
+ w.config = Utils.extend(w.config, options)
+
+ if (overwriteInitialConfig) {
+ // we need to forget the lastXAxis and lastYAxis as user forcefully overwriteInitialConfig. If we do not do this, and next time when user zooms the chart after setting yaxis.min/max or xaxis.min/max - the stored lastXAxis will never allow the chart to use the updated min/max by user.
+ w.globals.lastXAxis = options.xaxis
+ ? Utils.clone(options.xaxis)
+ : []
+ w.globals.lastYAxis = options.yaxis
+ ? Utils.clone(options.yaxis)
+ : []
+
+ // After forgetting lastAxes, we need to restore the new config in initialConfig/initialSeries
+ w.globals.initialConfig = Utils.extend({}, w.config)
+ w.globals.initialSeries = Utils.clone(w.config.series)
+
+ if (options.series) {
+ // Replace the collapsed series data
+ for (
+ let i = 0;
+ i < w.globals.collapsedSeriesIndices.length;
+ i++
+ ) {
+ let series =
+ w.config.series[w.globals.collapsedSeriesIndices[i]]
+ w.globals.collapsedSeries[i].data = w.globals.axisCharts
+ ? series.data.slice()
+ : series
+ }
+ for (
+ let i = 0;
+ i < w.globals.ancillaryCollapsedSeriesIndices.length;
+ i++
+ ) {
+ let series =
+ w.config.series[w.globals.ancillaryCollapsedSeriesIndices[i]]
+ w.globals.ancillaryCollapsedSeries[i].data = w.globals
+ .axisCharts
+ ? series.data.slice()
+ : series
+ }
+
+ // Ensure that auto-generated axes are scaled to the visible data
+ ch.series.emptyCollapsedSeries(w.config.series)
+ }
+ }
+ }
+
+ return ch.update(options).then(() => {
+ if (chartIndex === charts.length - 1) {
+ resolve(ch)
+ }
+ })
+ })
+ })
+ }
+
+ /**
+ * Private method to update Series.
+ *
+ * @param {array} series - New series which will override the existing
+ */
+ _updateSeries(newSeries, animate, overwriteInitialSeries = false) {
+ return new Promise((resolve) => {
+ const w = this.w
+
+ w.globals.shouldAnimate = animate
+
+ w.globals.dataChanged = true
+
+ if (animate) {
+ this.ctx.series.getPreviousPaths()
+ }
+
+ let existingSeries
+
+ // axis charts
+ if (w.globals.axisCharts) {
+ existingSeries = newSeries.map((s, i) => {
+ return this._extendSeries(s, i)
+ })
+
+ if (existingSeries.length === 0) {
+ existingSeries = [{ data: [] }]
+ }
+ w.config.series = existingSeries
+ } else {
+ // non-axis chart (pie/radialbar)
+ w.config.series = newSeries.slice()
+ }
+
+ if (overwriteInitialSeries) {
+ w.globals.initialConfig.series = Utils.clone(w.config.series)
+ w.globals.initialSeries = Utils.clone(w.config.series)
+ }
+ return this.ctx.update().then(() => {
+ resolve(this.ctx)
+ })
+ })
+ }
+
+ _extendSeries(s, i) {
+ const w = this.w
+ const ser = w.config.series[i]
+
+ return {
+ ...w.config.series[i],
+ name: s.name ? s.name : ser?.name,
+ color: s.color ? s.color : ser?.color,
+ type: s.type ? s.type : ser?.type,
+ group: s.group ? s.group : ser?.group,
+ hidden: typeof s.hidden !== 'undefined' ? s.hidden : ser?.hidden,
+ data: s.data ? s.data : ser?.data,
+ zIndex: typeof s.zIndex !== 'undefined' ? s.zIndex : i,
+ }
+ }
+
+ toggleDataPointSelection(seriesIndex, dataPointIndex) {
+ const w = this.w
+ let elPath = null
+ const parent = `.apexcharts-series[data\\:realIndex='${seriesIndex}']`
+
+ if (w.globals.axisCharts) {
+ elPath = w.globals.dom.Paper.select(
+ `${parent} path[j='${dataPointIndex}'], ${parent} circle[j='${dataPointIndex}'], ${parent} rect[j='${dataPointIndex}']`
+ ).members[0]
+ } else {
+ // dataPointIndex will be undefined here, hence using seriesIndex
+ if (typeof dataPointIndex === 'undefined') {
+ elPath = w.globals.dom.Paper.select(
+ `${parent} path[j='${seriesIndex}']`
+ ).members[0]
+
+ if (
+ w.config.chart.type === 'pie' ||
+ w.config.chart.type === 'polarArea' ||
+ w.config.chart.type === 'donut'
+ ) {
+ this.ctx.pie.pieClicked(seriesIndex)
+ }
+ }
+ }
+
+ if (elPath) {
+ const graphics = new Graphics(this.ctx)
+ graphics.pathMouseDown(elPath, null)
+ } else {
+ console.warn('toggleDataPointSelection: Element not found')
+ return null
+ }
+
+ return elPath.node ? elPath.node : null
+ }
+
+ forceXAxisUpdate(options) {
+ const w = this.w
+ const minmax = ['min', 'max']
+
+ minmax.forEach((a) => {
+ if (typeof options.xaxis[a] !== 'undefined') {
+ w.config.xaxis[a] = options.xaxis[a]
+ w.globals.lastXAxis[a] = options.xaxis[a]
+ }
+ })
+
+ if (options.xaxis.categories && options.xaxis.categories.length) {
+ w.config.xaxis.categories = options.xaxis.categories
+ }
+
+ if (w.config.xaxis.convertedCatToNumeric) {
+ const defaults = new Defaults(options)
+ options = defaults.convertCatToNumericXaxis(options, this.ctx)
+ }
+ return options
+ }
+
+ forceYAxisUpdate(options) {
+ if (
+ options.chart &&
+ options.chart.stacked &&
+ options.chart.stackType === '100%'
+ ) {
+ if (Array.isArray(options.yaxis)) {
+ options.yaxis.forEach((yaxe, index) => {
+ options.yaxis[index].min = 0
+ options.yaxis[index].max = 100
+ })
+ } else {
+ options.yaxis.min = 0
+ options.yaxis.max = 100
+ }
+ }
+ return options
+ }
+
+ /**
+ * This function reverts the yaxis and xaxis min/max values to what it was when the chart was defined.
+ * This function fixes an important bug where a user might load a new series after zooming in/out of previous series which resulted in wrong min/max
+ * Also, this should never be called internally on zoom/pan - the reset should only happen when user calls the updateSeries() function externally
+ * The function also accepts an object {xaxis, yaxis} which when present is set as the new xaxis/yaxis
+ */
+ revertDefaultAxisMinMax(opts) {
+ const w = this.w
+
+ let xaxis = w.globals.lastXAxis
+ let yaxis = w.globals.lastYAxis
+
+ if (opts && opts.xaxis) {
+ xaxis = opts.xaxis
+ }
+ if (opts && opts.yaxis) {
+ yaxis = opts.yaxis
+ }
+ w.config.xaxis.min = xaxis.min
+ w.config.xaxis.max = xaxis.max
+
+ const getLastYAxis = (index) => {
+ if (typeof yaxis[index] !== 'undefined') {
+ w.config.yaxis[index].min = yaxis[index].min
+ w.config.yaxis[index].max = yaxis[index].max
+ }
+ }
+
+ w.config.yaxis.map((yaxe, index) => {
+ if (w.globals.zoomed) {
+ // user has zoomed, check the last yaxis
+ getLastYAxis(index)
+ } else {
+ // user hasn't zoomed, check the last yaxis first
+ if (typeof yaxis[index] !== 'undefined') {
+ getLastYAxis(index)
+ } else {
+ // if last y-axis don't exist, check the original yaxis
+ if (typeof this.ctx.opts.yaxis[index] !== 'undefined') {
+ yaxe.min = this.ctx.opts.yaxis[index].min
+ yaxe.max = this.ctx.opts.yaxis[index].max
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/legend/Helpers.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/legend/Helpers.js
new file mode 100644
index 0000000..99780d1
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/legend/Helpers.js
@@ -0,0 +1,298 @@
+import Graphics from '../Graphics'
+import Utils from '../../utils/Utils'
+
+export default class Helpers {
+ constructor(lgCtx) {
+ this.w = lgCtx.w
+ this.lgCtx = lgCtx
+ }
+
+ getLegendStyles() {
+ let stylesheet = document.createElement('style')
+ stylesheet.setAttribute('type', 'text/css')
+ const nonce =
+ this.lgCtx.ctx?.opts?.chart?.nonce || this.w.config.chart.nonce
+ if (nonce) {
+ stylesheet.setAttribute('nonce', nonce)
+ }
+
+ const text = `
+ .apexcharts-flip-y {
+ transform: scaleY(-1) translateY(-100%);
+ transform-origin: top;
+ transform-box: fill-box;
+ }
+ .apexcharts-flip-x {
+ transform: scaleX(-1);
+ transform-origin: center;
+ transform-box: fill-box;
+ }
+ .apexcharts-legend {
+ display: flex;
+ overflow: auto;
+ padding: 0 10px;
+ }
+ .apexcharts-legend.apx-legend-position-bottom, .apexcharts-legend.apx-legend-position-top {
+ flex-wrap: wrap
+ }
+ .apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {
+ flex-direction: column;
+ bottom: 0;
+ }
+ .apexcharts-legend.apx-legend-position-bottom.apexcharts-align-left, .apexcharts-legend.apx-legend-position-top.apexcharts-align-left, .apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {
+ justify-content: flex-start;
+ }
+ .apexcharts-legend.apx-legend-position-bottom.apexcharts-align-center, .apexcharts-legend.apx-legend-position-top.apexcharts-align-center {
+ justify-content: center;
+ }
+ .apexcharts-legend.apx-legend-position-bottom.apexcharts-align-right, .apexcharts-legend.apx-legend-position-top.apexcharts-align-right {
+ justify-content: flex-end;
+ }
+ .apexcharts-legend-series {
+ cursor: pointer;
+ line-height: normal;
+ display: flex;
+ align-items: center;
+ }
+ .apexcharts-legend-text {
+ position: relative;
+ font-size: 14px;
+ }
+ .apexcharts-legend-text *, .apexcharts-legend-marker * {
+ pointer-events: none;
+ }
+ .apexcharts-legend-marker {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ margin-right: 1px;
+ }
+
+ .apexcharts-legend-series.apexcharts-no-click {
+ cursor: auto;
+ }
+ .apexcharts-legend .apexcharts-hidden-zero-series, .apexcharts-legend .apexcharts-hidden-null-series {
+ display: none !important;
+ }
+ .apexcharts-inactive-legend {
+ opacity: 0.45;
+ }`
+
+ let rules = document.createTextNode(text)
+
+ stylesheet.appendChild(rules)
+
+ return stylesheet
+ }
+
+ getLegendDimensions() {
+ const w = this.w
+ let currLegendsWrap =
+ w.globals.dom.baseEl.querySelector('.apexcharts-legend')
+ let { width: currLegendsWrapWidth, height: currLegendsWrapHeight } =
+ currLegendsWrap.getBoundingClientRect()
+
+ return {
+ clwh: currLegendsWrapHeight,
+ clww: currLegendsWrapWidth,
+ }
+ }
+
+ appendToForeignObject() {
+ const gl = this.w.globals
+
+ gl.dom.elLegendForeign.appendChild(this.getLegendStyles())
+ }
+
+ toggleDataSeries(seriesCnt, isHidden) {
+ const w = this.w
+ if (w.globals.axisCharts || w.config.chart.type === 'radialBar') {
+ w.globals.resized = true // we don't want initial animations again
+
+ let seriesEl = null
+
+ let realIndex = null
+
+ // yes, make it null. 1 series will rise at a time
+ w.globals.risingSeries = []
+
+ if (w.globals.axisCharts) {
+ seriesEl = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-series[data\\:realIndex='${seriesCnt}']`
+ )
+ realIndex = parseInt(seriesEl.getAttribute('data:realIndex'), 10)
+ } else {
+ seriesEl = w.globals.dom.baseEl.querySelector(
+ `.apexcharts-series[rel='${seriesCnt + 1}']`
+ )
+ realIndex = parseInt(seriesEl.getAttribute('rel'), 10) - 1
+ }
+
+ if (isHidden) {
+ const seriesToMakeVisible = [
+ {
+ cs: w.globals.collapsedSeries,
+ csi: w.globals.collapsedSeriesIndices,
+ },
+ {
+ cs: w.globals.ancillaryCollapsedSeries,
+ csi: w.globals.ancillaryCollapsedSeriesIndices,
+ },
+ ]
+ seriesToMakeVisible.forEach((r) => {
+ this.riseCollapsedSeries(r.cs, r.csi, realIndex)
+ })
+ } else {
+ this.hideSeries({ seriesEl, realIndex })
+ }
+ } else {
+ // for non-axis charts i.e pie / donuts
+ let seriesEl = w.globals.dom.Paper.select(
+ ` .apexcharts-series[rel='${seriesCnt + 1}'] path`
+ )
+
+ const type = w.config.chart.type
+ if (type === 'pie' || type === 'polarArea' || type === 'donut') {
+ let dataLabels = w.config.plotOptions.pie.donut.labels
+
+ const graphics = new Graphics(this.lgCtx.ctx)
+ graphics.pathMouseDown(seriesEl.members[0], null)
+ this.lgCtx.ctx.pie.printDataLabelsInner(
+ seriesEl.members[0].node,
+ dataLabels
+ )
+ }
+
+ seriesEl.fire('click')
+ }
+ }
+
+ getSeriesAfterCollapsing({ realIndex }) {
+ const w = this.w
+ const gl = w.globals
+
+ let series = Utils.clone(w.config.series)
+
+ if (gl.axisCharts) {
+ let yaxis = w.config.yaxis[gl.seriesYAxisReverseMap[realIndex]]
+
+ const collapseData = {
+ index: realIndex,
+ data: series[realIndex].data.slice(),
+ type: series[realIndex].type || w.config.chart.type,
+ }
+ if (yaxis && yaxis.show && yaxis.showAlways) {
+ if (gl.ancillaryCollapsedSeriesIndices.indexOf(realIndex) < 0) {
+ gl.ancillaryCollapsedSeries.push(collapseData)
+ gl.ancillaryCollapsedSeriesIndices.push(realIndex)
+ }
+ } else {
+ if (gl.collapsedSeriesIndices.indexOf(realIndex) < 0) {
+ gl.collapsedSeries.push(collapseData)
+ gl.collapsedSeriesIndices.push(realIndex)
+
+ let removeIndexOfRising = gl.risingSeries.indexOf(realIndex)
+ gl.risingSeries.splice(removeIndexOfRising, 1)
+ }
+ }
+ } else {
+ gl.collapsedSeries.push({
+ index: realIndex,
+ data: series[realIndex],
+ })
+ gl.collapsedSeriesIndices.push(realIndex)
+ }
+
+ gl.allSeriesCollapsed =
+ gl.collapsedSeries.length + gl.ancillaryCollapsedSeries.length ===
+ w.config.series.length
+
+ return this._getSeriesBasedOnCollapsedState(series)
+ }
+
+ hideSeries({ seriesEl, realIndex }) {
+ const w = this.w
+
+ let series = this.getSeriesAfterCollapsing({
+ realIndex,
+ })
+
+ let seriesChildren = seriesEl.childNodes
+ for (let sc = 0; sc < seriesChildren.length; sc++) {
+ if (
+ seriesChildren[sc].classList.contains('apexcharts-series-markers-wrap')
+ ) {
+ if (seriesChildren[sc].classList.contains('apexcharts-hide')) {
+ seriesChildren[sc].classList.remove('apexcharts-hide')
+ } else {
+ seriesChildren[sc].classList.add('apexcharts-hide')
+ }
+ }
+ }
+
+ this.lgCtx.ctx.updateHelpers._updateSeries(
+ series,
+ w.config.chart.animations.dynamicAnimation.enabled
+ )
+ }
+
+ riseCollapsedSeries(collapsedSeries, seriesIndices, realIndex) {
+ const w = this.w
+ let series = Utils.clone(w.config.series)
+
+ if (collapsedSeries.length > 0) {
+ for (let c = 0; c < collapsedSeries.length; c++) {
+ if (collapsedSeries[c].index === realIndex) {
+ if (w.globals.axisCharts) {
+ series[realIndex].data = collapsedSeries[c].data.slice()
+ } else {
+ series[realIndex] = collapsedSeries[c].data
+ }
+ series[realIndex].hidden = false
+ collapsedSeries.splice(c, 1)
+ seriesIndices.splice(c, 1)
+ w.globals.risingSeries.push(realIndex)
+ }
+ }
+
+ series = this._getSeriesBasedOnCollapsedState(series)
+
+ this.lgCtx.ctx.updateHelpers._updateSeries(
+ series,
+ w.config.chart.animations.dynamicAnimation.enabled
+ )
+ }
+ }
+
+ _getSeriesBasedOnCollapsedState(series) {
+ const w = this.w
+ let collapsed = 0
+
+ if (w.globals.axisCharts) {
+ series.forEach((s, sI) => {
+ if (
+ !(
+ w.globals.collapsedSeriesIndices.indexOf(sI) < 0 &&
+ w.globals.ancillaryCollapsedSeriesIndices.indexOf(sI) < 0
+ )
+ ) {
+ series[sI].data = []
+ collapsed++
+ }
+ })
+ } else {
+ series.forEach((s, sI) => {
+ if (!w.globals.collapsedSeriesIndices.indexOf(sI) < 0) {
+ series[sI] = 0
+ collapsed++
+ }
+ })
+ }
+
+ w.globals.allSeriesCollapsed = collapsed === series.length
+
+ return series
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/legend/Legend.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/legend/Legend.js
new file mode 100644
index 0000000..ec45e95
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/legend/Legend.js
@@ -0,0 +1,478 @@
+import CoreUtils from '../CoreUtils'
+import Dimensions from '../dimensions/Dimensions'
+import Graphics from '../Graphics'
+import Series from '../Series'
+import Utils from '../../utils/Utils'
+import Helpers from './Helpers'
+import Markers from '../Markers'
+
+/**
+ * ApexCharts Legend Class to draw legend.
+ *
+ * @module Legend
+ **/
+
+class Legend {
+ constructor(ctx) {
+ this.ctx = ctx
+ this.w = ctx.w
+
+ this.onLegendClick = this.onLegendClick.bind(this)
+ this.onLegendHovered = this.onLegendHovered.bind(this)
+
+ this.isBarsDistributed =
+ this.w.config.chart.type === 'bar' &&
+ this.w.config.plotOptions.bar.distributed &&
+ this.w.config.series.length === 1
+
+ this.legendHelpers = new Helpers(this)
+ }
+
+ init() {
+ const w = this.w
+
+ const gl = w.globals
+ const cnf = w.config
+
+ const showLegendAlways =
+ (cnf.legend.showForSingleSeries && gl.series.length === 1) ||
+ this.isBarsDistributed ||
+ gl.series.length > 1
+
+ this.legendHelpers.appendToForeignObject()
+
+ if ((showLegendAlways || !gl.axisCharts) && cnf.legend.show) {
+ while (gl.dom.elLegendWrap.firstChild) {
+ gl.dom.elLegendWrap.removeChild(gl.dom.elLegendWrap.firstChild)
+ }
+
+ this.drawLegends()
+
+ if (cnf.legend.position === 'bottom' || cnf.legend.position === 'top') {
+ this.legendAlignHorizontal()
+ } else if (
+ cnf.legend.position === 'right' ||
+ cnf.legend.position === 'left'
+ ) {
+ this.legendAlignVertical()
+ }
+ }
+ }
+
+ createLegendMarker({ i, fillcolor }) {
+ const w = this.w
+ const elMarker = document.createElement('span')
+ elMarker.classList.add('apexcharts-legend-marker')
+
+ let mShape = w.config.legend.markers.shape || w.config.markers.shape
+ let shape = mShape
+ if (Array.isArray(mShape)) {
+ shape = mShape[i]
+ }
+ let mSize = Array.isArray(w.config.legend.markers.size)
+ ? parseFloat(w.config.legend.markers.size[i])
+ : parseFloat(w.config.legend.markers.size)
+ let mOffsetX = Array.isArray(w.config.legend.markers.offsetX)
+ ? parseFloat(w.config.legend.markers.offsetX[i])
+ : parseFloat(w.config.legend.markers.offsetX)
+ let mOffsetY = Array.isArray(w.config.legend.markers.offsetY)
+ ? parseFloat(w.config.legend.markers.offsetY[i])
+ : parseFloat(w.config.legend.markers.offsetY)
+ let mBorderWidth = Array.isArray(w.config.legend.markers.strokeWidth)
+ ? parseFloat(w.config.legend.markers.strokeWidth[i])
+ : parseFloat(w.config.legend.markers.strokeWidth)
+
+ let mStyle = elMarker.style
+
+ mStyle.height = (mSize + mBorderWidth) * 2 + 'px'
+ mStyle.width = (mSize + mBorderWidth) * 2 + 'px'
+ mStyle.left = mOffsetX + 'px'
+ mStyle.top = mOffsetY + 'px'
+
+ if (w.config.legend.markers.customHTML) {
+ mStyle.background = 'transparent'
+ mStyle.color = fillcolor[i]
+
+ if (Array.isArray(w.config.legend.markers.customHTML)) {
+ if (w.config.legend.markers.customHTML[i]) {
+ elMarker.innerHTML = w.config.legend.markers.customHTML[i]()
+ }
+ } else {
+ elMarker.innerHTML = w.config.legend.markers.customHTML()
+ }
+ } else {
+ let markers = new Markers(this.ctx)
+
+ const markerConfig = markers.getMarkerConfig({
+ cssClass: `apexcharts-legend-marker apexcharts-marker apexcharts-marker-${shape}`,
+ seriesIndex: i,
+ strokeWidth: mBorderWidth,
+ size: mSize,
+ })
+
+ const SVGMarker = SVG(elMarker).size('100%', '100%')
+ const marker = new Graphics(this.ctx).drawMarker(0, 0, {
+ ...markerConfig,
+ pointFillColor: Array.isArray(fillcolor)
+ ? fillcolor[i]
+ : markerConfig.pointFillColor,
+ shape,
+ })
+
+ const shapesEls = SVG.select(
+ '.apexcharts-legend-marker.apexcharts-marker'
+ ).members
+ shapesEls.forEach((shapeEl) => {
+ if (shapeEl.node.classList.contains('apexcharts-marker-triangle')) {
+ shapeEl.node.style.transform = 'translate(50%, 45%)'
+ } else {
+ shapeEl.node.style.transform = 'translate(50%, 50%)'
+ }
+ })
+ SVGMarker.add(marker)
+ }
+ return elMarker
+ }
+
+ drawLegends() {
+ let me = this
+ let w = this.w
+
+ let fontFamily = w.config.legend.fontFamily
+
+ let legendNames = w.globals.seriesNames
+ let fillcolor = w.config.legend.markers.fillColors
+ ? w.config.legend.markers.fillColors.slice()
+ : w.globals.colors.slice()
+
+ if (w.config.chart.type === 'heatmap') {
+ const ranges = w.config.plotOptions.heatmap.colorScale.ranges
+ legendNames = ranges.map((colorScale) => {
+ return colorScale.name
+ ? colorScale.name
+ : colorScale.from + ' - ' + colorScale.to
+ })
+ fillcolor = ranges.map((color) => color.color)
+ } else if (this.isBarsDistributed) {
+ legendNames = w.globals.labels.slice()
+ }
+
+ if (w.config.legend.customLegendItems.length) {
+ legendNames = w.config.legend.customLegendItems
+ }
+ let legendFormatter = w.globals.legendFormatter
+
+ let isLegendInversed = w.config.legend.inverseOrder
+
+ for (
+ let i = isLegendInversed ? legendNames.length - 1 : 0;
+ isLegendInversed ? i >= 0 : i <= legendNames.length - 1;
+ isLegendInversed ? i-- : i++
+ ) {
+ let text = legendFormatter(legendNames[i], { seriesIndex: i, w })
+
+ let collapsedSeries = false
+ let ancillaryCollapsedSeries = false
+ if (w.globals.collapsedSeries.length > 0) {
+ for (let c = 0; c < w.globals.collapsedSeries.length; c++) {
+ if (w.globals.collapsedSeries[c].index === i) {
+ collapsedSeries = true
+ }
+ }
+ }
+
+ if (w.globals.ancillaryCollapsedSeriesIndices.length > 0) {
+ for (
+ let c = 0;
+ c < w.globals.ancillaryCollapsedSeriesIndices.length;
+ c++
+ ) {
+ if (w.globals.ancillaryCollapsedSeriesIndices[c] === i) {
+ ancillaryCollapsedSeries = true
+ }
+ }
+ }
+
+ let elMarker = this.createLegendMarker({ i, fillcolor })
+
+ Graphics.setAttrs(elMarker, {
+ rel: i + 1,
+ 'data:collapsed': collapsedSeries || ancillaryCollapsedSeries,
+ })
+
+ if (collapsedSeries || ancillaryCollapsedSeries) {
+ elMarker.classList.add('apexcharts-inactive-legend')
+ }
+
+ let elLegend = document.createElement('div')
+
+ let elLegendText = document.createElement('span')
+ elLegendText.classList.add('apexcharts-legend-text')
+ elLegendText.innerHTML = Array.isArray(text) ? text.join(' ') : text
+
+ let textColor = w.config.legend.labels.useSeriesColors
+ ? w.globals.colors[i]
+ : Array.isArray(w.config.legend.labels.colors)
+ ? w.config.legend.labels.colors?.[i]
+ : w.config.legend.labels.colors
+
+ if (!textColor) {
+ textColor = w.config.chart.foreColor
+ }
+
+ elLegendText.style.color = textColor
+
+ elLegendText.style.fontSize = parseFloat(w.config.legend.fontSize) + 'px'
+ elLegendText.style.fontWeight = w.config.legend.fontWeight
+ elLegendText.style.fontFamily = fontFamily || w.config.chart.fontFamily
+
+ Graphics.setAttrs(elLegendText, {
+ rel: i + 1,
+ i,
+ 'data:default-text': encodeURIComponent(text),
+ 'data:collapsed': collapsedSeries || ancillaryCollapsedSeries,
+ })
+
+ elLegend.appendChild(elMarker)
+ elLegend.appendChild(elLegendText)
+
+ const coreUtils = new CoreUtils(this.ctx)
+ if (!w.config.legend.showForZeroSeries) {
+ const total = coreUtils.getSeriesTotalByIndex(i)
+
+ if (
+ total === 0 &&
+ coreUtils.seriesHaveSameValues(i) &&
+ !coreUtils.isSeriesNull(i) &&
+ w.globals.collapsedSeriesIndices.indexOf(i) === -1 &&
+ w.globals.ancillaryCollapsedSeriesIndices.indexOf(i) === -1
+ ) {
+ elLegend.classList.add('apexcharts-hidden-zero-series')
+ }
+ }
+
+ if (!w.config.legend.showForNullSeries) {
+ if (
+ coreUtils.isSeriesNull(i) &&
+ w.globals.collapsedSeriesIndices.indexOf(i) === -1 &&
+ w.globals.ancillaryCollapsedSeriesIndices.indexOf(i) === -1
+ ) {
+ elLegend.classList.add('apexcharts-hidden-null-series')
+ }
+ }
+
+ w.globals.dom.elLegendWrap.appendChild(elLegend)
+ w.globals.dom.elLegendWrap.classList.add(
+ `apexcharts-align-${w.config.legend.horizontalAlign}`
+ )
+ w.globals.dom.elLegendWrap.classList.add(
+ 'apx-legend-position-' + w.config.legend.position
+ )
+
+ elLegend.classList.add('apexcharts-legend-series')
+ elLegend.style.margin = `${w.config.legend.itemMargin.vertical}px ${w.config.legend.itemMargin.horizontal}px`
+ w.globals.dom.elLegendWrap.style.width = w.config.legend.width
+ ? w.config.legend.width + 'px'
+ : ''
+ w.globals.dom.elLegendWrap.style.height = w.config.legend.height
+ ? w.config.legend.height + 'px'
+ : ''
+
+ Graphics.setAttrs(elLegend, {
+ rel: i + 1,
+ seriesName: Utils.escapeString(legendNames[i]),
+ 'data:collapsed': collapsedSeries || ancillaryCollapsedSeries,
+ })
+
+ if (collapsedSeries || ancillaryCollapsedSeries) {
+ elLegend.classList.add('apexcharts-inactive-legend')
+ }
+
+ if (!w.config.legend.onItemClick.toggleDataSeries) {
+ elLegend.classList.add('apexcharts-no-click')
+ }
+ }
+
+ w.globals.dom.elWrap.addEventListener('click', me.onLegendClick, true)
+
+ if (
+ w.config.legend.onItemHover.highlightDataSeries &&
+ w.config.legend.customLegendItems.length === 0
+ ) {
+ w.globals.dom.elWrap.addEventListener(
+ 'mousemove',
+ me.onLegendHovered,
+ true
+ )
+ w.globals.dom.elWrap.addEventListener(
+ 'mouseout',
+ me.onLegendHovered,
+ true
+ )
+ }
+ }
+
+ setLegendWrapXY(offsetX, offsetY) {
+ let w = this.w
+
+ let elLegendWrap = w.globals.dom.elLegendWrap
+
+ const legendHeight = elLegendWrap.clientHeight
+
+ let x = 0
+ let y = 0
+
+ if (w.config.legend.position === 'bottom') {
+ y =
+ w.globals.svgHeight -
+ Math.min(legendHeight, w.globals.svgHeight / 2) -
+ 5
+ } else if (w.config.legend.position === 'top') {
+ const dim = new Dimensions(this.ctx)
+ const titleH = dim.dimHelpers.getTitleSubtitleCoords('title').height
+ const subtitleH = dim.dimHelpers.getTitleSubtitleCoords('subtitle').height
+
+ y = (titleH > 0 ? titleH - 10 : 0) + (subtitleH > 0 ? subtitleH - 10 : 0)
+ }
+
+ elLegendWrap.style.position = 'absolute'
+
+ x = x + offsetX + w.config.legend.offsetX
+ y = y + offsetY + w.config.legend.offsetY
+
+ elLegendWrap.style.left = x + 'px'
+ elLegendWrap.style.top = y + 'px'
+
+ if (w.config.legend.position === 'right') {
+ elLegendWrap.style.left = 'auto'
+ elLegendWrap.style.right = 25 + w.config.legend.offsetX + 'px'
+ }
+
+ const fixedHeigthWidth = ['width', 'height']
+ fixedHeigthWidth.forEach((hw) => {
+ if (elLegendWrap.style[hw]) {
+ elLegendWrap.style[hw] = parseInt(w.config.legend[hw], 10) + 'px'
+ }
+ })
+ }
+
+ legendAlignHorizontal() {
+ let w = this.w
+
+ let elLegendWrap = w.globals.dom.elLegendWrap
+
+ elLegendWrap.style.right = 0
+
+ let dimensions = new Dimensions(this.ctx)
+ let titleRect = dimensions.dimHelpers.getTitleSubtitleCoords('title')
+ let subtitleRect = dimensions.dimHelpers.getTitleSubtitleCoords('subtitle')
+
+ let offsetX = 20
+ let offsetY = 0
+
+ if (w.config.legend.position === 'top') {
+ offsetY =
+ titleRect.height +
+ subtitleRect.height +
+ w.config.title.margin +
+ w.config.subtitle.margin -
+ 10
+ }
+
+ this.setLegendWrapXY(offsetX, offsetY)
+ }
+
+ legendAlignVertical() {
+ let w = this.w
+
+ let lRect = this.legendHelpers.getLegendDimensions()
+
+ let offsetY = 20
+ let offsetX = 0
+
+ if (w.config.legend.position === 'left') {
+ offsetX = 20
+ }
+
+ if (w.config.legend.position === 'right') {
+ offsetX = w.globals.svgWidth - lRect.clww - 10
+ }
+
+ this.setLegendWrapXY(offsetX, offsetY)
+ }
+
+ onLegendHovered(e) {
+ const w = this.w
+
+ const hoverOverLegend =
+ e.target.classList.contains('apexcharts-legend-series') ||
+ e.target.classList.contains('apexcharts-legend-text') ||
+ e.target.classList.contains('apexcharts-legend-marker')
+
+ if (w.config.chart.type !== 'heatmap' && !this.isBarsDistributed) {
+ if (
+ !e.target.classList.contains('apexcharts-inactive-legend') &&
+ hoverOverLegend
+ ) {
+ let series = new Series(this.ctx)
+ series.toggleSeriesOnHover(e, e.target)
+ }
+ } else {
+ // for heatmap handling
+ if (hoverOverLegend) {
+ let seriesCnt = parseInt(e.target.getAttribute('rel'), 10) - 1
+ this.ctx.events.fireEvent('legendHover', [this.ctx, seriesCnt, this.w])
+
+ let series = new Series(this.ctx)
+ series.highlightRangeInSeries(e, e.target)
+ }
+ }
+ }
+
+ onLegendClick(e) {
+ const w = this.w
+
+ if (w.config.legend.customLegendItems.length) return
+
+ if (
+ e.target.classList.contains('apexcharts-legend-series') ||
+ e.target.classList.contains('apexcharts-legend-text') ||
+ e.target.classList.contains('apexcharts-legend-marker')
+ ) {
+ let seriesCnt = parseInt(e.target.getAttribute('rel'), 10) - 1
+ let isHidden = e.target.getAttribute('data:collapsed') === 'true'
+
+ const legendClick = this.w.config.chart.events.legendClick
+ if (typeof legendClick === 'function') {
+ legendClick(this.ctx, seriesCnt, this.w)
+ }
+
+ this.ctx.events.fireEvent('legendClick', [this.ctx, seriesCnt, this.w])
+
+ const markerClick = this.w.config.legend.markers.onClick
+ if (
+ typeof markerClick === 'function' &&
+ e.target.classList.contains('apexcharts-legend-marker')
+ ) {
+ markerClick(this.ctx, seriesCnt, this.w)
+ this.ctx.events.fireEvent('legendMarkerClick', [
+ this.ctx,
+ seriesCnt,
+ this.w,
+ ])
+ }
+
+ // for now - just prevent click on heatmap legend - and allow hover only
+ const clickAllowed =
+ w.config.chart.type !== 'treemap' &&
+ w.config.chart.type !== 'heatmap' &&
+ !this.isBarsDistributed
+
+ if (clickAllowed && w.config.legend.onItemClick.toggleDataSeries) {
+ this.legendHelpers.toggleDataSeries(seriesCnt, isHidden)
+ }
+ }
+ }
+}
+
+export default Legend
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/settings/Config.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/settings/Config.js
new file mode 100644
index 0000000..efc4b91
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/settings/Config.js
@@ -0,0 +1,338 @@
+import Defaults from './Defaults'
+import Utils from './../../utils/Utils'
+import Options from './Options'
+
+/**
+ * ApexCharts Config Class for extending user options with pre-defined ApexCharts config.
+ *
+ * @module Config
+ **/
+export default class Config {
+ constructor(opts) {
+ this.opts = opts
+ }
+
+ init({ responsiveOverride }) {
+ let opts = this.opts
+ let options = new Options()
+ let defaults = new Defaults(opts)
+
+ this.chartType = opts.chart.type
+
+ opts = this.extendYAxis(opts)
+ opts = this.extendAnnotations(opts)
+
+ let config = options.init()
+ let newDefaults = {}
+ if (opts && typeof opts === 'object') {
+ let chartDefaults = {}
+ const chartTypes = [
+ 'line',
+ 'area',
+ 'bar',
+ 'candlestick',
+ 'boxPlot',
+ 'rangeBar',
+ 'rangeArea',
+ 'bubble',
+ 'scatter',
+ 'heatmap',
+ 'treemap',
+ 'pie',
+ 'polarArea',
+ 'donut',
+ 'radar',
+ 'radialBar',
+ ]
+
+ if (chartTypes.indexOf(opts.chart.type) !== -1) {
+ chartDefaults = defaults[opts.chart.type]()
+ } else {
+ chartDefaults = defaults.line()
+ }
+
+ if (opts.plotOptions?.bar?.isFunnel) {
+ chartDefaults = defaults.funnel()
+ }
+
+ if (opts.chart.stacked && opts.chart.type === 'bar') {
+ chartDefaults = defaults.stackedBars()
+ }
+
+ if (opts.chart.brush?.enabled) {
+ chartDefaults = defaults.brush(chartDefaults)
+ }
+
+ if (opts.plotOptions?.line?.isSlopeChart) {
+ chartDefaults = defaults.slope()
+ }
+
+ if (opts.chart.stacked && opts.chart.stackType === '100%') {
+ opts = defaults.stacked100(opts)
+ }
+
+ if (opts.plotOptions?.bar?.isDumbbell) {
+ opts = defaults.dumbbell(opts)
+ }
+
+ // If user has specified a dark theme, make the tooltip dark too
+ this.checkForDarkTheme(window.Apex) // check global window Apex options
+ this.checkForDarkTheme(opts) // check locally passed options
+
+ opts.xaxis = opts.xaxis || window.Apex.xaxis || {}
+
+ // an important boolean needs to be set here
+ // otherwise all the charts will have this flag set to true window.Apex.xaxis is set globally
+ if (!responsiveOverride) {
+ opts.xaxis.convertedCatToNumeric = false
+ }
+
+ opts = this.checkForCatToNumericXAxis(this.chartType, chartDefaults, opts)
+
+ if (
+ opts.chart.sparkline?.enabled ||
+ window.Apex.chart?.sparkline?.enabled
+ ) {
+ chartDefaults = defaults.sparkline(chartDefaults)
+ }
+ newDefaults = Utils.extend(config, chartDefaults)
+ }
+
+ // config should cascade in this fashion
+ // default-config < global-apex-variable-config < user-defined-config
+
+ // get GLOBALLY defined options and merge with the default config
+ let mergedWithDefaultConfig = Utils.extend(newDefaults, window.Apex)
+
+ // get the merged config and extend with user defined config
+ config = Utils.extend(mergedWithDefaultConfig, opts)
+
+ // some features are not supported. those mismatches should be handled
+ config = this.handleUserInputErrors(config)
+
+ return config
+ }
+
+ checkForCatToNumericXAxis(chartType, chartDefaults, opts) {
+ let defaults = new Defaults(opts)
+
+ const isBarHorizontal =
+ (chartType === 'bar' || chartType === 'boxPlot') &&
+ opts.plotOptions?.bar?.horizontal
+
+ const unsupportedZoom =
+ chartType === 'pie' ||
+ chartType === 'polarArea' ||
+ chartType === 'donut' ||
+ chartType === 'radar' ||
+ chartType === 'radialBar' ||
+ chartType === 'heatmap'
+
+ const notNumericXAxis =
+ opts.xaxis.type !== 'datetime' && opts.xaxis.type !== 'numeric'
+
+ let tickPlacement = opts.xaxis.tickPlacement
+ ? opts.xaxis.tickPlacement
+ : chartDefaults.xaxis && chartDefaults.xaxis.tickPlacement
+ if (
+ !isBarHorizontal &&
+ !unsupportedZoom &&
+ notNumericXAxis &&
+ tickPlacement !== 'between'
+ ) {
+ opts = defaults.convertCatToNumeric(opts)
+ }
+
+ return opts
+ }
+
+ extendYAxis(opts, w) {
+ let options = new Options()
+
+ if (
+ typeof opts.yaxis === 'undefined' ||
+ !opts.yaxis ||
+ (Array.isArray(opts.yaxis) && opts.yaxis.length === 0)
+ ) {
+ opts.yaxis = {}
+ }
+
+ // extend global yaxis config (only if object is provided / not an array)
+ if (
+ opts.yaxis.constructor !== Array &&
+ window.Apex.yaxis &&
+ window.Apex.yaxis.constructor !== Array
+ ) {
+ opts.yaxis = Utils.extend(opts.yaxis, window.Apex.yaxis)
+ }
+
+ // as we can't extend nested object's array with extend, we need to do it first
+ // user can provide either an array or object in yaxis config
+ if (opts.yaxis.constructor !== Array) {
+ // convert the yaxis to array if user supplied object
+ opts.yaxis = [Utils.extend(options.yAxis, opts.yaxis)]
+ } else {
+ opts.yaxis = Utils.extendArray(opts.yaxis, options.yAxis)
+ }
+
+ let isLogY = false
+ opts.yaxis.forEach((y) => {
+ if (y.logarithmic) {
+ isLogY = true
+ }
+ })
+
+ let series = opts.series
+ if (w && !series) {
+ series = w.config.series
+ }
+
+ // A logarithmic chart works correctly when each series has a corresponding y-axis
+ // If this is not the case, we manually create yaxis for multi-series log chart
+ if (isLogY && series.length !== opts.yaxis.length && series.length) {
+ opts.yaxis = series.map((s, i) => {
+ if (!s.name) {
+ series[i].name = `series-${i + 1}`
+ }
+ if (opts.yaxis[i]) {
+ opts.yaxis[i].seriesName = series[i].name
+ return opts.yaxis[i]
+ } else {
+ const newYaxis = Utils.extend(options.yAxis, opts.yaxis[0])
+ newYaxis.show = false
+ return newYaxis
+ }
+ })
+ }
+
+ if (isLogY && series.length > 1 && series.length !== opts.yaxis.length) {
+ console.warn(
+ 'A multi-series logarithmic chart should have equal number of series and y-axes'
+ )
+ }
+ return opts
+ }
+
+ // annotations also accepts array, so we need to extend them manually
+ extendAnnotations(opts) {
+ if (typeof opts.annotations === 'undefined') {
+ opts.annotations = {}
+ opts.annotations.yaxis = []
+ opts.annotations.xaxis = []
+ opts.annotations.points = []
+ }
+
+ opts = this.extendYAxisAnnotations(opts)
+ opts = this.extendXAxisAnnotations(opts)
+ opts = this.extendPointAnnotations(opts)
+
+ return opts
+ }
+
+ extendYAxisAnnotations(opts) {
+ let options = new Options()
+
+ opts.annotations.yaxis = Utils.extendArray(
+ typeof opts.annotations.yaxis !== 'undefined'
+ ? opts.annotations.yaxis
+ : [],
+ options.yAxisAnnotation
+ )
+ return opts
+ }
+
+ extendXAxisAnnotations(opts) {
+ let options = new Options()
+
+ opts.annotations.xaxis = Utils.extendArray(
+ typeof opts.annotations.xaxis !== 'undefined'
+ ? opts.annotations.xaxis
+ : [],
+ options.xAxisAnnotation
+ )
+ return opts
+ }
+ extendPointAnnotations(opts) {
+ let options = new Options()
+
+ opts.annotations.points = Utils.extendArray(
+ typeof opts.annotations.points !== 'undefined'
+ ? opts.annotations.points
+ : [],
+ options.pointAnnotation
+ )
+ return opts
+ }
+
+ checkForDarkTheme(opts) {
+ if (opts.theme && opts.theme.mode === 'dark') {
+ if (!opts.tooltip) {
+ opts.tooltip = {}
+ }
+ if (opts.tooltip.theme !== 'light') {
+ opts.tooltip.theme = 'dark'
+ }
+
+ if (!opts.chart.foreColor) {
+ opts.chart.foreColor = '#f6f7f8'
+ }
+
+ if (!opts.theme.palette) {
+ opts.theme.palette = 'palette4'
+ }
+ }
+ }
+
+ handleUserInputErrors(opts) {
+ let config = opts
+ // conflicting tooltip option. intersect makes sure to focus on 1 point at a time. Shared cannot be used along with it
+ if (config.tooltip.shared && config.tooltip.intersect) {
+ throw new Error(
+ 'tooltip.shared cannot be enabled when tooltip.intersect is true. Turn off any other option by setting it to false.'
+ )
+ }
+
+ if (config.chart.type === 'bar' && config.plotOptions.bar.horizontal) {
+ // No multiple yaxis for bars
+ if (config.yaxis.length > 1) {
+ throw new Error(
+ 'Multiple Y Axis for bars are not supported. Switch to column chart by setting plotOptions.bar.horizontal=false'
+ )
+ }
+
+ // if yaxis is reversed in horizontal bar chart, you should draw the y-axis on right side
+ if (config.yaxis[0].reversed) {
+ config.yaxis[0].opposite = true
+ }
+
+ config.xaxis.tooltip.enabled = false // no xaxis tooltip for horizontal bar
+ config.yaxis[0].tooltip.enabled = false // no xaxis tooltip for horizontal bar
+ config.chart.zoom.enabled = false // no zooming for horz bars
+ }
+
+ if (config.chart.type === 'bar' || config.chart.type === 'rangeBar') {
+ if (config.tooltip.shared) {
+ if (
+ config.xaxis.crosshairs.width === 'barWidth' &&
+ config.series.length > 1
+ ) {
+ config.xaxis.crosshairs.width = 'tickWidth'
+ }
+ }
+ }
+
+ if (
+ config.chart.type === 'candlestick' ||
+ config.chart.type === 'boxPlot'
+ ) {
+ if (config.yaxis[0].reversed) {
+ console.warn(
+ `Reversed y-axis in ${config.chart.type} chart is not supported.`
+ )
+ config.yaxis[0].reversed = false
+ }
+ }
+
+ return config
+ }
+}
diff --git a/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/settings/Defaults.js b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/settings/Defaults.js
new file mode 100644
index 0000000..99853e9
--- /dev/null
+++ b/flexy-bootstrap-lite-1.0.0/assets/libs/apexcharts/src/modules/settings/Defaults.js
@@ -0,0 +1,1220 @@
+import Utils from '../../utils/Utils'
+import DateTime from '../../utils/DateTime'
+import Formatters from '../Formatters'
+
+/**
+ * ApexCharts Default Class for setting default options for all chart types.
+ *
+ * @module Defaults
+ **/
+
+const getRangeValues = ({
+ isTimeline,
+ ctx,
+ seriesIndex,
+ dataPointIndex,
+ y1,
+ y2,
+ w,
+}) => {
+ let start = w.globals.seriesRangeStart[seriesIndex][dataPointIndex]
+ let end = w.globals.seriesRangeEnd[seriesIndex][dataPointIndex]
+ let ylabel = w.globals.labels[dataPointIndex]
+ let seriesName = w.config.series[seriesIndex].name
+ ? w.config.series[seriesIndex].name
+ : ''
+ const yLbFormatter = w.globals.ttKeyFormatter
+ const yLbTitleFormatter = w.config.tooltip.y.title.formatter
+
+ const opts = {
+ w,
+ seriesIndex,
+ dataPointIndex,
+ start,
+ end,
+ }
+
+ if (typeof yLbTitleFormatter === 'function') {
+ seriesName = yLbTitleFormatter(seriesName, opts)
+ }
+ if (w.config.series[seriesIndex].data[dataPointIndex]?.x) {
+ ylabel = w.config.series[seriesIndex].data[dataPointIndex].x
+ }
+
+ if (!isTimeline) {
+ if (w.config.xaxis.type === 'datetime') {
+ let xFormat = new Formatters(ctx)
+ ylabel = xFormat.xLabelFormat(w.globals.ttKeyFormatter, ylabel, ylabel, {
+ i: undefined,
+ dateFormatter: new DateTime(ctx).formatDate,
+ w,
+ })
+ }
+ }
+
+ if (typeof yLbFormatter === 'function') {
+ ylabel = yLbFormatter(ylabel, opts)
+ }
+ if (Number.isFinite(y1) && Number.isFinite(y2)) {
+ start = y1
+ end = y2
+ }
+
+ let startVal = ''
+ let endVal = ''
+
+ const color = w.globals.colors[seriesIndex]
+ if (w.config.tooltip.x.formatter === undefined) {
+ if (w.config.xaxis.type === 'datetime') {
+ let datetimeObj = new DateTime(ctx)
+ startVal = datetimeObj.formatDate(
+ datetimeObj.getDate(start),
+ w.config.tooltip.x.format
+ )
+ endVal = datetimeObj.formatDate(
+ datetimeObj.getDate(end),
+ w.config.tooltip.x.format
+ )
+ } else {
+ startVal = start
+ endVal = end
+ }
+ } else {
+ startVal = w.config.tooltip.x.formatter(start)
+ endVal = w.config.tooltip.x.formatter(end)
+ }
+
+ return { start, end, startVal, endVal, ylabel, color, seriesName }
+}
+const buildRangeTooltipHTML = (opts) => {
+ let { color, seriesName, ylabel, start, end, seriesIndex, dataPointIndex } =
+ opts
+
+ const formatter = opts.ctx.tooltip.tooltipLabels.getFormatters(seriesIndex)
+
+ start = formatter.yLbFormatter(start)
+ end = formatter.yLbFormatter(end)
+ const val = formatter.yLbFormatter(
+ opts.w.globals.series[seriesIndex][dataPointIndex]
+ )
+
+ let valueHTML = ''
+ const rangeValues = `
+ ${start}
+ -
+ ${end}
+ `
+
+ if (opts.w.globals.comboCharts) {
+ if (
+ opts.w.config.series[seriesIndex].type === 'rangeArea' ||
+ opts.w.config.series[seriesIndex].type === 'rangeBar'
+ ) {
+ valueHTML = rangeValues
+ } else {
+ valueHTML = `${val}`
+ }
+ } else {
+ valueHTML = rangeValues
+ }
+ return (
+ '