improve: local import page's file select part
This commit is contained in:
parent
b16f03c8f2
commit
f04b92b76c
@ -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"
|
||||
}
|
||||
}
|
||||
|
179
pnpm-lock.yaml
179
pnpm-lock.yaml
@ -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==}
|
||||
|
@ -4,4 +4,8 @@
|
||||
|
||||
h1 {
|
||||
@apply text-4xl font-bold leading-[4rem];
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-3xl font-medium leading-[3rem];
|
||||
}
|
24
src/app.html
24
src/app.html
@ -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>
|
||||
|
17
src/components/import/addIcon.svelte
Normal file
17
src/components/import/addIcon.svelte
Normal 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
7
src/components/import/fileList.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
interface FileItem {
|
||||
name: string;
|
||||
size?: number;
|
||||
type: string;
|
||||
lastModified?: number;
|
||||
lastModifiedDate?: Date;
|
||||
}
|
4
src/components/import/fileList.state.ts
Normal file
4
src/components/import/fileList.state.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { atom } from 'jotai-svelte'
|
||||
|
||||
export const fileListState = atom([] as any[]);
|
||||
export const finalFileListState = atom([] as any[]);
|
57
src/components/import/fileList.svelte
Normal file
57
src/components/import/fileList.svelte
Normal 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>
|
41
src/components/import/fileSelector.svelte
Normal file
41
src/components/import/fileSelector.svelte
Normal 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>
|
15
src/components/import/importIcon.svelte
Normal file
15
src/components/import/importIcon.svelte
Normal 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>
|
@ -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');
|
||||
});
|
||||
});
|
10
src/lib/audioFormatText.ts
Normal file
10
src/lib/audioFormatText.ts
Normal 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];
|
||||
}
|
13
src/lib/convertCoverData.ts
Normal file
13
src/lib/convertCoverData.ts
Normal 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
|
||||
}
|
4
src/lib/extractFileName.ts
Normal file
4
src/lib/extractFileName.ts
Normal 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
19
src/lib/formatDuration.ts
Normal 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;
|
||||
}
|
11
src/lib/getAudioCoverURL.ts
Normal file
11
src/lib/getAudioCoverURL.ts
Normal 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
10
src/lib/humanSize.ts
Normal 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]}`
|
||||
}
|
@ -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>
|
1
src/routes/import/local/+page.server.js
Normal file
1
src/routes/import/local/+page.server.js
Normal file
@ -0,0 +1 @@
|
||||
export const ssr = false;
|
17
src/routes/import/local/+page.svelte
Normal file
17
src/routes/import/local/+page.svelte
Normal 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/>
|
@ -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
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user