diff --git a/README.md b/README.md index b4d7241..0b274a2 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,9 @@ Contributions are always welcome! Please read the [Contributing Guide](CONTRIBUT ## 💬 **Join the Community** -Join our [Discord server](https://discord.gg/CrAbAYMGCe/) to connect with other users and developers. +Join our [Discord server](https://discord.gg/CrAbAYMGCe) to connect with other users and developers. -Discord +Discord --- diff --git a/main/background.ts b/main/background.ts index 26d98b3..c95cef5 100644 --- a/main/background.ts +++ b/main/background.ts @@ -18,17 +18,18 @@ import { removeSongFromPlaylist, searchDB, updatePlaylist, + updateSettings, } from "./helpers/db/connectDB"; import { initDatabase } from "./helpers/db/createDB"; import { parseFile, selectCover } from "music-metadata"; +import fs from "fs"; const isProd = process.env.NODE_ENV === "production"; -process.traceProcessWarnings = true; if (isProd) { serve({ directory: "app" }); } else { - app.setPath("userData", `${app.getPath("userData")} (development)`); + app.setPath("userData", `${app.getPath("userData")}`); } let settings: any; @@ -38,8 +39,8 @@ let settings: any; initDatabase(); settings = await getSettings(); - if (settings[0]) { - await initializeData(settings[0].musicFolder); + if (settings) { + await initializeData(settings.musicFolder); } })(); @@ -47,8 +48,8 @@ let settings: any; await app.whenReady(); // @hiaaryan: Using Depreciated API [Seeking Not Supported with Net] - protocol.registerFileProtocol("music", (request, callback) => { - callback(request.url.slice("music://".length)); + protocol.registerFileProtocol("wora", (request, callback) => { + callback({ path: request.url.replace("wora://", "") }); }); const mainWindow = createWindow("main", { @@ -56,6 +57,7 @@ let settings: any; height: 900, titleBarStyle: "hidden", trafficLightPosition: { x: 20, y: 15 }, + transparent: true, icon: path.join(__dirname, "resources/icon.icns"), webPreferences: { preload: path.join(__dirname, "preload.js"), @@ -66,7 +68,7 @@ let settings: any; mainWindow.setMinimumSize(1500, 900); mainWindow.setTitle("Wora"); - if (settings[0]) { + if (settings) { if (isProd) { await mainWindow.loadURL("app://./home"); } else { @@ -204,6 +206,30 @@ ipcMain.handle("removeSongFromPlaylist", async (_, data: any) => { return remove; }); +ipcMain.handle("getSettings", async () => { + const settings = await getSettings(); + return settings; +}); + +ipcMain.handle("updateSettings", async (_, data: any) => { + const settings = await updateSettings(data); + return settings; +}); + +ipcMain.handle("uploadProfilePicture", async (_, file) => { + const uploadsDir = path.join(app.getPath("userData"), "uploads"); + if (!fs.existsSync(uploadsDir)) { + fs.mkdirSync(uploadsDir, { recursive: true }); + } + + const fileName = `profile_${Date.now()}${path.extname(file.name)}`; + const filePath = path.join(uploadsDir, fileName); + + fs.writeFileSync(filePath, Buffer.from(file.data)); + + return filePath; +}); + app.on("window-all-closed", () => { app.quit(); }); diff --git a/main/helpers/db/connectDB.ts b/main/helpers/db/connectDB.ts index 931b2b1..e99e0f9 100644 --- a/main/helpers/db/connectDB.ts +++ b/main/helpers/db/connectDB.ts @@ -12,7 +12,17 @@ const db: BetterSQLite3Database = drizzle(sqlite, { }); export const getSettings = async () => { - return db.select().from(settings).limit(1); + const throwSettings = await db.select().from(settings).limit(1); + return throwSettings[0]; +}; + +export const updateSettings = async (data: any) => { + const update = await db.update(settings).set({ + name: data.name, + profilePicture: data.profilePicture, + }); + + return true; }; export const getSongs = async () => { @@ -153,7 +163,7 @@ export const createPlaylist = async (data: any) => { if (data.description) { description = data.description; } else { - description = "An epic playlist created by you ✌️"; + description = "An epic playlist created by you."; } if (data.coverArt) { @@ -178,7 +188,7 @@ export const updatePlaylist = async (data: any) => { if (data.data.description) { description = data.data.description; } else { - description = "An epic playlist created by you ✌️"; + description = "An epic playlist created by you."; } if (data.coverArt) { @@ -390,14 +400,26 @@ export const initializeData = async (musicFolder: string) => { await db.insert(playlists).values({ name: "Favourites", coverArt: "/favouritesCoverArt.png", - description: "Songs liked by you ❤️", + description: "Songs liked by you.", }); } - await db.delete(settings); - await db.insert(settings).values({ - fullName: "Aaryan Kapoor", - profilePicture: "/ak.jpeg", - musicFolder, - }); + const getSettings = await db + .select() + .from(settings) + .where(eq(settings.id, 1)); + + if (getSettings[0]) { + await db + .update(settings) + .set({ + musicFolder, + }) + .where(eq(settings.id, 1)); + return; + } else { + await db.insert(settings).values({ + musicFolder, + }); + } }; diff --git a/main/helpers/db/createDB.ts b/main/helpers/db/createDB.ts index 36ead79..0792483 100644 --- a/main/helpers/db/createDB.ts +++ b/main/helpers/db/createDB.ts @@ -10,7 +10,7 @@ export const initDatabase = () => { sqlite.exec(` CREATE TABLE IF NOT EXISTS settings ( id INTEGER PRIMARY KEY, - fullName TEXT, + name TEXT, profilePicture TEXT, musicFolder TEXT ); diff --git a/main/helpers/db/schema.ts b/main/helpers/db/schema.ts index 1d01883..739700d 100644 --- a/main/helpers/db/schema.ts +++ b/main/helpers/db/schema.ts @@ -3,7 +3,7 @@ import { relations } from "drizzle-orm"; export const settings = sqliteTable("settings", { id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), - fullName: text("fullName"), + name: text("name"), profilePicture: text("profilePicture"), musicFolder: text("musicFolder"), }); diff --git a/renderer/.DS_Store b/renderer/.DS_Store index b76ad49..3d90192 100644 Binary files a/renderer/.DS_Store and b/renderer/.DS_Store differ diff --git a/renderer/components/ui/navbar.tsx b/renderer/components/ui/navbar.tsx index 558068c..f6e9c7f 100644 --- a/renderer/components/ui/navbar.tsx +++ b/renderer/components/ui/navbar.tsx @@ -11,9 +11,9 @@ import { TooltipContent, TooltipProvider, TooltipTrigger, -} from "../ui/tooltip"; -import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; -import { Button } from "../ui/button"; +} from "@/components/ui/tooltip"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; import { Command, CommandDialog, @@ -21,21 +21,21 @@ import { CommandInput, CommandItem, CommandList, -} from "../ui/command"; +} from "@/components/ui/command"; import { useEffect, useState } from "react"; import Link from "next/link"; import Image from "next/image"; import { useRouter } from "next/router"; import { usePlayer } from "@/context/playerContext"; -import Spinner from "./spinner"; +import Spinner from "@/components/ui/spinner"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, -} from "./dialog"; -import { Input } from "./input"; +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; import { Form, FormControl, @@ -54,11 +54,17 @@ const formSchema = z.object({ description: z.string().optional(), }); +type Settings = { + name: string; + profilePicture: string; +}; + const Navbar = () => { const router = useRouter(); const [open, setOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false); const [searchResults, setSearchResults] = useState([]); + const [settings, setSettings] = useState(null); const [search, setSearch] = useState(""); const [loading, setLoading] = useState(false); const { setQueueAndPlay } = usePlayer(); @@ -113,6 +119,8 @@ const Navbar = () => { router.push(`/albums/${item.id}`); } else if (item.type === "Song") { setQueueAndPlay([item], 0); + } else if (item.type === "Playlist") { + router.push(`/playlists/${item.id}`); } setOpen(false); }; @@ -133,6 +141,12 @@ const Navbar = () => { resolver: zodResolver(formSchema), }); + useEffect(() => { + window.ipc.invoke("getSettings").then((response) => { + setSettings(response); + }); + }, []); + return (
@@ -142,13 +156,14 @@ const Navbar = () => { - - AK + -

Aaryan Kapoor

+

{settings && settings.name ? settings.name : "Wora User"}

@@ -303,7 +318,8 @@ const Navbar = () => { onSelect={() => handleItemClick(item)} >
- {item.type === "Album" && ( + {(item.type === "Playlist" || + item.type === "Album") && (
{item.name}
diff --git a/renderer/components/ui/player.tsx b/renderer/components/ui/player.tsx index 4dc9d97..d99ede8 100644 --- a/renderer/components/ui/player.tsx +++ b/renderer/components/ui/player.tsx @@ -77,7 +77,7 @@ function Player() { if (!song?.filePath) return; const sound = new Howl({ - src: ["music://" + song?.filePath], + src: ["wora://" + song?.filePath], format: [song?.filePath.split(".").pop()], html5: true, autoplay: true, @@ -588,10 +588,10 @@ function Player() { />
-
-

- Name:{" "} - {metadata && metadata.common.title} +

+

+ → {metadata && metadata.common.title} [ + {metadata && metadata.format.codec}]

Artist:{" "} diff --git a/renderer/pages/_app.tsx b/renderer/pages/_app.tsx index f741c61..e817fa2 100644 --- a/renderer/pages/_app.tsx +++ b/renderer/pages/_app.tsx @@ -5,6 +5,7 @@ import Player from "@/components/ui/player"; import Head from "next/head"; import { PlayerProvider } from "@/context/playerContext"; import { useRouter } from "next/router"; +import { Toaster } from "@/components/ui/sonner"; export default function App({ Component, pageProps }) { const router = useRouter(); @@ -18,7 +19,7 @@ export default function App({ Component, pageProps }) { } return ( -

+
Wora @@ -26,6 +27,7 @@ export default function App({ Component, pageProps }) {
+
diff --git a/renderer/pages/albums/[slug].tsx b/renderer/pages/albums/[slug].tsx index ce825e6..f9917ce 100644 --- a/renderer/pages/albums/[slug].tsx +++ b/renderer/pages/albums/[slug].tsx @@ -24,7 +24,6 @@ import { ContextMenuSubTrigger, ContextMenuTrigger, } from "@/components/ui/context-menu"; -import { Toaster } from "@/components/ui/sonner"; import { toast } from "sonner"; type Song = { @@ -109,7 +108,6 @@ export default function Album() { return ( -
{album -
{playlist && playlist.id === 1 ? (
diff --git a/renderer/pages/settings.tsx b/renderer/pages/settings.tsx index 9f69f6e..7c1b6bf 100644 --- a/renderer/pages/settings.tsx +++ b/renderer/pages/settings.tsx @@ -1,20 +1,260 @@ -import React from "react"; +import React, { useEffect, useState, useRef } from "react"; import { ScrollArea } from "@/components/ui/scroll-area"; +import Image from "next/image"; +import { + IconArrowRight, + IconCheck, + IconHeart, + IconX, +} from "@tabler/icons-react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { Input } from "@/components/ui/input"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; +import { Button } from "@/components/ui/button"; +import Spinner from "@/components/ui/spinner"; +import { Avatar, AvatarImage } from "@/components/ui/avatar"; +import { toast } from "sonner"; + +const formSchema = z.object({ + name: z.string().min(2, { + message: "Username must be at least 2 characters long.", + }), + profilePicture: z.any().optional(), +}); + +type Settings = { + name: string; + profilePicture: string; +}; export default function Settings() { + const [settings, setSettings] = useState(null); + const [loading, setLoading] = useState(false); + const [musicLoading, setMusicLoading] = useState(false); + + useEffect(() => { + window.ipc.invoke("getSettings").then((response) => { + setSettings(response); + }); + }, []); + + const updateSettings = async (data: z.infer) => { + setLoading(true); + + let profilePicturePath = settings?.profilePicture; + + if ( + data.profilePicture && + data.profilePicture instanceof FileList && + data.profilePicture.length > 0 + ) { + const file = data.profilePicture[0]; + const fileData = await file.arrayBuffer(); + try { + profilePicturePath = await window.ipc.invoke("uploadProfilePicture", { + name: file.name, + data: Array.from(new Uint8Array(fileData)), + }); + } catch (error) { + console.error("Error uploading profile picture:", error); + toast( +
+ + Failed to upload profile picture. Using existing picture. +
, + ); + // Fallback to the original profile picture + profilePicturePath = settings?.profilePicture; + } + } else { + // No new file selected, use the existing profile picture + profilePicturePath = settings?.profilePicture; + } + + const updatedData = { + name: data.name, + profilePicture: profilePicturePath, + }; + + await window.ipc.invoke("updateSettings", updatedData).then((response) => { + if (response) { + setLoading(false); + setSettings((prevSettings) => ({ ...prevSettings, ...updatedData })); + toast( +
+ + Your settings are updated. +
, + ); + } + }); + }; + + const form = useForm>({ + resolver: zodResolver(formSchema), + }); + + useEffect(() => { + console.log("settings", settings); + if (settings) { + form.reset({ + name: settings.name, + profilePicture: settings.profilePicture, + }); + } + }, [settings]); + + const updateMusicFolder = () => { + setMusicLoading(true); + window.ipc + .invoke("setMusicFolder", true) + .then((response) => { + setMusicLoading(false); + if (response) return; + toast( +
+ + Your music folder updated. +
, + ); + }) + .catch(() => setMusicLoading(false)); + }; + return (
Settings
-
- Hey Aaryan! Ready for a Jam Session? -
+
You're on your own here.
-
-
-
+
+
+
+
+ +
+ + + +
+
+
+ ( + + + + + + + )} + /> + { + const fileInputRef = useRef(null); + return ( + + + { + const files = e.target.files; + if (files && files.length > 0) { + onChange(files); + } + }} + ref={fileInputRef} + {...rest} + /> + + + + ); + }} + /> +
+ +
+
+ +
+
+
+
+ Logo +
+
+ Made with + + by hiaaryan. +
+
+
+
+
+
+ +
+
+
+
+ Logo +
+
+ Made with + + by hiaaryan. +
+
+
+
diff --git a/renderer/public/.DS_Store b/renderer/public/.DS_Store index 53fda93..abfafa2 100644 Binary files a/renderer/public/.DS_Store and b/renderer/public/.DS_Store differ diff --git a/renderer/public/ak.jpeg b/renderer/public/ak.jpeg deleted file mode 100644 index df5bbb7..0000000 Binary files a/renderer/public/ak.jpeg and /dev/null differ diff --git a/renderer/public/userPicture.png b/renderer/public/userPicture.png new file mode 100644 index 0000000..a909cf2 Binary files /dev/null and b/renderer/public/userPicture.png differ