diff --git a/package.json b/package.json index da22092..d67a12c 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,9 @@ "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.10", "@types/better-sqlite3": "^7.6.12", + "@types/spotify-web-api-node": "^5.0.11", "autoprefixer": "^10.4.20", + "bits-ui": "1.0.0-next.78", "clsx": "^2.1.1", "drizzle-kit": "^0.30.2", "eslint": "^9.18.0", @@ -36,6 +38,7 @@ "prettier-plugin-tailwindcss": "^0.6.10", "svelte": "^5.0.0", "svelte-check": "^4.0.0", + "svelte-dnd-action": "^0.9.54", "tailwind-merge": "^2.6.0", "tailwind-variants": "^0.3.1", "tailwindcss": "^3.4.17", @@ -46,6 +49,9 @@ }, "dependencies": { "better-sqlite3": "^11.8.0", - "drizzle-orm": "^0.38.4" + "drizzle-orm": "^0.38.4", + "nanoid": "^5.0.9", + "spotify-web-api-node": "^5.0.2", + "svelte-kit-sessions": "^0.4.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbb60b8..b2d3b49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,15 @@ importers: drizzle-orm: specifier: ^0.38.4 version: 0.38.4(@types/better-sqlite3@7.6.12)(better-sqlite3@11.8.1) + nanoid: + specifier: ^5.0.9 + version: 5.0.9 + spotify-web-api-node: + specifier: ^5.0.2 + version: 5.0.2 + svelte-kit-sessions: + specifier: ^0.4.0 + version: 0.4.0(@sveltejs/kit@2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11(@types/node@22.10.7)))(svelte@5.19.0)(vite@5.4.11(@types/node@22.10.7)))(svelte@5.19.0) devDependencies: '@eslint/compat': specifier: ^1.2.5 @@ -39,9 +48,15 @@ importers: '@types/better-sqlite3': specifier: ^7.6.12 version: 7.6.12 + '@types/spotify-web-api-node': + specifier: ^5.0.11 + version: 5.0.11 autoprefixer: specifier: ^10.4.20 version: 10.4.20(postcss@8.5.1) + bits-ui: + specifier: 1.0.0-next.78 + version: 1.0.0-next.78(svelte@5.19.0) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -75,6 +90,9 @@ importers: svelte-check: specifier: ^4.0.0 version: 4.1.4(picomatch@4.0.2)(svelte@5.19.0)(typescript@5.7.3) + svelte-dnd-action: + specifier: ^0.9.54 + version: 0.9.54(svelte@5.19.0) tailwind-merge: specifier: ^2.6.0 version: 2.6.0 @@ -569,6 +587,15 @@ packages: resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.6.9': + resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==} + + '@floating-ui/dom@1.6.13': + resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==} + + '@floating-ui/utils@0.2.9': + resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -589,10 +616,17 @@ packages: resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} engines: {node: '>=18.18'} + '@internationalized/date@3.7.0': + resolution: {integrity: sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/ttlcache@1.4.1': + resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} + engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -790,6 +824,9 @@ packages: svelte: ^5.0.0-next.96 || ^5.0.0 vite: ^5.0.0 + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@tailwindcss/container-queries@0.1.1': resolution: {integrity: sha512-p18dswChx6WnTSaJCSGx6lTmrGzNNvm2FtXmiO6AuA1V4U5REyoqwmT6kgAsIMdjo07QdAfYXHJ4hnMtfHzWgA==} peerDependencies: @@ -818,6 +855,12 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/spotify-api@0.0.25': + resolution: {integrity: sha512-okhoy0U9fPWtwqCfbDyW8VxamhqvXE0gXIVeMOh5HcvEFQvWW2X0VsvdiX/OyiGQpZbZiOJXIGrbnIPfK0AIpA==} + + '@types/spotify-web-api-node@5.0.11': + resolution: {integrity: sha512-RS3IkSqH9geC61e8qd+Oy7giOTtiY7ywm0Z4bu5uYuc7XuOcLfDwKjmle85IbpTEdazeCgmIbo8nMLg7WDVvgw==} + '@typescript-eslint/eslint-plugin@8.20.0': resolution: {integrity: sha512-naduuphVw5StFfqp4Gq4WhIBE2gN1GEmMUExpJYknZJdRnc+2gDzB8Z3+5+/Kv33hPQRDGzQO/0opHE72lZZ6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -916,6 +959,9 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.20: resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} @@ -943,6 +989,12 @@ packages: bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bits-ui@1.0.0-next.78: + resolution: {integrity: sha512-jZjG2ObZ/CNyCNaXecpItC7hRXqJAgEfMhr06/eNrf3wHiiPyhdcy4OkzLcJyxeOrDyj+xma8cZTd3JRWqJdAw==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.11.0 + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -967,6 +1019,14 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + call-bind-apply-helpers@1.0.1: + resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + engines: {node: '>= 0.4'} + + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1004,6 +1064,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1011,6 +1075,9 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1018,6 +1085,9 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1051,6 +1121,10 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -1160,6 +1234,10 @@ packages: sqlite3: optional: true + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1175,6 +1253,18 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + esbuild-register@3.6.0: resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} peerDependencies: @@ -1301,6 +1391,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fastq@1.18.0: resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} @@ -1338,6 +1431,14 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + form-data@3.0.2: + resolution: {integrity: sha512-sJe+TQb2vIaIyO783qN6BlMYWMw3WBOHA1Ay2qxsnjuafEOQFJ2JakedOQirT6D5XPRxDvS7AHYyem9fTpb4LQ==} + engines: {node: '>= 6'} + + formidable@1.2.6: + resolution: {integrity: sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==} + deprecated: 'Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau' + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -1352,6 +1453,14 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-intrinsic@1.2.7: + resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} @@ -1378,6 +1487,10 @@ packages: resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -1385,6 +1498,10 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -1413,6 +1530,9 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1510,14 +1630,35 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -1562,6 +1703,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@5.0.9: + resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==} + engines: {node: ^18 || >=20} + hasBin: true + napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} @@ -1591,6 +1737,10 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + object-inspect@1.13.3: + resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} + engines: {node: '>= 0.4'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -1794,6 +1944,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1840,6 +1994,16 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + runed@0.20.0: + resolution: {integrity: sha512-YqPxaUdWL5nUXuSF+/v8a+NkVN8TGyEGbQwTA25fLY35MR/2bvZ1c6sCbudoo1kT4CAJPh4kUkcgGVxW127WKw==} + peerDependencies: + svelte: ^5.7.0 + + runed@0.22.0: + resolution: {integrity: sha512-ZWVXWhOr0P5xdNgtviz6D1ivLUDWKLCbeC5SUEJ3zBkqLReVqWHenFxMNFeFaiC5bfxhFxyxzyzB+98uYFtwdA==} + peerDependencies: + svelte: ^5.7.0 + sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -1863,6 +2027,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -1888,6 +2068,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + spotify-web-api-node@5.0.2: + resolution: {integrity: sha512-r82dRWU9PMimHvHEzL0DwEJrzFk+SMCVfq249SLt3I7EFez7R+jeoKQd+M1//QcnjqlXPs2am4DFsGk8/GCsrA==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -1915,11 +2098,19 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + style-to-object@1.0.8: + resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true + superagent@6.1.0: + resolution: {integrity: sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==} + engines: {node: '>= 7.0.0'} + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1936,6 +2127,11 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' + svelte-dnd-action@0.9.54: + resolution: {integrity: sha512-Hue0e449cd3WOASOeawFhEDUb6WFz6QyMVzuEd2RCWhzUAZS6PvOCLZeJyDkx4uKgYve9PR08yzTlEV0oKaH8A==} + peerDependencies: + svelte: '>=3.23.0 || ^5.0.0-next.0' + svelte-eslint-parser@0.43.0: resolution: {integrity: sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1945,6 +2141,18 @@ packages: svelte: optional: true + svelte-kit-sessions@0.4.0: + resolution: {integrity: sha512-cWjHwd+EGIuZ0p8CxSqE5EMOT8EUsoYfAnbE8QB+r6FonroYiMvTLUgv8b9dVLC55Yw3UtTntjUaZ5fKJF3XOA==} + peerDependencies: + '@sveltejs/kit': ^1.0.0 || ^2.0.0 + svelte: ^5.1.13 + + svelte-toolbelt@0.7.0: + resolution: {integrity: sha512-i/Tv4NwAWWqJnK5H0F8y/ubDnogDYlwwyzKhrspTUFzrFuGnYshqd2g4/R43ds841wmaFiSW/HsdsdWhPOlrAA==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.0.0 + svelte@5.19.0: resolution: {integrity: sha512-qvd2GvvYnJxS/MteQKFSMyq8cQrAAut28QZ39ySv9k3ggmhw4Au4Rfcsqva74i0xMys//OhbhVCNfXPrDzL/Bg==} engines: {node: '>=18'} @@ -2002,6 +2210,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -2382,6 +2593,17 @@ snapshots: '@eslint/core': 0.10.0 levn: 0.4.1 + '@floating-ui/core@1.6.9': + dependencies: + '@floating-ui/utils': 0.2.9 + + '@floating-ui/dom@1.6.13': + dependencies: + '@floating-ui/core': 1.6.9 + '@floating-ui/utils': 0.2.9 + + '@floating-ui/utils@0.2.9': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -2395,6 +2617,10 @@ snapshots: '@humanwhocodes/retry@0.4.1': {} + '@internationalized/date@3.7.0': + dependencies: + '@swc/helpers': 0.5.15 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2404,6 +2630,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/ttlcache@1.4.1': {} + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -2578,6 +2806,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + '@tailwindcss/container-queries@0.1.1(tailwindcss@3.4.17)': dependencies: tailwindcss: 3.4.17 @@ -2603,6 +2835,12 @@ snapshots: '@types/resolve@1.20.2': {} + '@types/spotify-api@0.0.25': {} + + '@types/spotify-web-api-node@5.0.11': + dependencies: + '@types/spotify-api': 0.0.25 + '@typescript-eslint/eslint-plugin@8.20.0(@typescript-eslint/parser@8.20.0(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3))(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -2720,6 +2958,8 @@ snapshots: aria-query@5.3.2: {} + asynckit@0.4.0: {} + autoprefixer@10.4.20(postcss@8.5.1): dependencies: browserslist: 4.24.4 @@ -2747,6 +2987,16 @@ snapshots: dependencies: file-uri-to-path: 1.0.0 + bits-ui@1.0.0-next.78(svelte@5.19.0): + dependencies: + '@floating-ui/core': 1.6.9 + '@floating-ui/dom': 1.6.13 + '@internationalized/date': 3.7.0 + esm-env: 1.2.2 + runed: 0.22.0(svelte@5.19.0) + svelte: 5.19.0 + svelte-toolbelt: 0.7.0(svelte@5.19.0) + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -2780,6 +3030,16 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + call-bind-apply-helpers@1.0.1: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.3: + dependencies: + call-bind-apply-helpers: 1.0.1 + get-intrinsic: 1.2.7 + callsites@3.1.0: {} camelcase-css@2.0.1: {} @@ -2817,14 +3077,22 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@4.1.1: {} commondir@1.0.1: {} + component-emitter@1.3.1: {} + concat-map@0.0.1: {} cookie@0.6.0: {} + cookiejar@2.1.4: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2847,6 +3115,8 @@ snapshots: deepmerge@4.3.1: {} + delayed-stream@1.0.0: {} + detect-libc@2.0.3: {} devalue@5.1.1: {} @@ -2869,6 +3139,12 @@ snapshots: '@types/better-sqlite3': 7.6.12 better-sqlite3: 11.8.1 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} electron-to-chromium@1.5.83: {} @@ -2881,6 +3157,14 @@ snapshots: dependencies: once: 1.4.0 + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + esbuild-register@3.6.0(esbuild@0.19.12): dependencies: debug: 4.4.0 @@ -3100,6 +3384,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-safe-stringify@2.1.1: {} + fastq@1.18.0: dependencies: reusify: 1.0.4 @@ -3135,6 +3421,14 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@3.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + formidable@1.2.6: {} + fraction.js@4.3.7: {} fs-constants@1.0.0: {} @@ -3144,6 +3438,24 @@ snapshots: function-bind@1.1.2: {} + get-intrinsic@1.2.7: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-tsconfig@4.8.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -3171,10 +3483,14 @@ snapshots: globals@15.14.0: {} + gopd@1.2.0: {} + graphemer@1.4.0: {} has-flag@4.0.0: {} + has-symbols@1.1.0: {} + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -3196,6 +3512,8 @@ snapshots: ini@1.3.8: {} + inline-style-parser@0.2.4: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -3277,13 +3595,25 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + math-intrinsics@1.1.0: {} + merge2@1.4.1: {} + methods@1.1.2: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@2.6.0: {} + mimic-response@3.1.0: {} mini-svg-data-uri@1.4.4: {} @@ -3316,6 +3646,8 @@ snapshots: nanoid@3.3.8: {} + nanoid@5.0.9: {} + napi-build-utils@1.0.2: {} natural-compare@1.4.0: {} @@ -3334,6 +3666,8 @@ snapshots: object-hash@3.0.0: {} + object-inspect@1.13.3: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -3471,6 +3805,10 @@ snapshots: punycode@2.3.1: {} + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} rc@1.2.8: @@ -3537,6 +3875,16 @@ snapshots: dependencies: queue-microtask: 1.2.3 + runed@0.20.0(svelte@5.19.0): + dependencies: + esm-env: 1.2.2 + svelte: 5.19.0 + + runed@0.22.0(svelte@5.19.0): + dependencies: + esm-env: 1.2.2 + svelte: 5.19.0 + sade@1.8.1: dependencies: mri: 1.2.0 @@ -3553,6 +3901,34 @@ snapshots: shebang-regex@3.0.0: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.3 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + object-inspect: 1.13.3 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + object-inspect: 1.13.3 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.3 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + signal-exit@4.1.0: {} simple-concat@1.0.1: {} @@ -3578,6 +3954,12 @@ snapshots: source-map@0.6.1: {} + spotify-web-api-node@5.0.2: + dependencies: + superagent: 6.1.0 + transitivePeerDependencies: + - supports-color + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -3606,6 +3988,10 @@ snapshots: strip-json-comments@3.1.1: {} + style-to-object@1.0.8: + dependencies: + inline-style-parser: 0.2.4 + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -3616,6 +4002,22 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 + superagent@6.1.0: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.0 + fast-safe-stringify: 2.1.1 + form-data: 3.0.2 + formidable: 1.2.6 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.14.0 + readable-stream: 3.6.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -3634,6 +4036,10 @@ snapshots: transitivePeerDependencies: - picomatch + svelte-dnd-action@0.9.54(svelte@5.19.0): + dependencies: + svelte: 5.19.0 + svelte-eslint-parser@0.43.0(svelte@5.19.0): dependencies: eslint-scope: 7.2.2 @@ -3644,6 +4050,19 @@ snapshots: optionalDependencies: svelte: 5.19.0 + svelte-kit-sessions@0.4.0(@sveltejs/kit@2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11(@types/node@22.10.7)))(svelte@5.19.0)(vite@5.4.11(@types/node@22.10.7)))(svelte@5.19.0): + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@sveltejs/kit': 2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11(@types/node@22.10.7)))(svelte@5.19.0)(vite@5.4.11(@types/node@22.10.7)) + svelte: 5.19.0 + + svelte-toolbelt@0.7.0(svelte@5.19.0): + dependencies: + clsx: 2.1.1 + runed: 0.20.0(svelte@5.19.0) + style-to-object: 1.0.8 + svelte: 5.19.0 + svelte@5.19.0: dependencies: '@ampproject/remapping': 2.3.0 @@ -3736,6 +4155,8 @@ snapshots: ts-interface-checker@0.1.13: {} + tslib@2.8.1: {} + tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..17248ba --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,13 @@ +import { SESH_SECRET } from '$env/static/private'; +import type { Handle } from '@sveltejs/kit'; +import { sveltekitSessionHandle } from 'svelte-kit-sessions'; + +export const handle: Handle = sveltekitSessionHandle({ + secret: SESH_SECRET +}); + +declare module 'svelte-kit-sessions' { + interface SessionData { + userId: string; + } +} diff --git a/src/lib/components/DNDGroup.svelte b/src/lib/components/DNDGroup.svelte new file mode 100644 index 0000000..e4a4079 --- /dev/null +++ b/src/lib/components/DNDGroup.svelte @@ -0,0 +1,39 @@ + + +
+ {#if image} + {#each items as item, i (item.id)} +
+ Album Art + +
+ {/each} + {:else} + {#each items as item, i (item.id)} +
+

{item.value}

+ + +
+ {/each} + {/if} +
diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..6353277 --- /dev/null +++ b/src/lib/components/ui/button/button.svelte @@ -0,0 +1,75 @@ + + + + +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/src/lib/components/ui/button/index.ts b/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..fb585d7 --- /dev/null +++ b/src/lib/components/ui/button/index.ts @@ -0,0 +1,17 @@ +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants, +} from "./button.svelte"; + +export { + Root, + type ButtonProps as Props, + // + Root as Button, + buttonVariants, + type ButtonProps, + type ButtonSize, + type ButtonVariant, +}; diff --git a/src/lib/server/AlbumState.svelte.ts b/src/lib/server/AlbumState.svelte.ts new file mode 100644 index 0000000..47f2d70 --- /dev/null +++ b/src/lib/server/AlbumState.svelte.ts @@ -0,0 +1,47 @@ +import type { AlbumSolveState } from '$lib/types'; + +class AlbumState { + private albums: SpotifyApi.AlbumObjectSimplified[] | undefined = undefined; + + setAlbums(data: SpotifyApi.AlbumObjectSimplified[]) { + if (!data) { + return; + } + + this.albums = data; + } + + checkSolve(data: AlbumSolveState[]) { + if (!data || !this.albums) { + return false; + } + + for (const solve of data) { + const search = this.albums.filter((album) => album.name === solve.name); + + if (!search) { + return false; + } + + const matching = search.at(0); + + if (!matching) { + return false; + } + + if (matching.images.at(0)?.url !== solve.imageUrl) { + return false; + } + + for (const artistName of solve.artists) { + if (!matching.artists.find((artist) => artist.name === artistName)) { + return false; + } + } + } + + return true; + } +} + +export const albumState = new AlbumState(); diff --git a/src/lib/server/PlayerState.svelte.ts b/src/lib/server/PlayerState.svelte.ts new file mode 100644 index 0000000..a8498cd --- /dev/null +++ b/src/lib/server/PlayerState.svelte.ts @@ -0,0 +1,60 @@ +import type { Player } from '$lib/types'; +import { getContext, setContext } from 'svelte'; + +export class PlayerState { + players = $state([]); + + newPlayer(id: string) { + this.players.push({ id: id, stage: 0, highscore: 0 }); + } + + getStage(id: string) { + const player = this.players.find((player) => player.id === id); + + if (!player) { + return undefined; + } + + return player.stage; + } + + nextStage(id: string) { + const player = this.players.find((player) => player.id === id); + + if (!player) { + return; + } + + player.stage += 1; + } + + score(id: string, win: boolean) { + const player = this.players.find((player) => player.id === id); + + if (!player) { + return; + } + + if (win) { + player.stage += 1; + + if (player.stage > player.highscore) { + player.highscore = player.stage; + } + } else { + player.stage = 0; + } + } + + getHighscore(id: string) { + const player = this.players.find((player) => player.id === id); + + if (!player) { + return undefined; + } + + return player.highscore; + } +} + +export const playerState = new PlayerState(); diff --git a/src/lib/server/Spotify.svelte.ts b/src/lib/server/Spotify.svelte.ts new file mode 100644 index 0000000..67a38ef --- /dev/null +++ b/src/lib/server/Spotify.svelte.ts @@ -0,0 +1,62 @@ +import SpotifyWebApi from 'spotify-web-api-node'; +import { CLIENT_ID, CLIENT_SECRET } from '$env/static/private'; +import { getRandomSearch } from '$lib/utils'; + +class SpotifyAPI { + private api = new SpotifyWebApi({ + clientId: CLIENT_ID, + clientSecret: CLIENT_SECRET + }); + private exiresAt: Date = $state(new Date()); + + async refreshAccessToken() { + // If current token is valid for at least 100ms more + if (this.exiresAt.getTime() - new Date().getTime() > 10) { + return true; + } + + return await this.api.clientCredentialsGrant().then( + (data) => { + console.log(data.body); + if (!data.body['expires_in'] || !data.body['access_token']) { + return false; + } + + const new_date = new Date(); + new_date.setSeconds(new_date.getSeconds() + Number(data.body['expires_in'])); + + this.exiresAt = new_date; + this.api.setAccessToken(data.body['access_token']); + + return true; + }, + function (err) { + console.log('Something went wrong when retrieving an access token', err); + return false; + } + ); + } + + async getRandomAlbum() { + if (!(await this.refreshAccessToken())) { + return undefined; + } + + const randomSearch = getRandomSearch(); + const randomOffset = Math.floor(Math.random() * 1000); + + return await this.api.search(randomSearch, ['album'], { limit: 1, offset: randomOffset }).then( + function (data) { + if (data.body.albums?.items?.at(0)) { + return data.body.albums.items.at(0); + } + }, + (err) => { + console.log(err); + return undefined; + } + ); + } +} + +export const spotifyAPI = new SpotifyAPI(); diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..37d1116 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,11 @@ +export type AlbumSolveState = { + name: string; + artists: string[]; + imageUrl: string; +}; + +export type Player = { + id: string; + stage: number; + highscore: number; +}; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index ac680b3..61d5240 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,62 @@ -import { type ClassValue, clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +export function getRandomSearch() { + // A list of all characters that can be chosen. + const characters = 'abcdefghijklmnopqrstuvwxyz'; + + // Gets a random character from the characters string. + const randomCharacter = characters.charAt(Math.floor(Math.random() * characters.length)); + let randomSearch = ''; + + // Places the wildcard character at the beginning, or both beginning and end, randomly. + switch (Math.round(Math.random())) { + case 0: + randomSearch = randomCharacter + '%'; + break; + case 1: + randomSearch = '%' + randomCharacter + '%'; + break; + } + + return randomSearch; +} + +export function shuffleObjectValues(arr: Array): Array { + // Create a copy of the array + const copy = structuredClone(arr); + + // Get all keys from the first object + const keys = Object.keys(copy[0] as object); + + keys.forEach((key: string) => { + // Get all values for this key with proper type assertion + const values = copy.map((obj) => (obj as { [key: string]: unknown })[key]); + + // Fisher-Yates shuffle algorithm + for (let i = values.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [values[i], values[j]] = [values[j], values[i]]; + } + + // Reassign shuffled values back to objects + copy.forEach((obj, index) => { + (obj as { [key: string]: unknown })[key] = values[index]; + }); + }); + + return copy; +} + +export function shuffleArray(array: T[]): T[] { + for (let i = array.length - 1; i >= 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [array[i], array[j]] = [array[j], array[i]]; + } + + return array; +} diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..3b3d98d --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,83 @@ +import { shuffleArray } from '$lib/utils'; +import { nanoid } from 'nanoid'; +import type { PageServerLoad } from './$types'; +import type { AlbumSolveState } from '$lib/types'; +import { albumState } from '$lib/server/AlbumState.svelte'; +import { playerState } from '$lib/server/PlayerState.svelte'; +import { invalidateAll } from '$app/navigation'; + +const count = 3; +export const load: PageServerLoad = async ({ fetch, locals }) => { + const { session } = locals; + + if (!session?.data?.userId) { + await session.setData({ userId: nanoid() }); + await session.save(); + } + + const user = session.data.userId; + let stage = playerState.getStage(user); + + if (!stage) { + playerState.newPlayer(user); + stage = playerState.getStage(user); + } + + const highscore = playerState.getHighscore(user); + + const albumData = await fetch(`/api/getAlbums/${count}`) + .then((res) => { + return res.json(); + }) + .then((data) => { + return data; + }); + + const albumNames = albumData.albums.map((album) => ({ id: nanoid(), value: album.name })); + const albumImages = albumData.albums.map((album) => ({ + id: nanoid(), + value: album.images.at(0).url + })); + const albumArtists = albumData.albums.map((album) => ({ + id: nanoid(), + value: album.artists.map((artist) => artist.name).join(', ') + })); + + return { + names: shuffleArray(albumNames), + images: shuffleArray(albumImages), + artists: shuffleArray(albumArtists), + stage: stage, + highscore: highscore + }; +}; + +export const actions = { + default: async ({ request, locals }) => { + const { session } = locals; // you can access `locals.session` + + if (!session?.data?.userId) { + return; + } + + const user = session.data.userId; + const data = await request.formData(); + + const state: AlbumSolveState[] = []; + + for (let i = 0; i < count; i++) { + const name = data.get(`names_${i}`); + const image = data.get(`images_${i}`); + const artists = data.get(`artists_${i}`); + + const artistList = artists.split(', '); + + state.push({ name: name, imageUrl: image, artists: artistList }); + } + + const solved = albumState.checkSolve(state); + + playerState.score(user, solved); + return { loading: false }; + } +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index cc88df0..ef5b526 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1,22 @@ -

Welcome to SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

+ + +
+ + + + +
+

Stage: {data.stage}

+ +

High Score: {data.highscore}

+
+
diff --git a/src/routes/api/getAlbums/[count]/+server.ts b/src/routes/api/getAlbums/[count]/+server.ts new file mode 100644 index 0000000..ccf5c70 --- /dev/null +++ b/src/routes/api/getAlbums/[count]/+server.ts @@ -0,0 +1,20 @@ +import { albumState } from '$lib/server/AlbumState.svelte'; +import { spotifyAPI } from '$lib/server/Spotify.svelte'; +import { json } from '@sveltejs/kit'; + +export async function GET({ params }) { + const count = params.count || 1; + + const albums: SpotifyApi.AlbumObjectSimplified[] = []; + + for (let i = 0; i < count; i++) { + const album = await spotifyAPI.getRandomAlbum(); + if (album) { + albums.push(album); + } + } + + albumState.setAlbums(albums); + + return json({ albums: albums }); +}