Initial commit

This commit is contained in:
Dominic Zimmer 2024-04-28 13:01:14 +02:00
parent cb7566922f
commit e77a8821e0
3 changed files with 186 additions and 46 deletions

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=1920, height=1080 initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"

View File

@ -1,38 +1,108 @@
.App { :root {
text-align: center; --color-bg: #FADF7F;
--color-primary: #D9B26F;
--color-warn: #803B42;
} }
.App-logo { .content {
height: 40vmin; height: 100%;
pointer-events: none; width: 150px;
display: flex;
flex-direction: column;
gap: 15px;
padding: 15px;
background-color: var(--color-bg);
} }
.song-list {
@media (prefers-reduced-motion: no-preference) { display: flex;
.App-logo { flex-direction: column;
animation: App-logo-spin infinite 20s linear; gap: 5px;
flex: 1 1 100%;
.entry {
display: flex;
gap: 3px;
flex-direction: column;
span {
text-overflow: ellipsis;
overflow: hidden;
text-wrap: nowrap;
}
span:nth-child(1) {
font-size: 14pt;
}
span:nth-child(2) {
font-size: 10pt;
}
} }
} }
.controls {
.App-header { display: flex;
background-color: #282c34; flex-direction: column;
min-height: 100vh; gap: 5px;
.buttons {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
justify-content: space-around;
place-items: center;
button {
transition: transform 400ms ease-in-out;
aspect-ratio: 1;
height: 1.5rem;
grid-column: 1;
grid-row: 1;
}
button > div {
transition: transform 400ms ease-in-out;
}
button.plus {
z-index: 1;
div {
transform: rotate(0deg);
}
}
button.cross {
z-index: 1;
transform: translate(-25vw, 0);
div {
transform: rotate(225deg);
}
}
button.done {
transform: translate(25vw, 0);
z-index: 0;
}
button.gone {
transform: translate(0, 0);
z-index: 0;
}
}
}
.add-song-menu {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; gap: 5px;
font-size: calc(10px + 2vmin); input {
color: white; max-width: 100%;
} }
}
.App-link { .entry {
color: #61dafb; position: relative;
} padding: 15px;
background-color: var(--color-primary);
@keyframes App-logo-spin { outline: inherit;
from { border: none;
transform: rotate(0deg); .edit {
} position: absolute;
to { right: 12px;
transform: rotate(360deg); top: 2px;
font-size: 8pt !important;
}
.delete {
position: absolute;
right: 1px;
top: -6px;
font-size: 15pt !important;
} }
} }

View File

@ -1,24 +1,94 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import logo from './logo.svg'; import logo from './logo.svg';
import './App.css'; import './App.css';
function App() { type Entry =[string, string, string];
const songlib: Entry[] = [
[ "https://tabs.ultimate-guitar.com/tab/sdp/ne-leiche-chords-1869475", "SDP", "Ne Leiche" ],
[ "https://tabs.ultimate-guitar.com/tab/gloria-gaynor/i-will-survive-chords-154172", "Gloria Gaynor", "I will survive" ],
[ "https://tabs.ultimate-guitar.com/tab/sting/englishman-in-new-york-chords-2220", "Sting", "Englishman in New York"],
];
const App: React.FC = () => {
const [store, setStore] = useState<null | Entry[]>(null);
const [addNew, setAddNew] = useState<null|Entry>(null);
useEffect(() => {
if (store !== null) {
localStorage.setItem("jamsite", JSON.stringify(store));
return;
}
const entry = localStorage.getItem("jamsite");
if (!entry) {
setStore([]);
return;
}
const reStored = JSON.parse(entry);
setStore(reStored);
}, [store])
const sortedStore = [...songlib, ...(store??[]) ].sort((a, b) => a[2].localeCompare(b[2]))
return ( return (
<div className="App"> <div className="content">
<header className="App-header"> <div className="controls">
<img src={logo} className="App-logo" alt="logo" /> <div className="buttons">
<p> <button className={addNew === null ? "plus": "cross"} onClick={() => { if (addNew) { setAddNew(null); } else { setAddNew(["", "", ""]); }}} ><div></div></button>
Edit <code>src/App.tsx</code> and save to reload. <button className={addNew === null ? "gone" : "done"} onClick={() => {
</p> if (addNew === null) return;
<a setStore((store) => {
className="App-link" setAddNew(null);
href="https://reactjs.org" return [...(store??[]), addNew];
target="_blank" });
rel="noopener noreferrer" }}></button>
> </div>
Learn React { addNew !== null ?
</a> <div className={"add-song-menu"}>
</header> <input defaultValue={addNew[0]} onChange={(e) => {
setAddNew((addNew) => {
if (addNew === null) return null;
addNew[0] = e.target.value
return [...addNew];
});
}} placeholder="URL"></input>
<input defaultValue={addNew[1]} onChange={(e) => {
setAddNew((addNew) => {
if (addNew === null) return null;
addNew[1] = e.target.value
return [...addNew];
});
}} placeholder="Artist"></input>
<input defaultValue={addNew[2]} onChange={(e) => {
setAddNew((addNew) => {
if (addNew === null) return null;
addNew[2] = e.target.value
return [...addNew];
});
}} placeholder="Title"></input>
</div>
:null }
</div>
<div className="song-list">
{sortedStore.map(([url, artist, title], index) =>
<button key={index} className="entry" onClick={() => {
window.open(url, "jamsite", "popup")
}}>
<span className="delete" onClick={(event) => {
event.stopPropagation();
setStore((storage) => (storage??[]).filter((element) => JSON.stringify([url, artist, title]) !== JSON.stringify(element)))
}}>x</span>
<span className="edit" onClick={(event) => {
event.stopPropagation();
setStore((storage) => (storage??[]).filter((element) => JSON.stringify([url, artist, title]) !== JSON.stringify(element)))
setAddNew([url, artist, title]);
}}></span>
<span>{title}</span>
<span>{artist}</span>
</button>
)}
</div>
</div> </div>
); );
} }