Compare commits

...

4 Commits

Author SHA1 Message Date
dfb42c11b4 añadido link a la pagina de la demo 2025-04-26 15:38:16 -03:00
14b0f1b805 añadido soporte pantallas pequeñas 2025-03-24 15:12:05 -03:00
2484cf8191 correjido comportmiento del formulario de editar 2025-03-18 00:46:37 -03:00
3e0452c4d7 cleanup: eliminado evento sin uso 2025-03-18 00:46:19 -03:00
6 changed files with 207 additions and 46 deletions

View File

@@ -1,3 +1,6 @@
# Coord-Keeper # Coord-Keeper
Una pequeñisima app que estoy haciendo para almacenar localmente coordenadas de minecraft Una pequeñisima app que estoy haciendo para almacenar localmente coordenadas de minecraft
[Live Demo](https://fedesrv.ddns.net/coords)

View File

@@ -6,27 +6,80 @@ export default function Barra ({active, setType}) {
Coords-Keeper Coords-Keeper
</a> </a>
<div class="justify-content-end">
<ul class="navbar-nav">
<li className="nav-item">
<button <button
className={`btn bg-primary mx-2 ${active === "overworld" ? "text-black active" : "text-gray-100"}`} class="navbar-toggler"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#offcanvasNavbar"
aria-controls="offcanvasNavbar"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNavbar">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Coords-Keeper</h5>
<button
type="button"
class="btn-close"
data-bs-dismiss="offcanvas"
aria-label="Close"
></button>
</div>
<div class="offcanvas-body">
<ul class="navbar-nav justify-content-end flex-grow-1">
<li className="nav-item">
<a
className={`nav-link d-lg-none ${active === "overworld" ? "active" : ""}`}
onClick={() => {
setType("overworld");
document
.querySelector('[data-bs-dismiss="offcanvas"]')
.click();
}}
>
Overworld
</a>
<button
className={`btn bg-primary mx-2 d-none d-lg-block ${active === "overworld" ? "text-black active" : "text-gray-100"}`}
onClick={() => setType("overworld")} onClick={() => setType("overworld")}
> >
Overworld Overworld
</button> </button>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<a
className={`nav-link d-lg-none ${active === "nether" ? "active" : ""}`}
onClick={() => {
setType("nether");
document
.querySelector('[data-bs-dismiss="offcanvas"]')
.click();
}}
>
Nether
</a>
<button <button
className={`btn bg-danger mx-2 ${active === "nether" ? "text-black active" : "text-gray-100"}`} className={`btn bg-danger mx-2 d-none d-lg-block ${active === "nether" ? "text-black active" : "text-gray-100"}`}
onClick={() => setType("nether")} onClick={() => setType("nether")}
> >
Nether Nether
</button> </button>
</li> </li>
<li className="nav-item"> <li className="nav-item">
<a
className={`nav-link d-lg-none ${active === "end" ? "active" : ""}`}
onClick={() => {
setType("end");
document
.querySelector('[data-bs-dismiss="offcanvas"]')
.click();
}}
>
End
</a>
<button <button
className={`btn bg-success mx-2 ${active === "end" ? "text-black active" : "text-gray-100"}`} className={`btn bg-success mx-2 d-none d-lg-block ${active === "end" ? "text-black active" : "text-gray-100"}`}
onClick={() => setType("end")} onClick={() => setType("end")}
> >
End End
@@ -35,6 +88,7 @@ export default function Barra ({active, setType}) {
</ul> </ul>
</div> </div>
</div> </div>
</div>
</nav> </nav>
); );
} }

View File

@@ -0,0 +1,22 @@
export default function BotonAdd({ show, setshow }) {
if (!show) return null;
return (
<button
className="btn btn-primary rounded-circle position-fixed bottom-0 end-0 m-4 d-flex align-items-center justify-content-center"
style={{ width: "60px", height: "60px" }}
onClick={() => setshow(true)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
className="bi bi-plus"
viewBox="0 0 16 16"
>
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z" />
</svg>
</button>
);
}

View File

@@ -1,7 +1,9 @@
import { useState } from "react"; import React from "react";
export default function EditarModal({ tipo, coord, show, close, setCoords }) { export default function EditarModal({ tipo, coord, show, close, setCoords }) {
if (coord == null) return null; if (coord == null) return null;
const [hasY, setHasY] = useState(coord.y !== null);
const [showY, setShowY] = React.useState(coord.y !== null);
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
@@ -11,6 +13,7 @@ export default function EditarModal({ tipo, coord, show, close, setCoords }) {
const y = formData.get("y"); const y = formData.get("y");
const z = formData.get("z"); const z = formData.get("z");
const descripcion = formData.get("descripcion"); const descripcion = formData.get("descripcion");
const hasY = formData.get("hasY") === "true";
let storedTipo = localStorage.getItem(tipo); let storedTipo = localStorage.getItem(tipo);
if (storedTipo) { if (storedTipo) {
@@ -20,7 +23,7 @@ export default function EditarModal({ tipo, coord, show, close, setCoords }) {
arr[index] = { arr[index] = {
...coord, ...coord,
x: Number(x), x: Number(x),
y: y ? Number(y) : null, y: hasY ? Number(y) : null,
z: Number(z), z: Number(z),
descripcion: descripcion, descripcion: descripcion,
}; };
@@ -57,12 +60,11 @@ export default function EditarModal({ tipo, coord, show, close, setCoords }) {
<div className="mb-3"> <div className="mb-3">
<input <input
type="checkbox" type="checkbox"
name="hasY"
value="true"
className="form-check-input me-2" className="form-check-input me-2"
checked={hasY} defaultChecked={coord.y !== null}
onChange={(e) => { onChange={(e) => setShowY(e.target.checked)}
coord.y = e.target.checked ? "0" : null;
setHasY(e.target.checked);
}}
/> />
<label className="form-label">Lleva coordenada Y?</label> <label className="form-label">Lleva coordenada Y?</label>
</div> </div>
@@ -75,7 +77,7 @@ export default function EditarModal({ tipo, coord, show, close, setCoords }) {
defaultValue={coord.x} defaultValue={coord.x}
/> />
</div> </div>
{hasY && ( {showY && (
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Coordenada Y</label> <label className="form-label">Coordenada Y</label>
<input <input

View File

@@ -0,0 +1,52 @@
export default function ModalImport({ show, handleAddCoord, close }) {
if (!show) return null;
let coord = "";
return (
<div
className="modal"
tabIndex="-1"
style={{ display: "block", backgroundColor: "rgba(0,0,0,0.3)" }}
>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Añadir Coordenadas</h5>
<button
type="button"
className="btn-close"
onClick={() => close(false)}
></button>
</div>
<div className="modal-body">
<div className="form-floating mb-3">
<input
type="text"
className="form-control"
id="coordInput"
placeholder="Coordenadas"
onChange={(event) => (coord = event.target.value)}
/>
<label htmlFor="coordInput">Pega la coord</label>
</div>
</div>
<div className="modal-footer d-flex justify-content-between">
<button
type="button"
className="btn btn-secondary"
onClick={() => close(false)}
>
Close
</button>
<button
type="button"
className="btn btn-primary"
onClick={() => handleAddCoord(coord)}
>
Guardar Coord
</button>
</div>
</div>
</div>
</div>
);
}

View File

@@ -6,6 +6,8 @@ import RemoveIcon from "./RemoveIcon";
import StaticModal from "./StaticModal"; import StaticModal from "./StaticModal";
import EditarModal from "./EditarModal"; import EditarModal from "./EditarModal";
import TimedToast from "./TimedToast"; import TimedToast from "./TimedToast";
import BotonAdd from "./BotonAdd";
import ModalImport from "./ModalImport";
function Modall({ show, CloseModal, Coord, Coords, EliminarCoord, tipo }) { function Modall({ show, CloseModal, Coord, Coords, EliminarCoord, tipo }) {
if (show === true && Coord !== null) { if (show === true && Coord !== null) {
@@ -34,18 +36,40 @@ export default function Keeper({ tipo }) {
const [showEditar, setEditar] = useState(false); const [showEditar, setEditar] = useState(false);
const [showToast, setShowToast] = useState(false); const [showToast, setShowToast] = useState(false);
const [selCoord, setCoord] = useState(null); const [selCoord, setCoord] = useState(null);
const [showImport, setShowImport] = useState(false);
useEffect(() => { useEffect(() => {
const storedCoords = JSON.parse(localStorage.getItem(tipo) || "[]"); const storedCoords = JSON.parse(localStorage.getItem(tipo) || "[]");
console.log(storedCoords);
if (storedCoords) { if (storedCoords) {
setCoords(storedCoords); setCoords(storedCoords);
} }
}, [tipo]); }, [tipo]);
const añadirCoord = (coord) => {
const regex = /- (.*), X: (-?\d+)(?:\s*Y:\s*(-?\d+))?\s*Z:\s*(-?\d+)/;
const match = coord.match(regex);
if (match) {
const [_, descripcion, x, y, z] = match;
const newCoord = {
descripcion,
x: parseInt(x),
y: y ? parseInt(y) : null,
z: parseInt(z),
num: coords.length + 1,
};
const newCoords = [...coords, newCoord];
setCoords(newCoords);
localStorage.setItem(tipo, JSON.stringify(newCoords));
setShowToast(true);
setShowImport(false);
}
};
useEffect(() => { useEffect(() => {
const handlePaste = async (e) => { const handlePaste = async () => {
const text = await navigator.clipboard.readText(); const text = await navigator.clipboard.readText();
const regex = /- (.*), X: (-?\d+)(?:\s*Y:\s*(-?\d+))?\s*Z:\s*(-?\d+)/; const regex = /- (.*), X: (-?\d+)(?:\s*Y:\s*(-?\d+))?\s*Z:\s*(-?\d+)/;
const match = text.match(regex); const match = text.match(regex);
@@ -104,6 +128,15 @@ export default function Keeper({ tipo }) {
}; };
return ( return (
<> <>
<div className="d-lg-none">
<BotonAdd show={true} setshow={setShowImport} />
</div>
<ModalImport
show={showImport}
handleAddCoord={añadirCoord}
close={setShowImport}
/>
<Modall <Modall
show={showModal} show={showModal}
CloseModal={() => setModal(false)} CloseModal={() => setModal(false)}
@@ -112,7 +145,6 @@ export default function Keeper({ tipo }) {
EliminarCoord={setCoords} EliminarCoord={setCoords}
tipo={tipo} tipo={tipo}
/> />
<EditarModal <EditarModal
show={showEditar} show={showEditar}
close={() => setEditar(false)} close={() => setEditar(false)}
@@ -120,14 +152,12 @@ export default function Keeper({ tipo }) {
coord={selCoord} coord={selCoord}
setCoords={setCoords} setCoords={setCoords}
/> />
<TimedToast <TimedToast
durationMs={5000} durationMs={5000}
setShow={setShowToast} setShow={setShowToast}
show={showToast} show={showToast}
message={`Copiada Coordenada [${selCoord?.descripcion}]`} message={`Copiada Coordenada [${selCoord?.descripcion}]`}
/> />
<div className="accordion" id="accordionNuevaCoord"> <div className="accordion" id="accordionNuevaCoord">
<div className="accordion-item"> <div className="accordion-item">
<h2 className="accordion-header" id="headingNuevaCoord"> <h2 className="accordion-header" id="headingNuevaCoord">
@@ -162,13 +192,11 @@ export default function Keeper({ tipo }) {
</div> </div>
</div> </div>
<hr /> <hr />
{coords.length == 0 && ( {coords.length == 0 && (
<h2 className="list-group-item text-center"> <h2 className="list-group-item text-center">
No hay Coordenadas que Mostrar. No hay Coordenadas que Mostrar.
</h2> </h2>
)} )}
<table class="table table-hover table-striped"> <table class="table table-hover table-striped">
<thead> <thead>
<tr> <tr>