improve: local import page's file select part

This commit is contained in:
Alikia2x 2024-05-05 03:42:21 +08:00
parent b16f03c8f2
commit f04b92b76c
21 changed files with 459 additions and 23 deletions

View File

@ -34,7 +34,10 @@
},
"type": "module",
"dependencies": {
"jotai": "^2.8.0",
"jotai-svelte": "^0.0.2",
"localforage": "^1.10.0",
"music-metadata-browser": "^2.5.10",
"uuid": "^9.0.1"
}
}

View File

@ -5,9 +5,18 @@ settings:
excludeLinksFromLockfile: false
dependencies:
jotai:
specifier: ^2.8.0
version: 2.8.0
jotai-svelte:
specifier: ^0.0.2
version: 0.0.2(jotai@2.8.0)
localforage:
specifier: ^1.10.0
version: 1.10.0
music-metadata-browser:
specifier: ^2.5.10
version: 2.5.10
uuid:
specifier: ^9.0.1
version: 9.0.1
@ -645,6 +654,10 @@ packages:
- supports-color
dev: true
/@tokenizer/token@0.3.0:
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
dev: false
/@types/cookie@0.6.0:
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
dev: true
@ -711,6 +724,13 @@ packages:
pretty-format: 29.7.0
dev: true
/abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
dependencies:
event-target-shim: 5.0.1
dev: false
/acorn-jsx@5.3.2(acorn@8.11.3):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -822,6 +842,10 @@ packages:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: false
/binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@ -862,6 +886,13 @@ packages:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
dev: true
/buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
dev: false
/cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@ -957,6 +988,11 @@ packages:
resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==}
dev: true
/content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
dev: false
/cookie@0.6.0:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'}
@ -995,7 +1031,6 @@ packages:
optional: true
dependencies:
ms: 2.1.2
dev: true
/deep-eql@4.1.3:
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
@ -1259,6 +1294,16 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
dev: false
/events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
dev: false
/execa@8.0.1:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
@ -1310,6 +1355,15 @@ packages:
flat-cache: 3.2.0
dev: true
/file-type@16.5.4:
resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==}
engines: {node: '>=10'}
dependencies:
readable-web-to-node-stream: 3.0.2
strtok3: 6.3.0
token-types: 4.2.1
dev: false
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
@ -1452,6 +1506,10 @@ packages:
engines: {node: '>=16.17.0'}
dev: true
/ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: false
/ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
@ -1487,7 +1545,6 @@ packages:
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
@ -1558,6 +1615,27 @@ packages:
hasBin: true
dev: true
/jotai-svelte@0.0.2(jotai@2.8.0):
resolution: {integrity: sha512-dPxBEIIMNkfgtXvvyiiCW5ds1VeAyeyxfIu5qeXb7VSgvNgqU/lCKld3ue7yRLdcRJUx2ddocI49WTzzNp/uGQ==}
peerDependencies:
jotai: '>=1.12.0'
dependencies:
jotai: 2.8.0
dev: false
/jotai@2.8.0:
resolution: {integrity: sha512-yZNMC36FdLOksOr8qga0yLf14miCJlEThlp5DeFJNnqzm2+ZG7wLcJzoOyij5K6U6Xlc5ljQqPDlJRgqW0Y18g==}
engines: {node: '>=12.20.0'}
peerDependencies:
'@types/react': '>=17.0.0'
react: '>=17.0.0'
peerDependenciesMeta:
'@types/react':
optional: true
react:
optional: true
dev: false
/js-tokens@9.0.0:
resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==}
dev: true
@ -1681,6 +1759,11 @@ packages:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
dev: true
/media-typer@1.1.0:
resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
engines: {node: '>= 0.8'}
dev: false
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true
@ -1758,7 +1841,33 @@ packages:
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/music-metadata-browser@2.5.10:
resolution: {integrity: sha512-03UnAmsSJoZZ5kK2BnEnd2zpH8LXRWQ6xlc7akKudhc2d9FT+yAiqapnmOzjW3g4cxxvIsSK5MVBO2Gi+Ymjfw==}
dependencies:
buffer: 6.0.3
debug: 4.3.4
music-metadata: 7.14.0
readable-stream: 4.5.2
readable-web-to-node-stream: 3.0.2
transitivePeerDependencies:
- supports-color
dev: false
/music-metadata@7.14.0:
resolution: {integrity: sha512-xrm3w7SV0Wk+OythZcSbaI8mcr/KHd0knJieu8bVpaPfMv/Agz5EooCAPz3OR5hbYMiUG6dgAPKZKnMzV+3amA==}
engines: {node: '>=10'}
dependencies:
'@tokenizer/token': 0.3.0
content-type: 1.0.5
debug: 4.3.4
file-type: 16.5.4
media-typer: 1.1.0
strtok3: 6.3.0
token-types: 4.2.1
transitivePeerDependencies:
- supports-color
dev: false
/mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
@ -1902,6 +2011,11 @@ packages:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
dev: true
/peek-readable@4.1.0:
resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==}
engines: {node: '>=8'}
dev: false
/periscopic@3.1.0:
resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==}
dependencies:
@ -2072,6 +2186,11 @@ packages:
react-is: 18.3.1
dev: true
/process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
dev: false
/punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@ -2091,6 +2210,33 @@ packages:
pify: 2.3.0
dev: true
/readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: false
/readable-stream@4.5.2:
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
abort-controller: 3.0.0
buffer: 6.0.3
events: 3.3.0
process: 0.11.10
string_decoder: 1.3.0
dev: false
/readable-web-to-node-stream@3.0.2:
resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==}
engines: {node: '>=8'}
dependencies:
readable-stream: 3.6.2
dev: false
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@ -2170,6 +2316,10 @@ packages:
mri: 1.2.0
dev: true
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/sander@0.5.1:
resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==}
dependencies:
@ -2262,6 +2412,12 @@ packages:
strip-ansi: 7.1.0
dev: true
/string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies:
safe-buffer: 5.2.1
dev: false
/strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@ -2299,6 +2455,14 @@ packages:
js-tokens: 9.0.0
dev: true
/strtok3@6.3.0:
resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==}
engines: {node: '>=10'}
dependencies:
'@tokenizer/token': 0.3.0
peek-readable: 4.1.0
dev: false
/sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
@ -2522,6 +2686,14 @@ packages:
is-number: 7.0.0
dev: true
/token-types@4.2.1:
resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==}
engines: {node: '>=10'}
dependencies:
'@tokenizer/token': 0.3.0
ieee754: 1.2.1
dev: false
/totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
@ -2577,7 +2749,6 @@ packages:
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/uuid@9.0.1:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}

View File

@ -4,4 +4,8 @@
h1 {
@apply text-4xl font-bold leading-[4rem];
}
h2 {
@apply text-3xl font-medium leading-[3rem];
}

View File

@ -1,12 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Barlow:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"
rel="stylesheet"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,17 @@
<div class={$$props.class}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
fill="none"
viewBox="0 0 16 16"
>
<g style="fill: rgb(0, 0, 0);"
><path
d="M0 7.818c0 .394.342.72.745.72h6.514v6.363a.742.742 0 0 0 1.482 0V8.538h6.514c.403 0 .745-.326.745-.72a.743.743 0 0 0-.745-.728H8.741V.728a.742.742 0 0 0-1.482 0V7.09H.745A.743.743 0 0 0 0 7.818Z"
style="fill: rgb(0, 117, 255);"
class="fills"
/></g
>
</svg>
</div>

7
src/components/import/fileList.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
interface FileItem {
name: string;
size?: number;
type: string;
lastModified?: number;
lastModifiedDate?: Date;
}

View File

@ -0,0 +1,4 @@
import { atom } from 'jotai-svelte'
export const fileListState = atom([] as any[]);
export const finalFileListState = atom([] as any[]);

View File

@ -0,0 +1,57 @@
<script lang="ts">
import { useAtom } from 'jotai-svelte';
import { fileListState, finalFileListState } from './fileList.state';
import toHumanSize from '$lib/humanSize';
import audioFormatText from '$lib/audioFormatText';
import extractFileName from '$lib/extractFileName';
import getAudioMeta from '$lib/getAudioCoverURL';
import convertCoverData from '$lib/convertCoverData';
import type { IAudioMetadata } from 'music-metadata-browser';
import formatDuration from '$lib/formatDuration';
const items = useAtom(fileListState);
const finalItems = useAtom(finalFileListState);
$: {
const length = $items.length;
for (let i = 0; i < length; i++) {
if ($items[i].pic || $items[i].pic === 'N/A') continue;
getAudioMeta($items[i], (metadata: IAudioMetadata) => {
let cover: string | null = null;
let duration: number | null = null;
if (metadata.common.picture)
cover = convertCoverData(metadata.common.picture[0]);
if (metadata.format.duration)
duration = metadata.format.duration;
finalItems.update((prev) => {
if (cover) {
let currentItem = [];
currentItem = $items[i];
currentItem.pic = cover;
currentItem.duration = duration;
return [...prev, currentItem];
} else {
let currentItem = [];
currentItem = $items[i];
currentItem.pic = 'N/A';
currentItem.duration = duration;
return [...prev, currentItem];
}
});
});
}
}
</script>
<ul
class="mt-4 relative w-full min-h-48 max-h-[27rem] overflow-y-auto bg-zinc-200 dark:bg-zinc-800 rounded"
>
{#each $finalItems as item}
<li class="relative m-4 p-4 bg-zinc-300 dark:bg-zinc-600 rounded-lg">
<span>{extractFileName(item.name)}</span> <br />
<span>{toHumanSize(item.size)}</span> · <span>{audioFormatText(item.type)}</span> · <span>{item.duration ? formatDuration(item.duration) : 'N/A'}</span>
{#if item.pic !== 'N/A'}
<img class="h-16 w-16 object-cover absolute rounded-lg right-2 top-2" src={item.pic} alt="" />
{/if}
</li>
{/each}
</ul>

View File

@ -0,0 +1,41 @@
<script lang="ts">
export let audioFiles: HTMLInputElement;
import ImportIcon from './importIcon.svelte';
import { onMount } from 'svelte';
import { useAtom } from 'jotai-svelte';
import { fileListState } from './fileList.state';
import AddIcon from './addIcon.svelte';
const fileItems = useAtom(fileListState);
onMount(() => {
audioFiles.addEventListener('change', function (e: any) {
if (audioFiles.files) {
fileItems.update((prev) => {
if (audioFiles.files) {
return [...prev, ...Array.from(audioFiles.files)];
} else {
return prev;
}
});
}
});
return () => {};
});
</script>
<input style="display: none;" type="file" bind:this={audioFiles} multiple accept="audio/*" />
<div class={$$props.class}>
<button
on:click={() => {
audioFiles.click();
}}
>
<div>
{#if $fileItems.length > 0}
<AddIcon class="z-[1] relative text-3xl" />
{:else}
<ImportIcon class="z-[1] relative text-4xl" />
{/if}
</div>
</button>
</div>

View File

@ -0,0 +1,15 @@
<div class={$$props.class}>
<svg width="1em" xmlns="http://www.w3.org/2000/svg" height="1em" fill="none" viewBox="0 0 12 17.2"
><g style="mix-blend-mode: darken; fill: rgb(0, 0, 0);"
><path
d="M12.935 6.131v7.701c0 1.532-.636 2.168-2.169 2.168H2.168C.636 16 0 15.364 0 13.832V6.131c0-1.495.636-2.168 2.168-2.168h1.869v1.121H2.168c-.785 0-1.121.337-1.121 1.047v7.701c0 .822.336 1.159 1.121 1.159h8.598c.711 0 1.047-.337 1.047-1.159V6.131c0-.71-.336-1.047-1.047-1.047H8.897V3.963l1.889-.006c1.513.006 2.149.641 2.149 2.174Z"
style="fill: rgb(0, 117, 255); fill-opacity: 1;"
class="fills"
/><path
d="M6.505 11.607c.127 0 .205-.093.336-.205l3.028-2.991a.459.459 0 0 0 .112-.299c.019-.374-.223-.561-.523-.561-.206 0-.262.075-.299.113L7.178 9.645V.673C7.178.336 6.841 0 6.505 0c-.337 0-.673.336-.673.673v8.972L3.85 7.664a.404.404 0 0 0-.299-.113c-.266 0-.542.187-.523.561.008.169.056.243.112.299l3.028 2.991c.131.112.209.205.337.205Z"
style="fill: rgb(0, 117, 255); fill-opacity: 1;"
class="fills"
/></g
></svg
>
</div>

View File

@ -1,7 +1,20 @@
import { describe, it, expect } from 'vitest';
import formatDuration from '$lib/formatDuration';
describe('sum test', () => {
it('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
});
describe('formatDuration test', () => {
it('converts 120 seconds to "2:00"', () => {
expect(formatDuration(120)).toBe('2:00');
});
it('converts 185 seconds to "3:05"', () => {
expect(formatDuration(185)).toBe('3:05');
});
it('converts 601 seconds to "10:01"', () => {
expect(formatDuration(601)).toBe('10:01');
});
it('converts 3601 seconds to "1:00:01"', () => {
expect(formatDuration(3601)).toBe('1:00:01');
});
});

View File

@ -0,0 +1,10 @@
export default function(key: string){
const dict = {
"audio/mpeg": "MP3 音频",
"audio/ogg": "OGG 容器",
"audio/flac": "FLAC 无损音频",
"audio/aac": "AAC 音频"
}
if (!key) return "未知格式";
else return dict[key as keyof typeof dict];
}

View File

@ -0,0 +1,13 @@
export default function(dataObject: any) {
// Create a blob from the UInt8Array data
const blob = new Blob([dataObject.data], { type: dataObject.format });
// Create a URL for the blob
const imageUrl = URL.createObjectURL(blob);
// Create an Image object
const image = new Image();
image.src = imageUrl;
return imageUrl; // return the URL of the image
}

View File

@ -0,0 +1,4 @@
export default function(fullname: string){
if (!fullname) return '';
return fullname.split('.').slice(0, -1).join('.')
}

19
src/lib/formatDuration.ts Normal file
View File

@ -0,0 +1,19 @@
export default function(durationInSeconds: number): string {
// Calculate hours, minutes, and seconds
const hours = Math.floor(durationInSeconds / 3600);
const minutes = Math.floor((durationInSeconds % 3600) / 60);
const seconds = Math.round(durationInSeconds) % 60;
// Format hours, minutes, and seconds into string
let formattedTime = '';
if (hours > 0) {
formattedTime += hours + ':';
}
if (minutes < 10 && hours > 0) {
formattedTime += '0';
}
formattedTime += minutes + ':';
formattedTime += (seconds < 10 ? '0' : '') + seconds;
return formattedTime;
}

View File

@ -0,0 +1,11 @@
import * as musicMetadata from 'music-metadata-browser';
import convertCoverData from './convertCoverData';
export default function getAudioMeta(audio: File, callback: Function) {
musicMetadata.parseBlob(audio).then((metadata) => {
if (metadata)
callback(metadata);
else
callback(null);
})
}

10
src/lib/humanSize.ts Normal file
View File

@ -0,0 +1,10 @@
export default function toHumanSize(size: number | undefined){
if (!size) return '0 B'
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
let unitIndex = 0;
while (size >= 1000 && unitIndex < units.length - 1) {
size /= 1000;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`
}

View File

@ -4,7 +4,7 @@
<div
class="h-fit min-h-screen w-screen max-w-[100vw] overflow-x-hidden overflow-y-auto
bg-zinc-50 dark:bg-[#1f1f1f] text-black dark:text-white"
bg-zinc-50 dark:bg-[#1f1f1f] text-black dark:text-white relative z-0" style="font-family: 'Barlow', sans-serif;"
>
<slot />
</div>

View File

@ -0,0 +1 @@
export const ssr = false;

View File

@ -0,0 +1,17 @@
<script>
import FileList from '../../../components/import/fileList.svelte';
import FileSelector from '../../../components/import/fileSelector.svelte';
</script>
<h1>本地导入向导</h1>
<p>欢迎使用本地导入向导!</p>
<p>
你可以选择从本地导入你喜欢的音乐文件,并同时将封面、歌词、歌手与制作者等其他信息一并囊括其中。
</p>
<div class="w-full flex my-3">
<h2>音频</h2>
<FileSelector class="ml-auto top-2 relative" />
</div>
<FileList/>

View File

@ -1,9 +1,22 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill'
export default defineConfig({
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
}
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
},
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
},
plugins: [
NodeGlobalsPolyfillPlugin({
buffer: true
})
]
}
}
});