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>
<meta charset="utf-8" />
<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="description"

View File

@ -1,38 +1,108 @@
.App {
text-align: center;
:root {
--color-bg: #FADF7F;
--color-primary: #D9B26F;
--color-warn: #803B42;
}
.App-logo {
height: 40vmin;
pointer-events: none;
.content {
height: 100%;
width: 150px;
display: flex;
flex-direction: column;
gap: 15px;
padding: 15px;
background-color: var(--color-bg);
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
.song-list {
display: flex;
flex-direction: column;
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;
}
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
.controls {
display: flex;
flex-direction: column;
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;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
gap: 5px;
input {
max-width: 100%;
}
}
.entry {
position: relative;
padding: 15px;
background-color: var(--color-primary);
outline: inherit;
border: none;
.edit {
position: absolute;
right: 12px;
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 './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 (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<div className="content">
<div className="controls">
<div className="buttons">
<button className={addNew === null ? "plus": "cross"} onClick={() => { if (addNew) { setAddNew(null); } else { setAddNew(["", "", ""]); }}} ><div></div></button>
<button className={addNew === null ? "gone" : "done"} onClick={() => {
if (addNew === null) return;
setStore((store) => {
setAddNew(null);
return [...(store??[]), addNew];
});
}}></button>
</div>
{ addNew !== null ?
<div className={"add-song-menu"}>
<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>
);
}