From eb9775c89f3d887aace82caeeef326052436c9ba Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Sun, 14 May 2023 12:55:37 +0200 Subject: [PATCH] [feature-136] fix some bugs + prepare models download + use color libs for font contrast --- assets/jsons/translations/de.json | 1 + assets/jsons/translations/en.json | 1 + assets/jsons/translations/es.json | 1 + assets/jsons/translations/fr.json | 1 + package-lock.json | 58 +++++++++--- package.json | 2 + src/main/ipcs/bs-model-ipcs.ts | 2 +- .../local-models-manager.service.ts | 33 ++++--- .../maps-playlists-panel.component.tsx | 2 +- .../model-item.component.tsx | 94 ++++++++++++------- .../models-grid.component.tsx | 3 +- .../models-panel.component.tsx | 23 ++--- .../models-tabs-navbar.component.tsx | 8 +- .../shared/bsm-checkbox.component.tsx | 2 +- .../components/shared/bsm-range.component.tsx | 2 +- src/renderer/helpers/correct-text-color.ts | 13 +-- .../services/configuration.service.ts | 6 +- .../services/maps-downloader.service.ts | 2 +- .../models-downloader.service.ts | 76 +++++++++++++++ .../models-manager.service.ts | 2 + 20 files changed, 237 insertions(+), 95 deletions(-) create mode 100644 src/renderer/services/models-management/models-downloader.service.ts diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 36a61941..c1559641 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -1,6 +1,7 @@ { "misc": { "download": "Laden", + "add": "Hinzufügen", "verify": "Prüfen", "launch": "Starten", "mods": "Mods", diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 5e9fe0b5..f3fda60a 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -1,6 +1,7 @@ { "misc": { "download": "Download", + "add": "Add", "verify": "Verify", "launch": "Launch", "mods": "Mods", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index d2265272..b2edb914 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -1,6 +1,7 @@ { "misc": { "download": "Descargar", + "add": "Añadir", "verify": "Verificar", "launch": "Lanzar", "mods": "Mods", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 079a564b..82a9777a 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -1,6 +1,7 @@ { "misc": { "download": "Télécharger", + "add": "Ajouter", "verify": "Vérifier", "launch": "Lancer", "mods": "Mods", diff --git a/package-lock.json b/package-lock.json index 69d5aef1..7f253cbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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==" } } }, diff --git a/package.json b/package.json index 1df99fd6..6354d0f0 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/main/ipcs/bs-model-ipcs.ts b/src/main/ipcs/bs-model-ipcs.ts index 2f549a52..19fd4a1f 100644 --- a/src/main/ipcs/bs-model-ipcs.ts +++ b/src/main/ipcs/bs-model-ipcs.ts @@ -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("delete-models", async (req, reply) => { diff --git a/src/main/services/additional-content/local-models-manager.service.ts b/src/main/services/additional-content/local-models-manager.service.ts index f9b3820f..df74310a 100644 --- a/src/main/services/additional-content/local-models-manager.service.ts +++ b/src/main/services/additional-content/local-models-manager.service.ts @@ -167,19 +167,30 @@ export class LocalModelsManagerService { } - public async exportModels(output: string, version?: BSVersion, models?: BsmLocalModel[]): Promise>{ + public exportModels(output: string, version?: BSVersion, models?: BsmLocalModel[]): Observable{ // 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(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{ diff --git a/src/renderer/components/maps-mangement-components/maps-playlists-panel.component.tsx b/src/renderer/components/maps-mangement-components/maps-playlists-panel.component.tsx index dddebbe5..5124c5ff 100644 --- a/src/renderer/components/maps-mangement-components/maps-playlists-panel.component.tsx +++ b/src/renderer/components/maps-mangement-components/maps-playlists-panel.component.tsx @@ -108,7 +108,7 @@ export function MapsPlaylistsPanel({version}: Props) { - {t("pages.version-viewer.maps.tabs.maps.actions.add-maps.text")} + {t("misc.add")} )} {(!!version) && ( diff --git a/src/renderer/components/models-management/model-item.component.tsx b/src/renderer/components/models-management/model-item.component.tsx index 9d8aa97a..d399157c 100644 --- a/src/renderer/components/models-management/model-item.component.tsx +++ b/src/renderer/components/models-management/model-item.component.tsx @@ -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 & Partial + id: number, + isDownloading?: Observable + onDelete?: () => void, + onDownload?: () => void, + onCancelDownload?: () => void +} & Partial & Partial & Omit, "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(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 - setHovered(() => true)} onHoverEnd={() => setHovered(() => false)} onClick={onClick}> - + setHovered(() => true)} onHoverEnd={() => setHovered(() => false)} onClick={props.onClick}> + +
+ {!isDownloading ? ( +
+ {actionButtons().map((button, index) => ( + + {e.stopPropagation(); e.preventDefault(); button.action();}}/> + + ))} +
+ ) : ( +
+ +
+ )} +
- + {e.stopPropagation(); e.preventDefault()}}> - {name} - {author ?? ''} + {props.name} + {props.author ?? ''}
    - {tags?.map(tag => ( + {props.tags?.map(tag => (
  • {tag}
  • ))}
- copyContent(`${hash}`, 'hash')}>{hash} + copyContent(`${props.hash}`, 'hash')}>{props.hash}
- {id && ( + {props.id && ( - copyContent(`${id}`, 'id')}>{id} + copyContent(`${props.id}`, 'id')}>{props.id} )} - {path && ( + {props.path && ( - copyContent(`${path}`, 'path')}>{path} + copyContent(`${props.path}`, 'path')}>{props.path} )}
diff --git a/src/renderer/components/models-management/models-grid.component.tsx b/src/renderer/components/models-management/models-grid.component.tsx index 2c318378..40afea3f 100644 --- a/src/renderer/components/models-management/models-grid.component.tsx +++ b/src/renderer/components/models-management/models-grid.component.tsx @@ -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())) } diff --git a/src/renderer/components/models-management/models-panel.component.tsx b/src/renderer/components/models-management/models-panel.component.tsx index ac548872..21c364fb 100644 --- a/src/renderer/components/models-management/models-panel.component.tsx +++ b/src/renderer/components/models-management/models-panel.component.tsx @@ -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.Avatars); const [search, setSearch] = useState(""); - - const getSelectedModels = (ref?: MutableRefObject) => { - 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) => { - 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) => { - 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 (
+
setSearch(e.target.value)} tabIndex={-1}/>
diff --git a/src/renderer/components/models-management/models-tabs-navbar.component.tsx b/src/renderer/components/models-management/models-tabs-navbar.component.tsx index af88a984..60d0b232 100644 --- a/src/renderer/components/models-management/models-tabs-navbar.component.tsx +++ b/src/renderer/components/models-management/models-tabs-navbar.component.tsx @@ -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(); } diff --git a/src/renderer/components/shared/bsm-checkbox.component.tsx b/src/renderer/components/shared/bsm-checkbox.component.tsx index 6f420dbf..e08aa3a3 100644 --- a/src/renderer/components/shared/bsm-checkbox.component.tsx +++ b/src/renderer/components/shared/bsm-checkbox.component.tsx @@ -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; } diff --git a/src/renderer/components/shared/bsm-range.component.tsx b/src/renderer/components/shared/bsm-range.component.tsx index 35a55ab7..48039d7c 100644 --- a/src/renderer/components/shared/bsm-range.component.tsx +++ b/src/renderer/components/shared/bsm-range.component.tsx @@ -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 ( { - 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"; } \ No newline at end of file diff --git a/src/renderer/services/configuration.service.ts b/src/renderer/services/configuration.service.ts index a4a6077e..e8707a6f 100644 --- a/src/renderer/services/configuration.service.ts +++ b/src/renderer/services/configuration.service.ts @@ -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(key: DefaultConfigKey | string): BehaviorSubject{ + public watch(key: DefaultConfigKey | string): Observable{ 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(); } } \ No newline at end of file diff --git a/src/renderer/services/maps-downloader.service.ts b/src/renderer/services/maps-downloader.service.ts index ddeb3db8..f3fd1ddf 100644 --- a/src/renderer/services/maps-downloader.service.ts +++ b/src/renderer/services/maps-downloader.service.ts @@ -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)); diff --git a/src/renderer/services/models-management/models-downloader.service.ts b/src/renderer/services/models-management/models-downloader.service.ts new file mode 100644 index 00000000..95b3c6a0 --- /dev/null +++ b/src/renderer/services/models-management/models-downloader.service.ts @@ -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(null); + private readonly queue$ = new BehaviorSubject([]); + 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{ + 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{ + 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>{ + return this.modal.openModal(DownloadMapsModal, {version: null, ownedMaps: []}); // TODO MODELS + } + +} + +export type ModelDownload = Partial & Pick & {version: BSVersion}; +export type ModelDownloaded = BsmLocalModel & {version: BSVersion}; \ No newline at end of file diff --git a/src/renderer/services/models-management/models-manager.service.ts b/src/renderer/services/models-management/models-manager.service.ts index 2ddf02d3..20776aea 100644 --- a/src/renderer/services/models-management/models-manager.service.ts +++ b/src/renderer/services/models-management/models-manager.service.ts @@ -131,6 +131,8 @@ export class ModelsManagerService { public async deleteModels(models: BsmLocalModel[], version?: BSVersion): Promise{ + if(!models){ return Promise.resolve(false); } + const askModal = models.length > 1 || !this.config.get(ModelsManagerService.REMEMBER_CHOICE_DELETE_MODEL_KEY); if(askModal){