mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-136] fix some bugs + prepare models download + use color libs for font contrast
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"misc": {
|
||||
"download": "Laden",
|
||||
"add": "Hinzufügen",
|
||||
"verify": "Prüfen",
|
||||
"launch": "Starten",
|
||||
"mods": "Mods",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"misc": {
|
||||
"download": "Download",
|
||||
"add": "Add",
|
||||
"verify": "Verify",
|
||||
"launch": "Launch",
|
||||
"mods": "Mods",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"misc": {
|
||||
"download": "Descargar",
|
||||
"add": "Añadir",
|
||||
"verify": "Verificar",
|
||||
"launch": "Lanzar",
|
||||
"mods": "Mods",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"misc": {
|
||||
"download": "Télécharger",
|
||||
"add": "Ajouter",
|
||||
"verify": "Vérifier",
|
||||
"launch": "Lancer",
|
||||
"mods": "Mods",
|
||||
|
||||
Generated
+46
-12
@@ -10,7 +10,9 @@
|
||||
"dependencies": {
|
||||
"@node-steam/vdf": "^2.2.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/color": "^3.0.3",
|
||||
"archiver": "^5.3.1",
|
||||
"color": "^4.2.3",
|
||||
"dateformat": "^5.0.3",
|
||||
"dot-prop": "^8.0.0",
|
||||
"electron-debug": "^3.2.0",
|
||||
@@ -2273,6 +2275,27 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg=="
|
||||
},
|
||||
"node_modules/@types/color": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.3.tgz",
|
||||
"integrity": "sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==",
|
||||
"dependencies": {
|
||||
"@types/color-convert": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/color-convert": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.0.tgz",
|
||||
"integrity": "sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==",
|
||||
"dependencies": {
|
||||
"@types/color-name": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"node_modules/@types/connect": {
|
||||
"version": "3.4.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
||||
@@ -4922,7 +4945,6 @@
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
@@ -4935,7 +4957,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
@@ -4952,7 +4973,6 @@
|
||||
"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,
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
@@ -16267,7 +16287,6 @@
|
||||
"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,
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
@@ -16275,8 +16294,7 @@
|
||||
"node_modules/simple-swizzle/node_modules/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
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "1.0.7",
|
||||
@@ -20432,6 +20450,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/color": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.3.tgz",
|
||||
"integrity": "sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==",
|
||||
"requires": {
|
||||
"@types/color-convert": "*"
|
||||
}
|
||||
},
|
||||
"@types/color-convert": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.0.tgz",
|
||||
"integrity": "sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==",
|
||||
"requires": {
|
||||
"@types/color-name": "*"
|
||||
}
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"@types/connect": {
|
||||
"version": "3.4.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
|
||||
@@ -22549,7 +22588,6 @@
|
||||
"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"
|
||||
@@ -22559,7 +22597,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
@@ -22573,7 +22610,6 @@
|
||||
"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"
|
||||
@@ -31032,7 +31068,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -31040,8 +31075,7 @@
|
||||
"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
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -229,7 +229,9 @@
|
||||
"dependencies": {
|
||||
"@node-steam/vdf": "^2.2.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/color": "^3.0.3",
|
||||
"archiver": "^5.3.1",
|
||||
"color": "^4.2.3",
|
||||
"dateformat": "^5.0.3",
|
||||
"dot-prop": "^8.0.0",
|
||||
"electron-debug": "^3.2.0",
|
||||
|
||||
@@ -75,7 +75,7 @@ ipc.on<{version: BSVersion, type: MSModelType}>("get-version-models", async (req
|
||||
|
||||
ipc.on<{version: BSVersion, models: BsmLocalModel[], outPath: string}>("export-models", async (req, reply) => {
|
||||
const models = LocalModelsManagerService.getInstance();
|
||||
reply(await models.exportModels(req.args.outPath, req.args.version, req.args.models));
|
||||
reply(models.exportModels(req.args.outPath, req.args.version, req.args.models));
|
||||
});
|
||||
|
||||
ipc.on<BsmLocalModel[]>("delete-models", async (req, reply) => {
|
||||
|
||||
@@ -167,19 +167,30 @@ export class LocalModelsManagerService {
|
||||
|
||||
}
|
||||
|
||||
public async exportModels(output: string, version?: BSVersion, models?: BsmLocalModel[]): Promise<Observable<ArchiveProgress>>{
|
||||
public exportModels(output: string, version?: BSVersion, models?: BsmLocalModel[]): Observable<ArchiveProgress>{
|
||||
// TOTO NOT ASYNC
|
||||
const archive = new Archive(output);
|
||||
if(models?.length){
|
||||
models.forEach(model => archive.addFile(model.path, path.join(MODEL_TYPE_FOLDERS[model.type], path.basename(model.path))));
|
||||
}
|
||||
else{
|
||||
for(const type of MODEL_TYPES){
|
||||
archive.addDirectory(await this.getModelFolderPath(type, version));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return archive.finalize();
|
||||
return new Observable<ArchiveProgress>(subscriber => {
|
||||
|
||||
const archive = new Archive(output);
|
||||
|
||||
(async () => {
|
||||
|
||||
if(models?.length){
|
||||
models.forEach(model => archive.addFile(model.path, path.join(MODEL_TYPE_FOLDERS[model.type], path.basename(model.path))));
|
||||
}
|
||||
else{
|
||||
for(const type of MODEL_TYPES){
|
||||
archive.addDirectory(await this.getModelFolderPath(type, version));
|
||||
}
|
||||
}
|
||||
|
||||
})().catch(e => subscriber.error(e)).then(() => (
|
||||
archive.finalize().subscribe(subscriber)
|
||||
));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public deleteModels(models: BsmLocalModel[]): Observable<Progression>{
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ export function MapsPlaylistsPanel({version}: Props) {
|
||||
<motion.div className="h-full p-0.5" variants={variants}>
|
||||
<BsmIcon className="block h-full aspect-square brightness-150" icon="add"/>
|
||||
</motion.div>
|
||||
<span className="text-sm brightness-150">{t("pages.version-viewer.maps.tabs.maps.actions.add-maps.text")}</span>
|
||||
<span className="text-sm brightness-150">{t("misc.add")}</span>
|
||||
</motion.div>
|
||||
)}
|
||||
{(!!version) && (
|
||||
|
||||
@@ -11,47 +11,60 @@ import Tippy from "@tippyjs/react";
|
||||
import { followCursor } from "tippy.js"
|
||||
import defaultImage from '../../../../assets/images/default-version-img.jpg'
|
||||
import { BsmLocalModel } from "shared/models/models/bsm-local-model.interface";
|
||||
import { BsmIconType } from "../svgs/bsm-icon.component";
|
||||
import { Observable, of } from "rxjs";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { BsmButton } from "../shared/bsm-button.component";
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
selected?: boolean,
|
||||
hash: string,
|
||||
onClick?: React.ComponentProps<"li">["onClick"],
|
||||
} & Partial<MSModel> & Partial<BsmLocalModel>
|
||||
id: number,
|
||||
isDownloading?: Observable<boolean>
|
||||
onDelete?: () => void,
|
||||
onDownload?: () => void,
|
||||
onCancelDownload?: () => void
|
||||
} & Partial<MSModel> & Partial<BsmLocalModel> & Omit<React.ComponentProps<"li">, "id">
|
||||
|
||||
export const ModelItem = memo(({
|
||||
className,
|
||||
selected,
|
||||
path,
|
||||
hash,
|
||||
id,
|
||||
type,
|
||||
name,
|
||||
thumbnail,
|
||||
author,
|
||||
discordid,
|
||||
tags,
|
||||
onClick
|
||||
}: Props) => {
|
||||
export const ModelItem = memo((props: Props) => {
|
||||
|
||||
const color = useThemeColor("first-color");
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const [idContentCopied, setIdContentCopied] = useState<string>(null);
|
||||
const isDownloading = useObservable(props.isDownloading ?? of(false), false);
|
||||
|
||||
const modelPageUrl = (() => {
|
||||
if(!id){ return null; }
|
||||
const url = new URL(MODELS_TYPE_MS_PAGE_ROOT[type], MODEL_SABER_URL);
|
||||
url.searchParams.set("id", id.toString());
|
||||
if(!props.id){ return null; }
|
||||
const url = new URL(MODELS_TYPE_MS_PAGE_ROOT[props.type], MODEL_SABER_URL);
|
||||
url.searchParams.set("id", props.id.toString());
|
||||
return url.toString();
|
||||
})();
|
||||
|
||||
const authorPageUrl = (() => {
|
||||
if(!discordid){ return null; }
|
||||
if(!props.discordid){ return null; }
|
||||
const url = new URL("Profile", MODEL_SABER_URL);
|
||||
url.searchParams.set("user", discordid.toString());
|
||||
url.searchParams.set("user", props.discord.toString());
|
||||
return url.toString();
|
||||
})();
|
||||
|
||||
const actionButtons = (): {text: string, icon: BsmIconType, action: () => void}[] => {
|
||||
const buttons: {text: string, icon: BsmIconType, action: () => void}[] = [];
|
||||
|
||||
if(props.onDownload){
|
||||
buttons.push({ text: "Download", icon: "download", action: props.onDownload }); // TODO TRANSLATE
|
||||
}
|
||||
|
||||
if(props.onDelete){
|
||||
buttons.push({ text: "Delete", icon: "trash", action: props.onDelete }); // TODO TRANSLATE
|
||||
}
|
||||
|
||||
if(props.onCancelDownload){
|
||||
buttons.push({ text: "Cancel download", icon: "cross", action: props.onCancelDownload }); // TODO TRANSLATE
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
const copyContent = (value: string, id?: string) => {
|
||||
navigator.clipboard.writeText(value);
|
||||
setIdContentCopied(() => id);
|
||||
@@ -62,30 +75,45 @@ export const ModelItem = memo(({
|
||||
|
||||
return (
|
||||
// TODO TRANSLATE ALL TEXT
|
||||
<motion.li className={`relative flex-grow min-w-[13rem] h-52 cursor-pointer ${className ?? ""}`} onHoverStart={() => setHovered(() => true)} onHoverEnd={() => setHovered(() => false)} onClick={onClick}>
|
||||
<GlowEffect visible={selected || hovered}/>
|
||||
<motion.li className={`relative flex-grow min-w-[13rem] h-52 cursor-pointer ${props.className ?? ""}`} onHoverStart={() => setHovered(() => true)} onHoverEnd={() => setHovered(() => false)} onClick={props.onClick}>
|
||||
<GlowEffect visible={props.selected || hovered}/>
|
||||
<div>
|
||||
{!isDownloading ? (
|
||||
<div>
|
||||
{actionButtons().map((button, index) => (
|
||||
<Tippy key={index} placement="top" content={button.text} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
|
||||
<BsmButton className="absolute top-0 right-0 w-6 h-6 p-1 bg-main-color-3 bg-opacity-60 backdrop-blur-md rounded-md text-white" onClick={e => {e.stopPropagation(); e.preventDefault(); button.action();}}/>
|
||||
</Tippy>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute top-0 left-0 w-full h-full rounded-lg overflow-hidden blur-none">
|
||||
<BsmImage className={`absolute top-0 left-0 w-full h-full object-cover ${type === MSModelType.Avatar ? "object-top" : ""}`} image={thumbnail} placeholder={defaultImage} loading="lazy"/>
|
||||
<BsmImage className={`absolute top-0 left-0 w-full h-full object-cover ${props.type === MSModelType.Avatar ? "object-top" : ""}`} image={props.thumbnail} placeholder={defaultImage} loading="lazy"/>
|
||||
<motion.div className="absolute cursor-default top-[80%] left-0 w-full h-full p-2 flex flex-col gap-1.5 bg-main-color-3 bg-opacity-60 backdrop-blur-md transition-all hover:top-0" onClick={e =>{e.stopPropagation(); e.preventDefault()}}>
|
||||
<BsmLink className={`block w-fit max-w-full overflow-hidden font-bold whitespace-nowrap text-ellipsis ${id ? "cursor-pointer hover:underline" : ""}`} href={modelPageUrl}>{name}</BsmLink>
|
||||
<BsmLink className={`block w-fit max-w-full overflow-hidden whitespace-nowrap text-ellipsis brightness-200 ${authorPageUrl ? "cursor-pointer hover:underline" : ""}`} style={{color}} href={authorPageUrl}>{author ?? ''}</BsmLink>
|
||||
<BsmLink className={`block w-fit max-w-full overflow-hidden font-bold whitespace-nowrap text-ellipsis ${props.id ? "cursor-pointer hover:underline" : ""}`} href={modelPageUrl}>{props.name}</BsmLink>
|
||||
<BsmLink className={`block w-fit max-w-full overflow-hidden whitespace-nowrap text-ellipsis brightness-200 ${authorPageUrl ? "cursor-pointer hover:underline" : ""}`} style={{color}} href={authorPageUrl}>{props.author ?? ''}</BsmLink>
|
||||
<ul className="flex flex-row flex-wrap gap-1">
|
||||
{tags?.map(tag => (
|
||||
{props.tags?.map(tag => (
|
||||
<li key={tag} className="inline-block mr-1 text-xs bg-white text-black px-1 rounded-full p-0.5">{tag}</li>
|
||||
))}
|
||||
</ul>
|
||||
<Tippy placement="top" content={idContentCopied === "hash" ? "Copied!" : "Copy hash"} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
|
||||
<span className="mt-0.5 cursor-copy block w-full max-w-fit overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 uppercase text-xs" onClick={() => copyContent(`${hash}`, 'hash')}>{hash}</span>
|
||||
<span className="mt-0.5 cursor-copy block w-full max-w-fit overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 uppercase text-xs" onClick={() => copyContent(`${props.hash}`, 'hash')}>{props.hash}</span>
|
||||
</Tippy>
|
||||
<div className="flex gap-1">
|
||||
{id && (
|
||||
{props.id && (
|
||||
<Tippy placement="top" content={idContentCopied === "id" ? "Copied!" : "Copy id"} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
|
||||
<span className="cursor-copy w-fit shrink-0 max-w-full overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 uppercase text-xs" onClick={() => copyContent(`${id}`, 'id')}>{id}</span>
|
||||
<span className="cursor-copy w-fit shrink-0 max-w-full overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 uppercase text-xs" onClick={() => copyContent(`${props.id}`, 'id')}>{props.id}</span>
|
||||
</Tippy>
|
||||
)}
|
||||
{path && (
|
||||
{props.path && (
|
||||
<Tippy placement="top" content={idContentCopied === "path" ? "Copied!" : "Copy path"} followCursor="horizontal" plugins={[followCursor]} hideOnClick={false}>
|
||||
<span className="cursor-copy w-fit max-w-full overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 text-xs" onClick={() => copyContent(`${path}`, 'path')}>{path}</span>
|
||||
<span className="cursor-copy w-fit max-w-full overflow-hidden whitespace-nowrap text-ellipsis bg-main-color-1 rounded-md p-1 text-xs" onClick={() => copyContent(`${props.path}`, 'path')}>{props.path}</span>
|
||||
</Tippy>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active}
|
||||
loadModels();
|
||||
},
|
||||
deleteSelectedModels: () => {
|
||||
modelsManager.deleteModels(modelsSelected, version).then(deleted => {
|
||||
modelsManager.deleteModels(!modelsSelected?.length ? models : modelsSelected, version).then(deleted => {
|
||||
if(!deleted){ return; }
|
||||
const newModels = models.filter(m => !modelsSelected.some(d => d.hash === m.hash));
|
||||
setModels(() => newModels);
|
||||
@@ -83,6 +83,7 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active}
|
||||
|
||||
const loadModels = () => {
|
||||
const modelsObs$ = modelsManager.$getModels(type, version);
|
||||
setModels(() => null);
|
||||
setModelsLoadObservable(() => modelsObs$.pipe(map(models => models?.extra), distinctUntilChanged()));
|
||||
setProgress$(modelsObs$.pipe(map(models => Math.floor((models.current / models.total) * 100)), startWith(0), distinctUntilChanged()))
|
||||
}
|
||||
|
||||
@@ -7,35 +7,23 @@ import { BsmDropdownButton, DropDownItem } from "../shared/bsm-dropdown-button.c
|
||||
import { useConstant } from "renderer/hooks/use-constant.hook";
|
||||
import { ModelsManagerService } from "renderer/services/models-management/models-manager.service";
|
||||
import { BsmLocalModel } from "shared/models/models/bsm-local-model.interface";
|
||||
import { BsmButton } from "../shared/bsm-button.component";
|
||||
|
||||
export function ModelsPanel({version}: {version?: BSVersion}) {
|
||||
|
||||
const modelsManager = useConstant(() => ModelsManagerService.getInstance());
|
||||
const [avatarsRef, sabersRef, platformsRef, bloqsRef] = [useRef(null), useRef(null), useRef(null), useRef(null)];
|
||||
const [modelTypeTab, setModelTypeTab] = useState<ModelTabType>(ModelTabType.Avatars);
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
const getSelectedModels = (ref?: MutableRefObject<any>) => {
|
||||
if(ref){ return (ref.current?.getSelectedModels() as BsmLocalModel[]); }
|
||||
return [avatarsRef, sabersRef, platformsRef, bloqsRef].map(ref =>(ref.current?.getSelectedModels() as BsmLocalModel[])).flat()
|
||||
};
|
||||
const getAllModels = (ref?: MutableRefObject<any>) => {
|
||||
if(ref){ return (ref.current?.getModels() as BsmLocalModel[]); }
|
||||
return [avatarsRef, sabersRef, platformsRef, bloqsRef].map(ref =>(ref.current?.getModels() as BsmLocalModel[])).flat();
|
||||
}
|
||||
const reloadModels = (ref?: MutableRefObject<any>) => {
|
||||
if(ref){ return (ref.current?.reloadModels()); }
|
||||
return [avatarsRef, sabersRef, platformsRef, bloqsRef].forEach(ref => ref.current?.reloadModels())
|
||||
};
|
||||
|
||||
const getActiveTabRef = (activeTab: ModelTabType) => [avatarsRef, sabersRef, platformsRef, bloqsRef][modelTypeTab];
|
||||
const getActiveTabRef = () => [avatarsRef, sabersRef, platformsRef, bloqsRef][modelTypeTab];
|
||||
|
||||
const exportModels = () => {
|
||||
modelsManager.exportModels(getSelectedModels(), version);
|
||||
const selectedModels = [avatarsRef, sabersRef, platformsRef, bloqsRef].map(ref =>(ref.current?.getSelectedModels() as BsmLocalModel[])).flat()
|
||||
modelsManager.exportModels(selectedModels, version);
|
||||
}
|
||||
|
||||
const deleteModels = () => {
|
||||
const activeTab = getActiveTabRef(modelTypeTab);
|
||||
const activeTab = getActiveTabRef();
|
||||
activeTab.current?.deleteSelectedModels();
|
||||
}
|
||||
|
||||
@@ -47,6 +35,7 @@ export function ModelsPanel({version}: {version?: BSVersion}) {
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
||||
<div className="w-full shrink-0 flex h-9 justify-center px-40 gap-2 mb-3 text-main-color-1 dark:text-white">
|
||||
<BsmButton className="flex items-center justify-center w-fit rounded-full px-2 py-1 font-bold" icon="add" text="misc.add" typeColor="primary" withBar={false}/>
|
||||
<div className="h-full rounded-full bg-light-main-color-2 dark:bg-main-color-2 grow p-[6px]">
|
||||
<input type="text" className="h-full w-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2" placeholder={"TODO TRANSLATE"} onChange={e => setSearch(e.target.value)} tabIndex={-1}/>
|
||||
</div>
|
||||
|
||||
@@ -36,11 +36,9 @@ type TabProps = {
|
||||
version?: BSVersion,
|
||||
modelType: MSModelType,
|
||||
active?: boolean,
|
||||
onLink?: (type: MSModelType) => void,
|
||||
onUnlink?: (type: MSModelType) => void,
|
||||
} & React.ComponentProps<"li">;
|
||||
|
||||
function ModelTab({version, modelType, active, onClick, onLink, onUnlink}: TabProps){
|
||||
function ModelTab({version, modelType, active, onClick}: TabProps){
|
||||
|
||||
const modelsManager = useConstant(() => ModelsManagerService.getInstance());
|
||||
|
||||
@@ -62,8 +60,8 @@ function ModelTab({version, modelType, active, onClick, onLink, onUnlink}: TabPr
|
||||
}
|
||||
}, [version]);
|
||||
|
||||
const linkModels = () => modelsManager.linkModels(modelType, version).then(() => onLink?.(modelType));
|
||||
const unlinkModels = () => modelsManager.unlinkModels(modelType, version).then(() => onUnlink?.(modelType));
|
||||
const linkModels = () => modelsManager.linkModels(modelType, version)
|
||||
const unlinkModels = () => modelsManager.unlinkModels(modelType, version)
|
||||
|
||||
const onClickLink = () => {
|
||||
if(!version){ return Promise.resolve(); }
|
||||
|
||||
@@ -11,7 +11,7 @@ export function BsmCheckbox({className, checked, onChange, disabled} : Props) {
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const checkedColor = useThemeColor("first-color");
|
||||
const iconColor = getCorrectTextColor(checkedColor);
|
||||
const iconColor = checkedColor && getCorrectTextColor(checkedColor);
|
||||
|
||||
const handleClick = () => {
|
||||
if(disabled){ return; }
|
||||
|
||||
@@ -17,7 +17,7 @@ type Props = {
|
||||
export function BsmRange({colorType = "first-color", values, onChange, onFinalChange, min, max, renderLabel, step = 1, colors} : Props) {
|
||||
|
||||
const color = useThemeColor(colorType);
|
||||
const labelTextColor = getCorrectTextColor(color);
|
||||
const labelTextColor = color && getCorrectTextColor(color);
|
||||
|
||||
return (
|
||||
<Range
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import Color from "color";
|
||||
|
||||
/**
|
||||
* Return text color for best constrast with background color
|
||||
* @param {string} bgHex
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getCorrectTextColor(bgHex: string): string{
|
||||
const hex = bgHex.replaceAll("#", "");
|
||||
const uicolors = [parseInt(hex.substring(0, 2), 16) / 255, parseInt(hex.substring(0, 2), 16) / 255, parseInt(hex.substring(0, 2), 16) / 255];
|
||||
const c = uicolors.map(col => {
|
||||
if (col <= 0.03928) { return col / 12.92; }
|
||||
return (col + 0.055) / 1.055 ** 2.4;
|
||||
});
|
||||
const L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
|
||||
return (L > 0.128) ? "#000" : "fff";
|
||||
if(!bgHex){ return "#000"; }
|
||||
const color = Color(bgHex);
|
||||
return color.isLight() ? "#000" : "fff";
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { BehaviorSubject, Observable, shareReplay } from "rxjs";
|
||||
import { DefaultConfigKey, defaultConfiguration } from "renderer/config/default-configuration.config";
|
||||
|
||||
export class ConfigurationService {
|
||||
@@ -40,9 +40,9 @@ export class ConfigurationService {
|
||||
this.emitChange(key);
|
||||
}
|
||||
|
||||
public watch<T>(key: DefaultConfigKey | string): BehaviorSubject<T>{
|
||||
public watch<T>(key: DefaultConfigKey | string): Observable<T>{
|
||||
if(this.observers.has(key)){ return this.observers.get(key); }
|
||||
this.observers.set(key, new BehaviorSubject(this.get(key)));
|
||||
return this.observers.get(key);
|
||||
return this.observers.get(key).asObservable();
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export class MapsDownloaderService {
|
||||
while(this.mapsQueue$.value.at(0)){
|
||||
const toDownload = this.mapsQueue$.value.at(0);
|
||||
this.currentDownload$.next(toDownload);
|
||||
const downloaded = await this.downloadMap(toDownload.map, toDownload.version).toPromise();
|
||||
const downloaded = await this.downloadMap(toDownload.map, toDownload.version)?.toPromise();
|
||||
|
||||
if(downloaded){
|
||||
this.downloadedListerners.forEach(func => func(downloaded, toDownload.version));
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { BehaviorSubject, Observable, Subscription, distinctUntilChanged, filter, map, shareReplay } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BsmLocalModel } from "shared/models/models/bsm-local-model.interface";
|
||||
import { MSModel, MSModelType } from "shared/models/models/model-saber.model";
|
||||
import { ModalResponse, ModalService } from "../modale.service";
|
||||
import { IpcService } from "../ipc.service";
|
||||
import { DownloadMapsModal } from "renderer/components/modal/modal-types/download-maps-modal.component";
|
||||
|
||||
export class ModelsDownloaderService {
|
||||
|
||||
private static instance: ModelsDownloaderService;
|
||||
|
||||
public static getInstance(): ModelsDownloaderService{
|
||||
if(!ModelsDownloaderService.instance){ ModelsDownloaderService.instance = new ModelsDownloaderService(); }
|
||||
return ModelsDownloaderService.instance;
|
||||
}
|
||||
|
||||
private readonly ipc: IpcService;
|
||||
private readonly modal: ModalService
|
||||
|
||||
private readonly lastDownloadedModel$ = new BehaviorSubject<ModelDownload>(null);
|
||||
private readonly queue$ = new BehaviorSubject<ModelDownload[]>([]);
|
||||
private readonly currentDownload$ = this.queue$.pipe(map(queue => queue.at(0)), distinctUntilChanged(), shareReplay(1));
|
||||
|
||||
private constructor(){
|
||||
|
||||
this.ipc = IpcService.getInstance();
|
||||
this.modal = ModalService.getInsance();
|
||||
|
||||
this.currentDownload$.pipe(filter(v => !!v)).subscribe(model => this.downloadModel(model));
|
||||
}
|
||||
|
||||
private downloadModel(model: ModelDownload){
|
||||
// TODO: Download model
|
||||
}
|
||||
|
||||
public addModelToDownload(model: ModelDownload): ModelDownload{
|
||||
const queue = this.queue$.value;
|
||||
queue.push(model);
|
||||
this.queue$.next(queue);
|
||||
return model;
|
||||
}
|
||||
|
||||
public removeFromDownloadQueue(model: ModelDownload){
|
||||
const queue = this.queue$.value;
|
||||
const index = queue.findIndex(m => m.hash === model.hash);
|
||||
|
||||
if(index === -1){ return; }
|
||||
|
||||
queue.splice(index, 1);
|
||||
this.queue$.next(queue);
|
||||
}
|
||||
|
||||
public onModelsDownloaded$(cb: (model: ModelDownload) => void): Subscription{
|
||||
return this.lastDownloadedModel$.pipe(filter(v => !!v)).subscribe(cb);
|
||||
}
|
||||
|
||||
public isDownloading$(model: ModelDownload): Observable<boolean>{
|
||||
return this.currentDownload$.pipe(map(current => {
|
||||
if(!current){ return false; }
|
||||
return current.hash === model.hash && current.version === model.version
|
||||
}), distinctUntilChanged());
|
||||
}
|
||||
|
||||
public isPending$(model: ModelDownload): Observable<boolean>{
|
||||
return this.queue$.pipe(map(queue => queue.at(0).hash !== model.hash && queue.at(0).version !== model.version && queue.some(m => m.hash === model.hash && m.version === model.version)), distinctUntilChanged());
|
||||
}
|
||||
|
||||
public openDownloadModelsModal(version: BSVersion, type?: MSModelType): Promise<ModalResponse<void>>{
|
||||
return this.modal.openModal(DownloadMapsModal, {version: null, ownedMaps: []}); // TODO MODELS
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type ModelDownload = Partial<MSModel> & Pick<MSModel, "hash"> & {version: BSVersion};
|
||||
export type ModelDownloaded = BsmLocalModel & {version: BSVersion};
|
||||
@@ -131,6 +131,8 @@ export class ModelsManagerService {
|
||||
|
||||
public async deleteModels(models: BsmLocalModel[], version?: BSVersion): Promise<boolean>{
|
||||
|
||||
if(!models){ return Promise.resolve(false); }
|
||||
|
||||
const askModal = models.length > 1 || !this.config.get<boolean>(ModelsManagerService.REMEMBER_CHOICE_DELETE_MODEL_KEY);
|
||||
|
||||
if(askModal){
|
||||
|
||||
Reference in New Issue
Block a user