Compare commits

...

8 Commits

10 changed files with 499 additions and 70 deletions

View File

@@ -1,18 +1,18 @@
import { useState } from 'react' import { useState } from "react";
import Barra from './components/Barra'; import Barra from "./components/Barra";
import Keeper from './components/coordkeeper'; import Keeper from "./components/coordkeeper";
function App() { function App() {
const [type, setType] = useState("overworld"); const [type, setType] = useState("overworld");
return ( return (
<div> <div>
<Barra active={type} setType={setType} /> <Barra active={type} setType={setType} />
<div class="container border text-white vw-100 mt-2 rounded p-2"> <div class="container border text-white vw-100 mt-2 rounded p-2">
<Keeper tipo={type}/> <Keeper tipo={type} />
</div> </div>
</div> </div>
) );
} }
export default App; export default App;

View File

@@ -0,0 +1,15 @@
export default function EditarIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m21.561 5.318l-2.879-2.879A1.5 1.5 0 0 0 17.621 2c-.385 0-.768.146-1.061.439L13 6H4a1 1 0 0 0-1 1v13a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1v-9l3.561-3.561c.293-.293.439-.677.439-1.061s-.146-.767-.439-1.06M11.5 14.672L9.328 12.5l6.293-6.293l2.172 2.172zm-2.561-1.339l1.756 1.728L9 15zM16 19H5V8h6l-3.18 3.18c-.293.293-.478.812-.629 1.289c-.16.5-.191 1.056-.191 1.47V17h3.061c.414 0 1.108-.1 1.571-.29c.464-.19.896-.347 1.188-.64L16 13zm2.5-11.328L16.328 5.5l1.293-1.293l2.171 2.172z"
></path>
</svg>
);
}

View File

@@ -0,0 +1,124 @@
import { useState } from "react";
export default function EditarModal({ tipo, coord, show, close, setCoords }) {
if (coord == null) return null;
const [hasY, setHasY] = useState(coord.y !== null);
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const x = formData.get("x");
const y = formData.get("y");
const z = formData.get("z");
const descripcion = formData.get("descripcion");
let storedTipo = localStorage.getItem(tipo);
if (storedTipo) {
let arr = JSON.parse(storedTipo);
const index = arr.findIndex((item) => item.num === coord.num);
if (index !== -1) {
arr[index] = {
...coord,
x: Number(x),
y: y ? Number(y) : null,
z: Number(z),
descripcion: descripcion,
};
localStorage.setItem(tipo, JSON.stringify(arr));
setCoords(arr);
}
}
close();
};
if (!show) {
return null;
}
return (
<div
className="modal"
id="formModal"
tabIndex="-1"
style={{ display: "block" }}
>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Update Coord</h5>
<button
type="button"
className="btn-close"
onClick={close}
></button>
</div>
<div className="modal-body">
<form onSubmit={handleSubmit}>
<div className="mb-3">
<input
type="checkbox"
className="form-check-input me-2"
checked={hasY}
onChange={(e) => {
coord.y = e.target.checked ? "0" : null;
setHasY(e.target.checked);
}}
/>
<label className="form-label">Lleva coordenada Y?</label>
</div>
<div className="mb-3">
<label className="form-label">Coordenada X</label>
<input
type="number"
name="x"
className="form-control"
defaultValue={coord.x}
/>
</div>
{hasY && (
<div className="mb-3">
<label className="form-label">Coordenada Y</label>
<input
type="number"
name="y"
className="form-control"
defaultValue={coord.y}
/>
</div>
)}
<div className="mb-3">
<label className="form-label">Coordenada Z</label>
<input
type="number"
name="z"
className="form-control"
defaultValue={coord.z}
/>
</div>
<div className="mb-3">
<label className="form-label">Descripción</label>
<input
type="text"
name="descripcion"
className="form-control"
defaultValue={coord.descripcion}
/>
</div>
<hr />
<button type="submit" className="btn btn-primary m-2">
Actualizar
</button>
<button
type="button"
className="btn btn-secondary"
onClick={close}
>
Cancelar
</button>
</form>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,10 +1,11 @@
import { useState } from "react"; import { useState } from "react";
export default function NuevaCoord({coord, setcoord, colapsar, tipo}) { export default function NuevaCoord({ coord, setcoord, colapsar, tipo }) {
const [llevaY, setLlevaY] = useState(false); const [llevaY, setLlevaY] = useState(false);
const [x, setX] = useState(""); const [x, setX] = useState("");
const [y, setY] = useState(""); const [y, setY] = useState("");
const [z, setZ] = useState(""); const [z, setZ] = useState("");
const [descripcion, setDescripcion] = useState("");
const handleAdd = () => { const handleAdd = () => {
const newCoord = { const newCoord = {
@@ -12,44 +13,68 @@ export default function NuevaCoord({coord, setcoord, colapsar, tipo}) {
x: x, x: x,
y: llevaY ? y : null, y: llevaY ? y : null,
z: z, z: z,
descripcion: descripcion,
}; };
setcoord([...coord, newCoord]); setcoord([...coord, newCoord]);
localStorage.setItem(tipo, JSON.stringify([...coord, newCoord])); localStorage.setItem(tipo, JSON.stringify([...coord, newCoord]));
if (colapsar) colapsar();
}; };
return ( return (
<div> <div>
<input id="1" <input
type="checkbox" className="form-check-input me-2" id="1"
type="checkbox"
className="form-check-input me-2"
onChange={(e) => setLlevaY(e.target.checked)} onChange={(e) => setLlevaY(e.target.checked)}
data-bs-toggle="collapse" data-bs-toggle="collapse"
data-bs-target="#collapseY" data-bs-target="#collapseY"
/> />
<label className="form-check-label" htmlFor="1"> <label className="form-check-label" htmlFor="1">
Lleva coordenada y? Lleva coordenada y?
</label> </label>
<hr/> <hr />
<div className="d-flex align-items-center gap-1"> <div className="d-flex align-items-center gap-1">
<div className="input-group mb-3"> <div className="input-group mb-3">
<span class="input-group-text">x:</span> <span class="input-group-text">X:</span>
<input type="text" className="form-control" onChange={(e) =>setX(e.target.value)}/> <input
type="text"
className="form-control"
onChange={(e) => setX(e.target.value)}
/>
</div> </div>
<div className="input-group mb-3 collapse" id="collapseY"> <div className="input-group mb-3 collapse" id="collapseY">
<span class="input-group-text">y:</span> <span class="input-group-text">Y:</span>
<input type="text" className="form-control" onChange={(e) =>setY(e.target.value)}/> <input
type="text"
className="form-control"
onChange={(e) => setY(e.target.value)}
/>
</div> </div>
<div className="input-group mb-3"> <div className="input-group mb-3">
<span class="input-group-text">z:</span> <span class="input-group-text">Z:</span>
<input type="text" className="form-control" onChange={(e) =>setZ(e.target.value)}/> <input
type="text"
className="form-control"
onChange={(e) => setZ(e.target.value)}
/>
</div> </div>
</div> </div>
<hr/> <div className="input-group mb-3">
<button className="btn btn-primary" onClick={handleAdd}>Añadir</button> <span class="input-group-text">Descripción:</span>
<input
type="text"
className="form-control"
onChange={(e) => setDescripcion(e.target.value)}
/>
</div>
<hr />
<button className="btn btn-primary" onClick={handleAdd}>
Añadir
</button>
</div> </div>
); );
} }

View File

@@ -0,0 +1,15 @@
export default function RemoveIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zm2-4h2V8H9zm4 0h2V8h-2z"
></path>
</svg>
);
}

View File

@@ -0,0 +1,15 @@
export default function ShareIcon() {
return(
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M17 22q-1.25 0-2.125-.875T14 19q0-.15.075-.7L7.05 14.2q-.4.375-.925.588T5 15q-1.25 0-2.125-.875T2 12t.875-2.125T5 9q.6 0 1.125.213t.925.587l7.025-4.1q-.05-.175-.062-.337T14 5q0-1.25.875-2.125T17 2t2.125.875T20 5t-.875 2.125T17 8q-.6 0-1.125-.213T14.95 7.2l-7.025 4.1q.05.175.063.338T8 12t-.012.363t-.063.337l7.025 4.1q.4-.375.925-.587T17 16q1.25 0 2.125.875T20 19t-.875 2.125T17 22"
></path>
</svg>,
);
}

View File

@@ -0,0 +1,49 @@
export default function Modal({ CloseModal, Coord, EliminarCoord }) {
return (
<>
<div
className="modal fade show"
id="staticBackdrop"
data-bs-backdrop="static"
style={{ display: "block" }}
>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h1 className="modal-title fs-5">Eliminar Coordenada</h1>
<button
type="button"
className="btn-close"
onClick={CloseModal}
></button>
</div>
<div className="modal-body">
{console.log(Coord)}
Desea eliminar la Coordenada: X: {Coord.x},
{Coord.y != null && `Y: ${Coord.y}, `}Z: {Coord.z}
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-danger"
onClick={() => {
EliminarCoord();
CloseModal();
}}
>
Eliminar
</button>
<button
type="button"
className="btn btn-secondary"
onClick={CloseModal}
>
Cerrar
</button>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,35 @@
import { useEffect } from "react";
export default function TimedToast({ message, durationMs, show, setShow }) {
useEffect(() => {
const timer = setTimeout(() => {
setShow(false);
}, durationMs);
return () => clearTimeout(timer);
}, [durationMs]);
if (!show) return null;
return (
<div className="position-fixed bottom-0 end-0 p-3" style={{ zIndex: 11 }}>
<div
className="toast show"
role="alert"
aria-live="assertive"
aria-atomic="true"
>
<div className="toast-header">
<strong className="me-auto">Notification</strong>
<button
type="button"
className="btn-close"
onClick={() => setShow(false)}
aria-label="Close"
></button>
</div>
<div className="toast-body">{message}</div>
</div>
</div>
);
}

View File

@@ -1,13 +1,42 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import NuevaCoord from "./NuevaCoord"; import NuevaCoord from "./NuevaCoord";
import EditarIcon from "./EditarIcon";
import ShareIcon from "./ShareIcon";
import RemoveIcon from "./RemoveIcon";
import StaticModal from "./StaticModal";
import EditarModal from "./EditarModal";
import TimedToast from "./TimedToast";
function Modall({ show, CloseModal, Coord, Coords, EliminarCoord, tipo }) {
if (show === true && Coord !== null) {
return (
<StaticModal
CloseModal={CloseModal}
Coord={Coord}
EliminarCoord={() => {
const nuevasCoords = Coords.filter((c) => c !== Coord);
localStorage.setItem(tipo, JSON.stringify(nuevasCoords));
EliminarCoord(nuevasCoords);
}}
/>
);
}
return null;
}
export default function Keeper({ tipo }) {
const [coords, setCoords] = useState(
JSON.parse(localStorage.getItem(tipo) || "[]"),
);
export default function Keeper({tipo}){
const [coords,setCoords] = useState(JSON.parse(localStorage.getItem(tipo)||"[]"));
const collapseRef = useRef(null); const collapseRef = useRef(null);
const [showModal, setModal] = useState(false);
const [showEditar, setEditar] = useState(false);
const [showToast, setShowToast] = useState(false);
const [selCoord, setCoord] = useState(null);
console.log(coords);
useEffect(() => { useEffect(() => {
const storedCoords = JSON.parse(localStorage.getItem(tipo)||"[]"); const storedCoords = JSON.parse(localStorage.getItem(tipo) || "[]");
console.log(storedCoords); console.log(storedCoords);
if (storedCoords) { if (storedCoords) {
@@ -15,6 +44,56 @@ export default function Keeper({tipo}){
} }
}, [tipo]); }, [tipo]);
useEffect(() => {
const handlePaste = async (e) => {
const text = await navigator.clipboard.readText();
const regex = /- (.*), X: (-?\d+)(?:\s*Y:\s*(-?\d+))?\s*Z:\s*(-?\d+)/;
const match = text.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);
}
};
document.addEventListener("keydown", (e) => {
if (e.ctrlKey && e.key === "v") {
handlePaste(e);
}
});
/*return () => {
document.removeEventListener("keydown", handlePaste);
};*/
}, [coords, tipo]);
const Editar = (coord) => {
setCoord(coord);
setEditar(true);
};
const Compartir = (coord) => {
setCoord(coord);
const textToCopy = `- ${coord.descripcion}, X: ${coord.x}${coord.y ? ` Y: ${coord.y}` : ""} Z: ${coord.z}`;
navigator.clipboard.writeText(textToCopy);
setShowToast(true);
};
const Remover = (coord) => {
setCoord(coord);
setModal(true);
};
const collapseAccordion = () => { const collapseAccordion = () => {
if (collapseRef.current) { if (collapseRef.current) {
const bsCollapse = new window.bootstrap.Collapse(collapseRef.current, { const bsCollapse = new window.bootstrap.Collapse(collapseRef.current, {
@@ -25,6 +104,30 @@ export default function Keeper({tipo}){
}; };
return ( return (
<> <>
<Modall
show={showModal}
CloseModal={() => setModal(false)}
Coord={selCoord}
Coords={coords}
EliminarCoord={setCoords}
tipo={tipo}
/>
<EditarModal
show={showEditar}
close={() => setEditar(false)}
tipo={tipo}
coord={selCoord}
setCoords={setCoords}
/>
<TimedToast
durationMs={5000}
setShow={setShowToast}
show={showToast}
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">
@@ -48,20 +151,71 @@ export default function Keeper({tipo}){
ref={collapseRef} ref={collapseRef}
> >
<div className="accordion-body"> <div className="accordion-body">
<NuevaCoord coord={coords} setcoord={setCoords} colapsar={collapseAccordion} tipo={tipo} /> <NuevaCoord
coord={coords}
setcoord={setCoords}
colapsar={collapseAccordion}
tipo={tipo}
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<hr/> <hr />
{coords.length == 0 && {coords.length == 0 && (
<h3 className="text-center">No hay Coordenadas que Mostrar.</h3> <h2 className="list-group-item text-center">
} No hay Coordenadas que Mostrar.
{coords.length !=0 && coords.map(x=> </h2>
<div className="m-2"><b>#{x.num}:</b> x:{x.x} {x.y !== null && `y: ${x.y}`} z:{x.z} <button class="btn btn-outline-warning btn-sm">Editar</button><button className="btn btn-sm btn-outline-info ms-2">Compartir</button></div> )}
)} <table class="table table-hover table-striped">
<thead>
<tr>
<th>#</th>
<th>X:</th>
<th>Y:</th>
<th>Z:</th>
<th>Descripcion</th>
<th></th>
</tr>
</thead>
<tbody>
{coords.length != 0 &&
coords.map((x) => (
<tr>
<td>
<b>{x.num}</b>
</td>
<td>{x.x}</td>
<td>{x.y !== null && `Y: ${x.y}`}</td>
<td>{x.z}</td>
<td>{x.descripcion}</td>
<td>
<button
class="ms-2 btn btn-outline-warning btn-sm"
onClick={() => Editar(x)}
>
<EditarIcon />
</button>
<button
className="btn btn-sm btn-outline-info ms-2"
onClick={() => Compartir(x)}
>
<ShareIcon />
</button>
<button
className="btn btn-sm btn-outline-danger ms-2"
onClick={() => Remover(x)}
>
<RemoveIcon />
</button>
</td>
</tr>
))}
</tbody>
</table>
</> </>
); );
} }

View File

@@ -1,53 +1,50 @@
:root { :root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
font-weight: 400; font-weight: 400;
color-scheme: light dark; color-scheme: light dark;
color: rgba(255, 255, 255, 0.87); color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
h1 { h1 {
font-size: 3.2em; font-size: 3.2em;
line-height: 1.1; line-height: 1.1;
} }
button { button {
border-radius: 8px; border-radius: 8px;
border: 1px solid transparent; border: 1px solid transparent;
padding: 0.6em 1.2em; padding: 0.6em 1.2em;
font-size: 1em; font-size: 1em;
font-weight: 500; font-weight: 500;
font-family: inherit; font-family: inherit;
background-color: #1a1a1a; background-color: #1a1a1a;
cursor: pointer; cursor: pointer;
transition: border-color 0.25s; transition: border-color 0.25s;
} }
button:hover { button:hover {
border-color: #646cff; border-color: #646cff;
} }
button:focus, button:focus,
button:focus-visible { button:focus-visible {
outline: 4px auto -webkit-focus-ring-color; outline: 4px auto -webkit-focus-ring-color;
} }
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
:root { :root {
color: #213547; color: #213547;
background-color: #ffffff; background-color: #ffffff;
} }
a:hover { a:hover {
color: #747bff; color: #747bff;
} }
button { button {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
} }