mirror of
https://github.com/20kaushik02/spotify-manager-web.git
synced 2025-12-06 06:14:07 +00:00
ready for initial launch
styling fixes, info pages, screencaps,
This commit is contained in:
parent
e5e0751e8f
commit
c135dda6d0
BIN
public/landing-gif-2c.mp4
Normal file
BIN
public/landing-gif-2c.mp4
Normal file
Binary file not shown.
BIN
public/landing-gifc.mp4
Normal file
BIN
public/landing-gifc.mp4
Normal file
Binary file not shown.
@ -1,15 +1,16 @@
|
||||
import React from "react";
|
||||
import styles from "./AnimatedSVG.module.css";
|
||||
|
||||
const AnimatedSVG = (): React.ReactNode => {
|
||||
type AnimatedSVGProps = { size?: number };
|
||||
const AnimatedSVG = ({ size = 256 }: AnimatedSVGProps): React.ReactNode => {
|
||||
const stroke = "#fff";
|
||||
return (
|
||||
<div className={styles.svg_wrapper}>
|
||||
{/* width, height and viewBox are necessary */}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="256" // adjust size here
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 512 512"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
a {
|
||||
.base_link {
|
||||
padding: var(--mb-3) var(--mb-1);
|
||||
border-radius: 2%;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -15,5 +12,5 @@ a {
|
||||
|
||||
.inactive_link {
|
||||
background-color: var(--bgLinkInactive);
|
||||
box-shadow: 2px 2px var(--bg);
|
||||
box-shadow: 6px 6px var(--bg);
|
||||
}
|
||||
|
||||
@ -18,7 +18,9 @@ const StyledNavLink = ({
|
||||
return (
|
||||
<NavLink
|
||||
to={path}
|
||||
className={({ isActive }) => (isActive ? activeClass : inactiveClass)}
|
||||
className={({ isActive }) =>
|
||||
`${styles.base_link} ${isActive ? activeClass : inactiveClass}`
|
||||
}
|
||||
>
|
||||
{text}
|
||||
</NavLink>
|
||||
|
||||
@ -69,6 +69,32 @@ ul {
|
||||
h1 {
|
||||
font-size: var(--headingFontSize);
|
||||
}
|
||||
h2 {
|
||||
font-size: calc(0.9 * var(--headingFontSize));
|
||||
}
|
||||
h3 {
|
||||
font-size: calc(0.8 * var(--headingFontSize));
|
||||
}
|
||||
h4 {
|
||||
font-size: calc(0.7 * var(--headingFontSize));
|
||||
}
|
||||
h5 {
|
||||
font-size: calc(0.6 * var(--headingFontSize));
|
||||
}
|
||||
h6 {
|
||||
font-size: calc(0.5 * var(--headingFontSize));
|
||||
}
|
||||
|
||||
a,
|
||||
a:link,
|
||||
a:visited,
|
||||
a:focus,
|
||||
a:hover,
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
|
||||
@ -31,7 +31,7 @@ import styles from "./Graph.module.css";
|
||||
import { IoIosGitNetwork } from "react-icons/io";
|
||||
import { WiCloudRefresh } from "react-icons/wi";
|
||||
import { MdOutlineLock, MdOutlineLockOpen } from "react-icons/md";
|
||||
import { AiFillSpotify } from "react-icons/ai";
|
||||
import { AiFillSpotify, AiOutlineDisconnect } from "react-icons/ai";
|
||||
import { GiFamilyTree } from "react-icons/gi";
|
||||
import { IoArrowDownOutline, IoArrowUpOutline } from "react-icons/io5";
|
||||
|
||||
@ -241,6 +241,13 @@ const Graph = (): React.ReactNode => {
|
||||
[setLinkEdges, refreshAuth]
|
||||
);
|
||||
|
||||
// manually triggering edge removal
|
||||
const removeSelectedEdge = async () => {
|
||||
await flowInstance.deleteElements({
|
||||
edges: linkEdges.filter((ed) => ed.id === selectedEdgeID),
|
||||
});
|
||||
};
|
||||
|
||||
// remove edge
|
||||
const onFlowBeforeDelete: OnBeforeDelete = useCallback(
|
||||
async ({ nodes, edges }) => {
|
||||
@ -588,8 +595,18 @@ const Graph = (): React.ReactNode => {
|
||||
/>
|
||||
<Background variant={BackgroundVariant.Dots} gap={36} size={3} />
|
||||
<Panel position="top-right">{loading && <SimpleLoader />}</Panel>
|
||||
{selectedEdgeID !== "" && (
|
||||
<Panel position="top-left">
|
||||
<Button onClickMethod={removeSelectedEdge}>
|
||||
<AiOutlineDisconnect size={36} />
|
||||
Delete Link
|
||||
</Button>
|
||||
</Panel>
|
||||
)}
|
||||
</ReactFlow>
|
||||
<div className={`${styles.operations_wrapper} custom_scrollbar`}>
|
||||
{linkEdges.length > 0 ? (
|
||||
<>
|
||||
<Button disabled={loading} onClickMethod={backfillLink}>
|
||||
<IoArrowUpOutline size={36} />
|
||||
Backfill Link
|
||||
@ -609,12 +626,18 @@ const Graph = (): React.ReactNode => {
|
||||
<Button disabled={loading} onClickMethod={pruneChain}>
|
||||
<span>
|
||||
<IoArrowDownOutline size={24} />
|
||||
<GiFamilyTree size={24} style={{ transform: "rotate(180deg)" }} />
|
||||
<GiFamilyTree
|
||||
size={24}
|
||||
style={{ transform: "rotate(180deg)" }}
|
||||
/>
|
||||
</span>
|
||||
Prune Chain
|
||||
</Button>
|
||||
<hr className="divider" />
|
||||
<Button disabled={loading} onClickMethod={() => arrangeLayout("TB")}>
|
||||
<Button
|
||||
disabled={loading}
|
||||
onClickMethod={() => arrangeLayout("TB")}
|
||||
>
|
||||
<IoIosGitNetwork size={36} />
|
||||
Arrange
|
||||
</Button>
|
||||
@ -627,6 +650,8 @@ const Graph = (): React.ReactNode => {
|
||||
{isInteractive() ? "Lock" : "Unlock"}
|
||||
</Button>
|
||||
<hr className="divider" />
|
||||
</>
|
||||
) : null}
|
||||
<Button disabled={loading} onClickMethod={updateUserData}>
|
||||
<span className={styles.icons}>
|
||||
<WiCloudRefresh size={36} />
|
||||
|
||||
@ -1,6 +1,28 @@
|
||||
.htu_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 10vw;
|
||||
gap: var(--mb-1);
|
||||
padding: var(--mb-6);
|
||||
}
|
||||
|
||||
.htu_content_wrapper {
|
||||
word-wrap: break-word;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.htu_content_wrapper > ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--mb-2);
|
||||
list-style-type: none;
|
||||
}
|
||||
.htu_content_wrapper ul ul {
|
||||
list-style-type: "- ";
|
||||
}
|
||||
.htu_content_wrapper ul ol,
|
||||
.htu_content_wrapper ul ul {
|
||||
margin-left: var(--mb-4);
|
||||
}
|
||||
|
||||
@ -4,6 +4,122 @@ const HowToUse = (): React.ReactNode => {
|
||||
return (
|
||||
<div className={styles.htu_wrapper}>
|
||||
<h1>How To Use?</h1>
|
||||
<div className={styles.htu_content_wrapper}>
|
||||
<ul>
|
||||
<li>
|
||||
<h6>Step 1: Sync your playlists</h6>
|
||||
<ul>
|
||||
<li>
|
||||
In the{" "}
|
||||
<a href="/graph">
|
||||
<u>graph</u>
|
||||
</a>{" "}
|
||||
manager, click 'Sync Spotify' to load your playlists into the
|
||||
app. This pulls your latest Spotify playlists into the
|
||||
application.
|
||||
</li>
|
||||
<li>
|
||||
💡 Reminder: If you create or delete playlists later, you’ll
|
||||
need to sync again to update your data (Might add auto-sync
|
||||
later)
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h6>Step 2: Build Your Graph</h6>
|
||||
<ul>
|
||||
<li>
|
||||
Click and drag from one playlist’s bottom handle to another’s
|
||||
top handle to create a link
|
||||
<video height={320} width={320} autoPlay loop muted playsInline>
|
||||
<source
|
||||
src={`${process.env["PUBLIC_URL"]}/landing-gifc.mp4`}
|
||||
type="video/mp4"
|
||||
/>
|
||||
</video>
|
||||
</li>
|
||||
<li>
|
||||
Click on a link and hit the Delete key or the 'Delete Link'
|
||||
button to remove it.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h6>Step 3: Let The Music Flow!</h6>
|
||||
<ul>
|
||||
Once links exist, you can:
|
||||
<li>
|
||||
✅ Backfill a Link – Add missing tracks from one playlist to
|
||||
another.
|
||||
</li>
|
||||
<li>
|
||||
✅ Backfill a Chain – Backfill iteratively across multiple
|
||||
connected playlists.
|
||||
</li>
|
||||
<video height={320} width={480} autoPlay loop muted playsInline>
|
||||
<source
|
||||
src={`${process.env["PUBLIC_URL"]}/landing-gif-2c.mp4`}
|
||||
type="video/mp4"
|
||||
/>
|
||||
</video>
|
||||
<li>
|
||||
✅ Prune a Link – Remove excess tracks from the source playlist.
|
||||
</li>
|
||||
<li>
|
||||
✅ Prune a Chain – Do the same, but across a whole sequence of
|
||||
links.
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h6>What is this for?</h6>
|
||||
<ul>
|
||||
<li>
|
||||
I like to organize my own playlists as subsets/supersets. For
|
||||
example: I have an 'all' playlist, that has every song I listen
|
||||
to. Then I have a playlist for each genre. I have a playlist for
|
||||
soundtracks of certain shows or games. Some I make for my
|
||||
friends, and so on.
|
||||
</li>
|
||||
<li>
|
||||
Then there are playlists made by others, strangers and friends
|
||||
alike, that I save to my library, regularly checking for new
|
||||
additions.
|
||||
</li>
|
||||
<li>
|
||||
This application is to:
|
||||
<ol>
|
||||
<li>
|
||||
make sense of the growing chaos that is a music aficionado's
|
||||
Spotify library - the graph helps visualize connections, and
|
||||
makes music collection/maintenance easier
|
||||
</li>
|
||||
<li>
|
||||
automate the process - the REST API backend can be cURLed to
|
||||
achieve this
|
||||
</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<h6>For more:</h6>
|
||||
<ul>
|
||||
Check it out on GitHub:
|
||||
<li>
|
||||
<a href="https://github.com/20kaushik02/spotify-manager-web">
|
||||
<u>The front-end - ReactJS (ReactFlow)</u>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/20kaushik02/spotify-manager">
|
||||
<u>The REST API - ExpressJS</u>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,12 +1,35 @@
|
||||
.app_header {
|
||||
.landing_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 10vw;
|
||||
gap: var(--mb-2);
|
||||
}
|
||||
|
||||
.app_logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
.landing_header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.landing_content_wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--mb-2);
|
||||
}
|
||||
|
||||
.landing_content {
|
||||
text-align: justify;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.landing_links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { Link, useSearchParams } from "react-router-dom";
|
||||
import styles from "./Landing.module.css";
|
||||
import {
|
||||
showInfoToastNotification,
|
||||
showSuccessToastNotification,
|
||||
} from "../../components/ToastNotification/index.tsx";
|
||||
import AnimatedSVG from "../../components/AnimatedSVG/index.tsx";
|
||||
// import AnimatedSVG from "../../components/AnimatedSVG/index.tsx";
|
||||
import { FaGithub } from "react-icons/fa";
|
||||
|
||||
const Landing = (): React.ReactNode => {
|
||||
const [searchParams] = useSearchParams();
|
||||
@ -17,17 +18,41 @@ const Landing = (): React.ReactNode => {
|
||||
}
|
||||
}, [searchParams]);
|
||||
return (
|
||||
<>
|
||||
<header className={styles.app_header}>
|
||||
<AnimatedSVG />
|
||||
<h1>organize your Spotify playlists as a graph</h1>
|
||||
<div className={styles.landing_wrapper}>
|
||||
<header className={styles.landing_header}>
|
||||
{/* <AnimatedSVG size={192} /> */}
|
||||
<div className={styles.landing_content_wrapper}>
|
||||
<video height={320} width={320} autoPlay loop muted playsInline>
|
||||
<source
|
||||
src={`${process.env["PUBLIC_URL"]}/landing-gifc.mp4`}
|
||||
type="video/mp4"
|
||||
/>
|
||||
</video>
|
||||
<video height={320} width={480} autoPlay loop muted playsInline>
|
||||
<source
|
||||
src={`${process.env["PUBLIC_URL"]}/landing-gif-2c.mp4`}
|
||||
type="video/mp4"
|
||||
/>
|
||||
</video>
|
||||
</div>
|
||||
<h2>organize your Spotify playlists as a graph</h2>
|
||||
</header>
|
||||
<ul>
|
||||
<li>DAG graph of your playlists</li>
|
||||
<li>Link them to sync tracks</li>
|
||||
<li>Periodic syncing</li>
|
||||
<div className={styles.landing_content_wrapper}>
|
||||
<ul className={styles.landing_content}>
|
||||
<li>📊 Visualize your playlists as a connected graph</li>
|
||||
<li>🔗 Link playlists together to keep them in sync</li>
|
||||
<li>🔄 Fill songs from linked playlists</li>
|
||||
<li>✂️ Prune songs that don't belong</li>
|
||||
<li>🔜 More features on the way!</li>
|
||||
</ul>
|
||||
</>
|
||||
</div>
|
||||
<div className={styles.landing_links}>
|
||||
Check it out -
|
||||
<Link to="https://github.com/20kaushik02/spotify-manager-web">
|
||||
<FaGithub size={36} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
.settings_dataFile::before {
|
||||
content: "Upload";
|
||||
content: "Select File";
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user