import/export data, nav layout change, auth state improvement, css corrections and improvements

This commit is contained in:
2025-03-17 21:26:28 -07:00
parent 098706a70e
commit e5e0751e8f
19 changed files with 184 additions and 43 deletions

View File

@@ -30,11 +30,3 @@
display: flex;
flex-direction: row;
}
.divider {
display: block;
height: 1px;
width: 100%;
margin: var(--mb-2) auto;
border-top: 1px solid white;
}

View File

@@ -249,7 +249,10 @@ const Graph = (): React.ReactNode => {
showErrorToastNotification("Can't delete playlists!");
return false;
}
if (!edges[0]) throw new ReferenceError("no edge selected");
if (!edges[0]) {
showWarnToastNotification("Select a link!");
return false;
}
console.debug(
`deleted connection: ${edges[0].source} -> ${edges[0].target}`
);
@@ -598,7 +601,7 @@ const Graph = (): React.ReactNode => {
</span>
Backfill Chain
</Button>
<hr className={styles.divider} />
<hr className="divider" />
<Button disabled={loading} onClickMethod={pruneLink}>
<IoArrowDownOutline size={36} />
Prune Link
@@ -610,7 +613,7 @@ const Graph = (): React.ReactNode => {
</span>
Prune Chain
</Button>
<hr className={styles.divider} />
<hr className="divider" />
<Button disabled={loading} onClickMethod={() => arrangeLayout("TB")}>
<IoIosGitNetwork size={36} />
Arrange
@@ -623,7 +626,7 @@ const Graph = (): React.ReactNode => {
)}
{isInteractive() ? "Lock" : "Unlock"}
</Button>
<hr className={styles.divider} />
<hr className="divider" />
<Button disabled={loading} onClickMethod={updateUserData}>
<span className={styles.icons}>
<WiCloudRefresh size={36} />

View File

@@ -3,5 +3,4 @@
align-items: center;
justify-content: center;
margin-right: 10vw;
font-size: var(--headingFontSize);
}

View File

@@ -4,7 +4,6 @@
align-items: center;
justify-content: center;
margin-right: 10vw;
font-size: var(--headingFontSize);
}
.app_logo {

View File

@@ -3,5 +3,4 @@
align-items: center;
justify-content: center;
margin-right: 10vw;
font-size: var(--headingFontSize);
}

View File

@@ -3,7 +3,7 @@ import styles from "./Login.module.css";
import { authLoginFullURL } from "../../api/paths.ts";
// auth through backend
const Login = ():React.ReactNode => {
const Login = (): React.ReactNode => {
useEffect(() => {
const timeoutID = setTimeout(() => {
window.open(authLoginFullURL, "_self");
@@ -11,7 +11,11 @@ const Login = ():React.ReactNode => {
return () => clearTimeout(timeoutID);
}, []);
return <div className={styles.login_wrapper}>Logging in to Spotify...</div>;
return (
<div className={styles.login_wrapper}>
<h1>Logging in to Spotify...</h1>
</div>
);
};
export default Login;

View File

@@ -3,5 +3,4 @@
align-items: center;
justify-content: center;
margin-right: 10vw;
font-size: var(--headingFontSize);
}

View File

@@ -2,7 +2,7 @@ import React, { useEffect } from "react";
import styles from "./Logout.module.css";
import { authLogoutFullURL } from "../../api/paths.ts";
const Logout = ():React.ReactNode => {
const Logout = (): React.ReactNode => {
useEffect(() => {
const timeoutID = setTimeout(() => {
window.open(authLogoutFullURL, "_self");
@@ -10,7 +10,11 @@ const Logout = ():React.ReactNode => {
return () => clearTimeout(timeoutID);
}, []);
return <div className={styles.logout_wrapper}>See you soon!</div>;
return (
<div className={styles.logout_wrapper}>
<h1>See you soon!</h1>
</div>
);
};
export default Logout;

View File

@@ -1,7 +1,7 @@
.pnf_wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-right: 10vw;
font-size: var(--headingFontSize);
}

View File

@@ -1,13 +1,19 @@
import React, { useEffect } from "react";
import styles from "./PageNotFound.module.css";
import { showWarnToastNotification } from "../../components/ToastNotification/index.tsx";
import AnimatedSVG from "../../components/AnimatedSVG/index.tsx";
const PageNotFound = ():React.ReactNode => {
const PageNotFound = (): React.ReactNode => {
useEffect(() => {
showWarnToastNotification("Oops!");
}, []);
return <div className={styles.pnf_wrapper}>Page Not Found</div>;
return (
<div className={styles.pnf_wrapper}>
<AnimatedSVG/>
<h1>Page Not Found</h1>
</div>
);
};
export default PageNotFound;

View File

@@ -4,10 +4,45 @@
align-items: center;
justify-content: center;
margin-right: 10vw;
font-size: var(--headingFontSize);
}
.settings_controls {
padding: var(--mb-4);
width: 20vw;
gap: var(--mb-2);
padding: var(--mb-2);
background-color: var(--bgNav);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.settings_dataFile {
color: transparent;
}
.settings_dataFile::-webkit-file-upload-button {
visibility: hidden;
}
.settings_dataFile::before {
content: "Upload";
color: var(--text);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: var(--bg);
/* border: 1px solid #999; */
padding: var(--mb-2) 0;
outline: none;
white-space: nowrap;
user-select: none;
-webkit-user-select: none;
cursor: pointer;
}
.settings_dataFile:hover::before {
border-color: black;
}
.settings_dataFile_selection {
text-align: center;
}

View File

@@ -1,14 +1,82 @@
import React from "react";
import React, { useContext, useState } from "react";
import styles from "./Settings.module.css";
import Button from "../../components/Button/index.tsx";
import { loadExportDataFullURL } from "../../api/paths.ts";
import { useNavigate } from "react-router-dom";
import {
showSuccessToastNotification,
showWarnToastNotification,
} from "../../components/ToastNotification/index.tsx";
import APIWrapper from "../../components/APIWrapper/index.tsx";
import { apiImportGraph } from "../../api/load.ts";
import { RefreshAuthContext } from "../../App.tsx";
import SimpleLoader from "../../components/SimpleLoader/index.tsx";
const Settings = (): React.ReactNode => {
const refreshAuth = useContext(RefreshAuthContext);
const navigate = useNavigate();
const [dataFile, setDataFile] = useState<File>();
const [loading, setLoading] = useState<boolean>(false);
// let backend handle the attachment
const exportGraph = () => {
window.open(loadExportDataFullURL);
return;
};
const dataFileChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return;
let selectedFile = e.target.files[0];
if (!selectedFile) return;
if (selectedFile.type !== "application/json") {
showWarnToastNotification("Must be JSON file!");
return;
}
setDataFile(selectedFile);
};
const importGraph = async () => {
if (!dataFile) {
showWarnToastNotification("Select a file!");
return;
}
setLoading(true);
const resp = await APIWrapper({
apiFn: apiImportGraph,
data: dataFile,
refreshAuth,
});
setLoading(false);
if (resp?.status === 200) {
showSuccessToastNotification(resp.data.message);
}
return;
};
return (
<div className={styles.settings_wrapper}>
{loading && <SimpleLoader />}
<h1>Settings</h1>
<hr className="divider" />
<div className={styles.settings_controls}>
<Button>Export Data</Button>
<Button>Import Data</Button>
<Button disabled={loading} onClickMethod={exportGraph}>
Export Data
</Button>
<input
type="file"
name="dataFile"
onChange={dataFileChangeHandler}
className={styles.settings_dataFile}
/>
<p className={styles.settings_dataFile_selection}>
{dataFile ? `Selected file: ${dataFile.name}` : "No file selected"}
</p>
<Button disabled={loading} onClickMethod={importGraph}>
Import Data
</Button>
<Button disabled={loading} onClickMethod={() => navigate("/logout")}>
Log Out
</Button>
</div>
</div>
);