maps managment advancement

This commit is contained in:
MathieuG-P
2022-11-07 17:09:19 +01:00
parent 5e2dd10035
commit cd8dae5976
20 changed files with 515 additions and 29 deletions
+258
View File
@@ -3604,6 +3604,16 @@
"integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==",
"dev": true
},
"color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dev": true,
"requires": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3619,6 +3629,16 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dev": true,
"requires": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
@@ -4027,6 +4047,12 @@
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
"dev": true
},
"css-color-names": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz",
"integrity": "sha512-zj5D7X1U2h2zsXOAM8EyUREBnnts6H+Jm+d1M2DbiQQcUtnqgQsMrdo8JW9R80YFUmIdBZeMu5wvYM7hcgWP/Q==",
"dev": true
},
"css-declaration-sorter": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz",
@@ -4141,6 +4167,12 @@
}
}
},
"css-unit-converter": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==",
"dev": true
},
"css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
@@ -7026,6 +7058,12 @@
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
"hex-color-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==",
"dev": true
},
"hey-listen": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
@@ -7060,6 +7098,18 @@
"wbuf": "^1.1.0"
}
},
"hsl-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz",
"integrity": "sha512-M5ezZw4LzXbBKMruP+BNANf0k+19hDQMgpzBIYnya//Al+fjNct9Wf3b1WedLqdEs2hKBvxq/jh+DsHJLj0F9A==",
"dev": true
},
"hsla-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz",
"integrity": "sha512-7Wn5GMLuHBjZCb2bTmnDOycho0p/7UVaAeqXZGbHrBCl6Yd/xDhQJAXe6Ga9AXJH2I5zY1dEdYw2u1UptnSBJA==",
"dev": true
},
"html-encoding-sniffer": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
@@ -7104,6 +7154,12 @@
}
}
},
"html-tags": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz",
"integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==",
"dev": true
},
"html-webpack-plugin": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
@@ -7457,6 +7513,20 @@
"ci-info": "^3.2.0"
}
},
"is-color-stop": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz",
"integrity": "sha512-H1U8Vz0cfXNujrJzEcvvwMDW9Ra+biSYA3ThdQvAnMLJkEHQXn6bWzLkxHtVYJ+Sdbx0b6finn3jZiaVe7MAHA==",
"dev": true,
"requires": {
"css-color-names": "^0.0.4",
"hex-color-regex": "^1.1.0",
"hsl-regex": "^1.0.0",
"hsla-regex": "^1.0.0",
"rgb-regex": "^1.0.1",
"rgba-regex": "^1.0.0"
}
},
"is-core-module": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
@@ -8842,6 +8912,12 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"lodash.topath": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz",
"integrity": "sha512-1/W4dM+35DwvE/iEd1M9ekewOSTlpFekhw9mhAtrwjVqUr83/ilQiyAvmg4tVX7Unkcfl1KC+i9WdaT4B6aQcg==",
"dev": true
},
"lodash.union": {
"version": "4.6.0",
"resolved": "https://npm.zone51.tf/lodash.union/-/lodash.union-4.6.0.tgz",
@@ -9282,6 +9358,12 @@
"minimist": "^1.2.6"
}
},
"modern-normalize": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-1.1.0.tgz",
"integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==",
"dev": true
},
"mrmime": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
@@ -9361,6 +9443,15 @@
"semver": "^7.3.5"
}
},
"node-emoji": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
"integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==",
"dev": true,
"requires": {
"lodash": "^4.17.21"
}
},
"node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@@ -10449,6 +10540,12 @@
}
}
},
"pretty-hrtime": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
"integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
"dev": true
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -10538,6 +10635,100 @@
}
}
},
"ps-scrollbar-tailwind": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/ps-scrollbar-tailwind/-/ps-scrollbar-tailwind-0.0.1.tgz",
"integrity": "sha512-IPAAIXn0gNx4AzwHA9YY2+jZm18WTOzR+Xk+M3HfyEWWqRrGhBzQFUkgpQNe6mI2Bch1P4Bl62wh8rlR2W75jw==",
"dev": true,
"requires": {
"tailwindcss": "^2.0.2"
},
"dependencies": {
"fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.6",
"universalify": "^2.0.0"
}
},
"object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
"dev": true
},
"postcss-js": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz",
"integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==",
"dev": true,
"requires": {
"camelcase-css": "^2.0.1",
"postcss": "^8.1.6"
}
},
"tailwindcss": {
"version": "2.2.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.2.19.tgz",
"integrity": "sha512-6Ui7JSVtXadtTUo2NtkBBacobzWiQYVjYW0ZnKaP9S1ZCKQ0w7KVNz+YSDI/j7O7KCMHbOkz94ZMQhbT9pOqjw==",
"dev": true,
"requires": {
"arg": "^5.0.1",
"bytes": "^3.0.0",
"chalk": "^4.1.2",
"chokidar": "^3.5.2",
"color": "^4.0.1",
"cosmiconfig": "^7.0.1",
"detective": "^5.2.0",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.2.7",
"fs-extra": "^10.0.0",
"glob-parent": "^6.0.1",
"html-tags": "^3.1.0",
"is-color-stop": "^1.1.0",
"is-glob": "^4.0.1",
"lodash": "^4.17.21",
"lodash.topath": "^4.5.2",
"modern-normalize": "^1.1.0",
"node-emoji": "^1.11.0",
"normalize-path": "^3.0.0",
"object-hash": "^2.2.0",
"postcss-js": "^3.0.3",
"postcss-load-config": "^3.1.0",
"postcss-nested": "5.0.6",
"postcss-selector-parser": "^6.0.6",
"postcss-value-parser": "^4.1.0",
"pretty-hrtime": "^1.0.3",
"purgecss": "^4.0.3",
"quick-lru": "^5.1.1",
"reduce-css-calc": "^2.1.8",
"resolve": "^1.20.0",
"tmp": "^0.2.1"
}
},
"universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
"dev": true
}
}
},
"psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -10577,6 +10768,26 @@
"escape-goat": "^2.0.0"
}
},
"purgecss": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.1.3.tgz",
"integrity": "sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw==",
"dev": true,
"requires": {
"commander": "^8.0.0",
"glob": "^7.1.7",
"postcss": "^8.3.5",
"postcss-selector-parser": "^6.0.6"
},
"dependencies": {
"commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"dev": true
}
}
},
"qs": {
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
@@ -10896,6 +11107,24 @@
"strip-indent": "^3.0.0"
}
},
"reduce-css-calc": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
"integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==",
"dev": true,
"requires": {
"css-unit-converter": "^1.1.1",
"postcss-value-parser": "^3.3.0"
},
"dependencies": {
"postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"dev": true
}
}
},
"reflect.ownkeys": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz",
@@ -11074,6 +11303,18 @@
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==",
"dev": true
},
"rgb-regex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
"integrity": "sha512-gDK5mkALDFER2YLqH6imYvK6g02gpNGM4ILDZ472EwWfXZnC2ZEpoB2ECXTyOVUKuk/bPJZMzwQPBYICzP+D3w==",
"dev": true
},
"rgba-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz",
"integrity": "sha512-zgn5OjNQXLUTdq8m17KdaicF6w89TZs8ZU8y0AYENIU6wG8GG6LLm0yLSiPY8DmaYmHdgRW8rnApjoT0fQRfMg==",
"dev": true
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -11466,6 +11707,23 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dev": true,
"requires": {
"is-arrayish": "^0.3.1"
},
"dependencies": {
"is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
"dev": true
}
}
},
"sirv": {
"version": "1.0.19",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz",
+1
View File
@@ -200,6 +200,7 @@
"postcss": "^8.4.16",
"postcss-loader": "^6.2.1",
"prettier": "^2.7.1",
"ps-scrollbar-tailwind": "0.0.1",
"react-refresh": "^0.12.0",
"react-refresh-typescript": "^2.0.7",
"react-test-renderer": "^18.2.0",
@@ -18,7 +18,6 @@ export class LocalMapsManagerService {
private readonly LEVELS_ROOT_FOLDER = "Beat Saber_Data";
private readonly CUSTOM_LEVELS_FOLDER = "CustomLevels";
private readonly WIP_LEVELS_FOLDER = "CustomWIPLevels";
private readonly localVersion: BSLocalVersionService;
private readonly installLocation: InstallationLocationService;
+5 -2
View File
@@ -19,14 +19,17 @@ export default function App() {
const location = useLocation();
pageState.setLocation(location);
useEffect(() => {
themeService.theme$.subscribe(() => {
if(themeService.isDark || (themeService.isOS && window.matchMedia('(prefers-color-scheme: dark)').matches)){ return document.documentElement.classList.add('dark'); }
document.documentElement.classList.remove('dark');
});
}, []);
useEffect(() => {
pageState.setLocation(location);
}, [location])
return (
@@ -0,0 +1,68 @@
import { MapsManagerService } from "renderer/services/maps-manager.service"
import { BSVersion } from "shared/bs-version.interface"
import VisibilitySensor from "react-visibility-sensor"
import { useEffect, useState } from "react"
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"
import { Subscription } from "rxjs"
import { MapItem, MapItemProps } from "./map-item.component"
import { BsvMapCharacteristic, BsvMapDifficultyType } from "shared/models/maps/beat-saver.model"
type Props = {
version: BSVersion,
className?: string
}
export function LocalMapsListPanel({version, className} : Props) {
const mapsManager = MapsManagerService.getInstance();
const [isVisible, setIsVisible] = useState(false);
const [maps, setMaps] = useState([] as BsmLocalMap[]);
useEffect(() => {
const subs: Subscription[] = [];
if(isVisible && !maps?.length){
subs.push(mapsManager.getMaps(version).subscribe(localMaps => setMaps(() => localMaps)));
}
return () => { subs.forEach(s => s.unsubscribe()); }
}, [isVisible])
const extractMapDiffs = (map: BsmLocalMap): Map<BsvMapCharacteristic, BsvMapDifficultyType[]> => {
const res = new Map<BsvMapCharacteristic, BsvMapDifficultyType[]>();
map.rawInfo._difficultyBeatmapSets.forEach(set => {
set._difficultyBeatmaps.forEach(diff => {
const arr = res.get(set._beatmapCharacteristicName) || [];
arr.push(diff._difficulty);
res.set(set._beatmapCharacteristicName, arr);
})
})
return res;
}
const renderMapItem = (map: BsmLocalMap) => {
return <MapItem
key={map.hash}
hash={map.hash}
title={[map.rawInfo._songAuthorName, map.rawInfo._songName].join(" - ")}
coverUrl={map.coverUrl}
songUrl={map.songUrl}
autor={map.rawInfo._levelAuthorName}
bpm={map.rawInfo._beatsPerMinute}
duration={null}
diffs={extractMapDiffs(map)} mapId={null} qualified={null} ranked={null} autorLink={null}
/>;
}
return (
<VisibilitySensor onChange={setIsVisible}>
<ul className={className}>
{maps.map(map => renderMapItem(map))}
</ul>
</VisibilitySensor>
)
}
@@ -0,0 +1,84 @@
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"
import { BsmImage } from "../shared/bsm-image.component";
import { BsvMapCharacteristic, BsvMapDifficultyType } from "shared/models/maps/beat-saver.model"
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { LinkOpenerService } from "renderer/services/link-opener.service";
import { BsmLink } from "../shared/bsm-link.component";
import { BsmIcon } from "../svgs/bsm-icon.component";
import { BsmButton } from "../shared/bsm-button.component";
export type MapItemProps = {
hash: string,
title: string,
autor: string,
coverUrl: string,
songUrl: string,
autorLink: string,
mapId: string,
diffs: Map<BsvMapCharacteristic, BsvMapDifficultyType[]>,
qualified: boolean,
ranked: boolean,
bpm: number,
duration: number,
onDelete?: (hash: string) => void,
onDownload?: (id: string) => void,
}
export function MapItem({hash, title, autor, coverUrl, songUrl, autorLink, mapId, diffs, qualified, ranked, onDelete, onDownload}: MapItemProps) {
const color = useThemeColor("first-color");
const zipUrl = `https://r2cdn.beatsaver.com/${hash}.zip`
const previewUrl = `https://skystudioapps.com/bs-viewer/?url=${zipUrl}`
console.log(diffs)
const renderAutor = () => {
if(!autor){ null; }
if(autorLink){
return <BsmLink className="text-sm mb-1" href={autorLink} style={{color}}>{autor}</BsmLink>
}
return <span className="text-sm mb-1 text-gray-500">{autor}</span>
}
const renderDiff = (charac: BsvMapCharacteristic, diff: BsvMapDifficultyType) => {
const colorPill = diffColors[diff] ?? "";
return (
<li className="h-5 flex justify-center items-center gap-1 rounded-full px-2" style={{backgroundColor: colorPill}}>
<BsmIcon className="h-4 w-4" icon="bsMapDifficulty"/>
<span className="text-xs mb-[1.5px]">{diff === "ExpertPlus" ? "Expert+" : diff}</span>
</li>
)
}
return (
<li className="bg-main-color-1 rounded-md flex flex-row h-[120px] min-w-[400px] shrink overflow-hidden grow text-white basis-0">
<BsmImage className="" image={coverUrl}/>
<div className="h-full flex flex-col grow px-3 min-w-0 shrink">
<h1 className={`font-bold overflow-hidden text-ellipsis whitespace-nowrap ${mapId && "cursor-pointer hover:underline"}`} style={{textDecorationColor: color}}>{title}</h1>
{renderAutor()}
<ol className="flex pb-1 gap-2 scrollbar scrollbar-width-[4px] scrollbar-thumb-black scrollbar-track-transparent" style={{scrollbarGutter: "stable", }}>
{Array.from(diffs.entries()).map(([charac, diffs]) => diffs.map(diff => renderDiff(charac, diff)))}
</ol>
</div>
<div className="flex flex-col shrink-0 px-2 pt-1 gap-1">
<BsmButton className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:backdrop-brightness-75" iconColor={color} icon="trash" title="allo" withBar={false}/>
<BsmButton className="w-6 h-6 p-1 rounded-md !bg-inherit hover:backdrop-brightness-75" iconColor={color} icon="twitch" title="allo" withBar={false}/>
<BsmButton className="w-6 h-6 pr-[2px] rounded-md !bg-inherit hover:backdrop-brightness-75" iconColor={color} icon="play" title="allo" withBar={false}/>
<BsmLink href={previewUrl} internal>
<BsmButton className="w-6 h-6 px-[2px] rounded-md !bg-inherit hover:backdrop-brightness-75" iconColor={color} icon="eye" title="Preview" withBar={false}/>
</BsmLink>
</div>
</li>
)
}
const diffColors: Record<BsvMapDifficultyType, string> = {
Easy: "#008055",
Normal: "#1268a1",
Hard: "#bd5500",
Expert: "#b52a1c",
ExpertPlus: "#7646af"
}
@@ -1,8 +1,7 @@
import { useState } from "react"
import { useObservable } from "renderer/hooks/use-observable.hook"
import { MapsManagerService } from "renderer/services/maps-manager.service"
import { BSVersion } from "shared/bs-version.interface"
import { TabNavBar } from "../shared/tab-nav-bar.component"
import { LocalMapsListPanel } from "./local-maps-list-panel.component"
type Props = {
oneBlock?: boolean,
@@ -10,24 +9,17 @@ type Props = {
}
export function MapsPlaylistsPanel({version, oneBlock = false}: Props) {
const mapsManager = MapsManagerService.getInstance();
const [tabIndex, setTabIndex] = useState(0);
const maps = useObservable(mapsManager.getMaps(version));
console.log(maps);
return (
<>
{!oneBlock && <TabNavBar className="mb-8 w-72" tabsText={["Maps", "Playlists"]} onTabChange={setTabIndex}/>}
<div className="w-full h-full bg-main-color-1 rounded-md shadow-black shadow-md overflow-hidden">
<div className="w-full h-full bg-main-color-2 rounded-md shadow-black shadow-md overflow-hidden">
{oneBlock && <TabNavBar className="!rounded-none shadow-sm" tabsText={["Maps", "Playlists"]} onTabChange={setTabIndex}/>}
<div className="w-full h-full min-h-0 flex flex-row items-center transition-transform duration-300" style={{transform: `translate(${-(tabIndex * 100)}%, 0)`}}>
<div className="w-full h-full grow bg-red-300 shrink-0 overflow-y-scroll">
a
</div>
<div className="w-full h-full grow bg-green-300 shrink-0">b</div>
<LocalMapsListPanel className="h-full w-full grow shrink-0 flex flex-wrap justify-center content-start gap-2 p-3" version={version}/>
<div className="w-full h-full grow shrink-0">b</div>
</div>
</div>
</>
@@ -0,0 +1,9 @@
import { ModalComponent } from "renderer/services/modale.service"
export const IframeModal: ModalComponent<void, string> = ({resolver, data}) => {
return (
<div className="w-[calc(100vw-250px)] h-[calc(100vh-250px)]">
<iframe className="w-full h-full" src={data}></iframe>
</div>
)
}
@@ -25,9 +25,10 @@ type propsType = {
typeColor?:BsmButtonType,
color?: string,
title?: string,
iconColor?: string,
}
export function BsmButton({className, style, imgClassName, iconClassName, icon, image, text, type, active, withBar = true, disabled, onClickOutside, onClick, typeColor, color, title}: propsType) {
export function BsmButton({className, style, imgClassName, iconClassName, icon, image, text, type, active, withBar = true, disabled, onClickOutside, onClick, typeColor, color, title, iconColor}: propsType) {
const t = useTranslation();
const secondColor = useThemeColor("second-color");
@@ -52,7 +53,7 @@ export function BsmButton({className, style, imgClassName, iconClassName, icon,
<OutsideClickHandler onOutsideClick={e => onClickOutside && onClickOutside(e)}>
<div onClick={onClick} title={t(title)} className={`${className} overflow-hidden cursor-pointer group ${(!withBar && !disabled && (!!typeColor || !!color)) && "hover:brightness-[1.15]"} ${disabled && "brightness-75 cursor-not-allowed"} ${renderTypeColor}`} style={{...style, backgroundColor: primaryColor || color}}>
{ image && <BsmImage image={image} className={imgClassName}/> }
{ icon && <BsmIcon icon={icon} className={iconClassName ?? "h-full w-full text-gray-800 dark:text-white"}/> }
{ icon && <BsmIcon icon={icon} className={iconClassName ?? "h-full w-full text-gray-800 dark:text-white"} style={{color: iconColor}}/> }
{text && (type === "submit" ? <button type="submit" className="w-full h-full" style={{...(!!textColor && {color: textColor})}}>{t(text)}</button> : <span style={{...(!!textColor && {color: `${textColor}`})}}>{t(text)}</span>)}
{ withBar && (
<div className="absolute bottom-0 left-0 w-full h-1 bg-current" style={{color: secondColor}}>
@@ -0,0 +1,24 @@
import { LinkOpenerService } from "renderer/services/link-opener.service"
import { ModalService } from "renderer/services/modale.service";
import { IframeModal } from "../modal/modal-types/iframe-modal.component";
type Props = {
className?: string,
href?: string,
internal?: boolean
children?: React.ReactNode,
style?: React.CSSProperties
}
export function BsmLink({className, href, children, style, internal}: Props) {
const linkOpener = LinkOpenerService.getInstance();
const openLink = () => {
linkOpener.open(href, internal);
}
return (
<a className={`${className} ${href && "cursor-pointer"}`} onClick={openLink} style={style}>{children}</a>
)
}
@@ -25,9 +25,12 @@ import { ThreeDotsIcon } from "./icons/three-dots-icon.component";
import { GitHubIcon } from "./icons/github-icon.component";
import { CloseIcon } from "./icons/close-icon.component";
import { BsMapDifficultyIcon } from "./icons/bs-map-difficulty-icon.component";
import { TwitchIcon } from "./icons/twitch-icon.component";
import EyeIcon from "./icons/eye-icon.component";
import { PlayIcon } from "./icons/play-icon.component";
export type BsmIconType = (
"settings"|"trash"|"favorite"|"folder"|"bsNote"|"check"|"three-dots"|
"settings"|"trash"|"favorite"|"folder"|"bsNote"|"check"|"three-dots"|"twitch"|"eye"|"play"|
"terminal"|"desktop"|"oculus"|"add"|"cross"|"task"|"github"|"close"|
"copy"|"steam"|"edit"|"export"|"patreon"|"search"|"bsMapDifficulty"|
"fr-FR-flag"|"es-ES-flag"|"en-US-flag"|"en-EN-flag"
@@ -62,6 +65,9 @@ export const BsmIcon = memo(({className, icon, style}: {className?: string, icon
if(icon === "github"){ return <GitHubIcon className={className} style={style}/> }
if(icon === "close"){ return <CloseIcon className={className} style={style}/> }
if(icon === "bsMapDifficulty"){ return <BsMapDifficultyIcon className={className} style={style}/> }
if(icon === "twitch"){ return <TwitchIcon className={className} style={style}/> }
if(icon === "eye"){ return <EyeIcon className={className} style={style}/> }
if(icon === "play"){ return <PlayIcon className={className} style={style}/> }
return <TrashIcon className={className} style={style}/>
}
@@ -0,0 +1,9 @@
import { CSSProperties } from "react";
export default function EyeIcon(props: {className?: string, style?: CSSProperties}) {
return (
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" height="40" width="40" viewBox="0 0 40 40" fill="currentColor">
<path d="M20 26.375q3 0 5.104-2.104t2.104-5.104q0-3-2.104-5.104Q23 11.958 20 11.958t-5.104 2.105q-2.104 2.104-2.104 5.104t2.104 5.104Q17 26.375 20 26.375Zm0-2.792q-1.875 0-3.146-1.291-1.271-1.292-1.271-3.125 0-1.875 1.292-3.146T20 14.75q1.875 0 3.146 1.292 1.271 1.291 1.271 3.125 0 1.875-1.292 3.145-1.292 1.271-3.125 1.271Zm0 8.334q-5.833 0-10.625-3.146T2 20.417q-.167-.25-.229-.584-.063-.333-.063-.666 0-.334.063-.688.062-.354.229-.562 2.583-5.209 7.375-8.354Q14.167 6.417 20 6.417t10.625 3.146q4.792 3.145 7.375 8.354.167.208.229.562.063.354.063.688 0 .333-.063.666-.062.334-.229.584-2.583 5.208-7.375 8.354Q25.833 31.917 20 31.917Zm0-12.75Zm0 9.708q4.917 0 9.042-2.646t6.291-7.062q-2.166-4.417-6.291-7.063T20 9.458q-4.917 0-9.042 2.646t-6.333 7.063q2.208 4.416 6.333 7.062T20 28.875Z"/>
</svg>
)
}
@@ -0,0 +1,9 @@
import { CSSProperties } from "react";
export function PlayIcon(props: {className?: string, style?: CSSProperties}) {
return (
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" height="40" width="40" viewBox="0 0 40 40" fill="currentColor">
<path d="M15.458 30.542q-.791.541-1.604.083-.812-.458-.812-1.417V10.625q0-.958.812-1.417.813-.458 1.604.084l14.625 9.291q.75.5.75 1.355 0 .854-.75 1.312Z"/>
</svg>
)
}
@@ -0,0 +1,9 @@
import { CSSProperties } from "react";
export function TwitchIcon(props: {className?: string, style?: CSSProperties}) {
return (
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<path d="M5.7 0L1.4 10.985V55.88h15.284V64h8.597l8.12-8.12h12.418l16.716-16.716V0H5.7zm51.104 36.3L47.25 45.85H31.967l-8.12 8.12v-8.12H10.952V5.73h45.85V36.3zM47.25 16.716v16.716h-5.73V16.716h5.73zm-15.284 0v16.716h-5.73V16.716h5.73z" fill="currentColor" fillRule="evenodd"/>
</svg>
)
}
+10 -2
View File
@@ -1,10 +1,13 @@
import { IframeModal } from "renderer/components/modal/modal-types/iframe-modal.component";
import { IpcService } from "./ipc.service";
import { ModalService } from "./modale.service";
export class LinkOpenerService{
private static instance: LinkOpenerService;
private readonly ipcService: IpcService
private readonly ipcService: IpcService;
private readonly modals: ModalService;
public static getInstance(): LinkOpenerService{
if(!LinkOpenerService.instance){ LinkOpenerService.instance = new LinkOpenerService(); }
@@ -13,9 +16,14 @@ export class LinkOpenerService{
private constructor(){
this.ipcService = IpcService.getInstance();
this.modals = ModalService.getInsance();
}
public open(url: string): void{
public open(url: string, internal?: boolean): void{
if(internal){
this.modals.openModal(IframeModal, url);
return;
}
this.ipcService.sendLazy("new-window", {args: url});
}
@@ -19,6 +19,7 @@ export class MapsManagerService {
}
public getMaps(version?: BSVersion): Observable<BsmLocalMap[]>{
console.log("get maps")
return new Observable(obs => {
this.ipcService.send<BsmLocalMap[], BSVersion>("get-version-maps", {args: version}).then(res => {
if(!res.success){ return obs.next(null);}
+2 -2
View File
@@ -24,7 +24,7 @@ export class PageStateService {
}
public getRoute(): string{
return this.location$.value.pathname;
return this.location$.value?.pathname;
}
public get state$(): Observable<unknown>{
@@ -32,7 +32,7 @@ export class PageStateService {
}
public get route$(): Observable<string>{
return this.location$.pipe(map(location => location.pathname));
return this.location$.pipe(map(location => location?.pathname));
}
}
+6 -3
View File
@@ -115,10 +115,10 @@ export interface BsvMapTestplay {
export interface BsvMapDifficulty {
bombs: number,
characteristic: string,
characteristic: BsvMapCharacteristic,
chroma: boolean,
cinema: boolean,
difficulty: string,
difficulty: BsvMapDifficultyType,
events: number,
length: number,
maxScore: number,
@@ -138,4 +138,7 @@ export interface BsvMapParitySummary {
errors: number,
resets: number,
warns: number,
}
}
export type BsvMapCharacteristic = ("Standard" | "OneSaber" | "NoArrows" | "90Degree" | "360Degree" | "Lightshow" | "Lawless")
export type BsvMapDifficultyType = ("Easy" | "Normal" | "Hard" | "Expert" | "ExpertPlus")
+4 -2
View File
@@ -1,3 +1,5 @@
import { BsvMapCharacteristic, BsvMapDifficultyType } from "./beat-saver.model"
export interface RawMapInfoData<T = unknown> {
_version: string,
_songName: string,
@@ -19,12 +21,12 @@ export interface RawMapInfoData<T = unknown> {
}
export interface RawDifficultySet {
_beatmapCharacteristicName: string,
_beatmapCharacteristicName: BsvMapCharacteristic,
_difficultyBeatmaps: RawMapDifficulty[]
}
export interface RawMapDifficulty {
_difficulty: string,
_difficulty: BsvMapDifficultyType,
_difficultyRank: string,
_beatmapFilename: string,
_noteJumpMovementSpeed: number,
+2 -2
View File
@@ -6,7 +6,7 @@ module.exports = {
darkMode: 'class',
content: ['./src/renderer/**/*.tsx'],
mode: 'jit',
plugins: [require('tailwind-scrollbar-hide'), require('tailwind-scrollbar')({ nocompatible: true }), require("tailwindcss-scoped-groups")({groups: ["one"]})],
plugins: [require('tailwind-scrollbar-hide'), require('tailwind-scrollbar')({ nocompatible: true }), require("tailwindcss-scoped-groups")({groups: ["one"]}), require('ps-scrollbar-tailwind')],
theme:{
colors:{
...colors,
@@ -26,7 +26,7 @@ module.exports = {
},
boxShadow: {
'center': '0px 0px 8px 0px',
}
},
}
}
};