Initial commit
This commit is contained in:
parent
cb7566922f
commit
e77a8821e0
@ -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"
|
||||
|
126
src/App.css
126
src/App.css
@ -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;
|
||||
}
|
||||
}
|
||||
|
104
src/App.tsx
104
src/App.tsx
@ -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>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user