mirror of
https://github.com/trafficlunar/jellyfin-spicetify.git
synced 2026-06-13 19:07:06 +00:00
feat: loading indicators, copy quick connect code
Also adds .editorconfig, formats most files, and remove archive false on workflow until nightly.link adds support for it
This commit is contained in:
parent
eee96c84af
commit
71c62c3f07
15 changed files with 2878 additions and 2817 deletions
11
.editorconfig
Normal file
11
.editorconfig
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
max_line_length = 160
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
|
@ -13,4 +13,4 @@ jobs:
|
|||
with:
|
||||
name: jellyfin-spicetify
|
||||
path: dist/jellyfin-spicetify.js
|
||||
archive: false
|
||||
# archive: false
|
||||
|
|
|
|||
|
|
@ -18,6 +18,6 @@ WIP: A Spicetify extension to integrate your Jellyfin music library into Spotify
|
|||
|
||||
The following are current limitations with the extension. They are not impossible to implement, but are rather time-consuming or require fragile solutions.
|
||||
|
||||
- Non-Spotify tracks
|
||||
### Non-Spotify tracks
|
||||
|
||||
Tracks that don't exist on Spotify can't be included in playlists, queue, etc. They can only be accessed via search and don't show up on the player interface.
|
||||
|
|
|
|||
3
build.ts
3
build.ts
|
|
@ -59,6 +59,8 @@ const options: BuildOptions = {
|
|||
|
||||
copyFileSync("./dist/jellyfin-spicetify.js", path);
|
||||
}
|
||||
|
||||
console.log("Built!");
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
@ -67,6 +69,7 @@ const options: BuildOptions = {
|
|||
|
||||
if (isWatch) {
|
||||
const ctx = await context(options);
|
||||
console.log("Watching...");
|
||||
await ctx.watch();
|
||||
} else {
|
||||
await build(options);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "bun build.ts & spicetify apply",
|
||||
"build": "bun build.ts && spicetify apply",
|
||||
"build-local": "bun build.ts --local",
|
||||
"watch": "bun build.ts --watch & spicetify watch -le"
|
||||
"watch": "bun build.ts --watch && spicetify watch -le"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,26 @@ import styles from "./styles.module.css";
|
|||
|
||||
type View = "url" | "password" | "quick-connect" | "settings";
|
||||
|
||||
const LoadingIndicatorButton = ({ children, onClick, isLoading }: { children: React.ReactNode; onClick: () => void; isLoading: boolean }) => (
|
||||
<button onClick={onClick} className={styles.button}>
|
||||
{isLoading && (
|
||||
<svg width="17" height="18" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor" d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||
>
|
||||
<animateTransform attributeName="transform" type="rotate" dur="0.75s" values="0 12 12;360 12 12" repeatCount="indefinite" />
|
||||
</path>
|
||||
</svg>
|
||||
)}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
export default function SettingsModal() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const [url, setUrl] = useState(Spicetify.LocalStorage.get("jellyfin-url") || "");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
|
@ -16,28 +35,33 @@ export default function SettingsModal() {
|
|||
const [quickConnectCode, setQuickConnectCode] = useState("");
|
||||
|
||||
const createApi = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
const servers = await jellyfin.sdk.discovery.getRecommendedServerCandidates(url);
|
||||
const best = jellyfin.sdk.discovery.findBestServer(servers);
|
||||
if (!best) {
|
||||
Spicetify.showNotification("Failed to connect to server!", true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
const api = jellyfin.sdk.createApi(best.address);
|
||||
|
||||
Spicetify.LocalStorage.set("jellyfin-url", url);
|
||||
|
||||
jellyfin.setApi(api);
|
||||
|
||||
setView("password");
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const login = async () => {
|
||||
if (!jellyfin.api) return;
|
||||
const userApi = getUserApi(jellyfin.api);
|
||||
setIsLoading(true);
|
||||
|
||||
const userApi = getUserApi(jellyfin.api);
|
||||
const auth = await userApi.authenticateUserByName({ authenticateUserByName: { Username: username, Pw: password } });
|
||||
|
||||
if (!auth.data.AccessToken) {
|
||||
Spicetify.showNotification("Failed to login!", true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -48,9 +72,11 @@ export default function SettingsModal() {
|
|||
if (user.data.Id) jellyfin.setUser(user.data.Id);
|
||||
|
||||
setView("settings");
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
const logout = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
Spicetify.LocalStorage.remove("jellyfin-token");
|
||||
setView("url");
|
||||
};
|
||||
|
|
@ -164,15 +190,16 @@ export default function SettingsModal() {
|
|||
</div>
|
||||
|
||||
<hr className={styles.hr} />
|
||||
<button onClick={createApi} className={styles.button}>
|
||||
<LoadingIndicatorButton onClick={createApi} isLoading={isLoading}>
|
||||
Next
|
||||
</button>
|
||||
</LoadingIndicatorButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.modal}>
|
||||
{view === "quick-connect" ? (
|
||||
<>
|
||||
<div className={styles.inputContainer}>
|
||||
<label htmlFor="code">Code</label>
|
||||
|
||||
|
|
@ -184,6 +211,17 @@ export default function SettingsModal() {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(quickConnectCode);
|
||||
Spicetify.showNotification("Copied!");
|
||||
}}
|
||||
className={`${styles.button} ${styles.secondary}`}
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className={styles.inputContainer}>
|
||||
|
|
@ -196,17 +234,23 @@ export default function SettingsModal() {
|
|||
<input id="password" type="password" placeholder="Enter password..." value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<button onClick={login} className={styles.button}>
|
||||
<LoadingIndicatorButton onClick={login} isLoading={isLoading}>
|
||||
Log in
|
||||
</button>
|
||||
</LoadingIndicatorButton>
|
||||
</>
|
||||
)}
|
||||
|
||||
<hr className={styles.hr} />
|
||||
<button onClick={() => setView((prev) => (prev === "password" ? "quick-connect" : "password"))} className={`${styles.quickConnect} ${styles.button}`}>
|
||||
<button onClick={() => setView((prev) => (prev === "password" ? "quick-connect" : "password"))} className={`${styles.button} ${styles.secondary}`}>
|
||||
{view === "password" ? "Quick Connect" : "Username/Password"}
|
||||
</button>
|
||||
<button onClick={() => setView("url")} className={styles.button}>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setView("url");
|
||||
}}
|
||||
className={styles.button}
|
||||
>
|
||||
Change URL
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@
|
|||
outline: none;
|
||||
cursor: pointer;
|
||||
width: fit-content;
|
||||
border-radius: 0.35rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background-color: var(--spice-main-elevated);
|
||||
}
|
||||
|
||||
.hr {
|
||||
|
|
@ -46,6 +54,7 @@
|
|||
padding: 0.5rem 0.6rem;
|
||||
font-size: 0.95rem;
|
||||
transition: 200ms border-color;
|
||||
border-radius: 0.35rem;
|
||||
}
|
||||
|
||||
.inputContainer input:focus {
|
||||
|
|
@ -65,10 +74,6 @@
|
|||
color: var(--spice-subtext);
|
||||
}
|
||||
|
||||
.quick_connect {
|
||||
background-color: var(--spice-main-elevated);
|
||||
}
|
||||
|
||||
.quickConnectWrapper {
|
||||
position: relative;
|
||||
display: grid;
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@
|
|||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.*"
|
||||
]
|
||||
"include": ["./src/**/*.*"],
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue