From 645f18002add6d495c8a9a77c9d304b3e92e6539 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Tue, 16 May 2023 00:50:47 +0200 Subject: [PATCH] [feature-136] can now search models from the download modal --- src/main/ipcs/model-saber.ipcs.ts | 9 ++ src/main/main.ts | 2 - .../local-playlists-manager.service.ts | 2 +- .../model-saber/model-saber.service.ts | 11 ++ .../download-maps-modal.component.tsx | 10 +- .../download-models-modal.component.tsx | 133 ++++++++++++++++++ .../model-item.component.tsx | 80 ++++++----- .../models-grid.component.tsx | 17 ++- .../models-panel.component.tsx | 39 ++--- .../models-tabs-navbar.component.tsx | 8 +- .../shared/bsm-select.component.tsx | 25 ++-- .../hooks/use-behavior-subject.hook.ts | 6 +- src/renderer/hooks/use-service.hook.ts | 7 + src/renderer/index.css | 22 +++ src/renderer/services/ipc.service.ts | 2 +- .../models-downloader.service.ts | 14 +- .../thrird-partys/model-saber.service.ts | 25 +++- src/renderer/windows/App.tsx | 22 ++- src/{main => shared}/helpers/url.helpers.ts | 0 src/shared/models/models/constants.ts | 5 +- src/shared/models/models/model-saber.model.ts | 3 +- src/shared/models/service.interface.ts | 3 + 22 files changed, 336 insertions(+), 109 deletions(-) create mode 100644 src/renderer/components/modal/modal-types/models/download-models-modal.component.tsx create mode 100644 src/renderer/hooks/use-service.hook.ts rename src/{main => shared}/helpers/url.helpers.ts (100%) create mode 100644 src/shared/models/service.interface.ts diff --git a/src/main/ipcs/model-saber.ipcs.ts b/src/main/ipcs/model-saber.ipcs.ts index 911d7542..6ebe54ec 100644 --- a/src/main/ipcs/model-saber.ipcs.ts +++ b/src/main/ipcs/model-saber.ipcs.ts @@ -2,6 +2,10 @@ import { ipcMain } from "electron"; import { IpcRequest } from "shared/models/ipc"; import { ModelSaberService } from "../services/thrid-party/model-saber/model-saber.service"; import { UtilsService } from "../services/utils.service"; +import { IpcService } from "../services/ipc.service"; +import { MSGetQuery } from "shared/models/models/model-saber.model"; + +const ipc = IpcService.getInstance(); ipcMain.on("ms-get-model-by-id", async (event, request: IpcRequest) => { const utils = UtilsService.getInstance(); @@ -12,4 +16,9 @@ ipcMain.on("ms-get-model-by-id", async (event, request: IpcRequest { utils.ipcSend(request.responceChannel, {success: false, error: e}); }) +}); + +ipc.on("search-models", async (req, reply) => { + const ms = ModelSaberService.getInstance(); + reply(ms.searchModels(req.args)); }); \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index d140e8e6..d194a6fd 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -106,7 +106,5 @@ else{ log.error(args?.args); }); - LocalModelsManagerService.getInstance().getModels(MSModelType.Saber, null).toPromise().catch(console.error).then(console.log); - }).catch(log.error); } \ No newline at end of file diff --git a/src/main/services/additional-content/local-playlists-manager.service.ts b/src/main/services/additional-content/local-playlists-manager.service.ts index 47339efa..4dc06018 100644 --- a/src/main/services/additional-content/local-playlists-manager.service.ts +++ b/src/main/services/additional-content/local-playlists-manager.service.ts @@ -7,7 +7,7 @@ import { RequestService } from "../request.service"; import { UtilsService } from "../utils.service"; import { LocalMapsManagerService } from "./local-maps-manager.service"; import log from "electron-log" -import { isValidUrl } from "../../helpers/url.helpers"; +import { isValidUrl } from "../../../shared/helpers/url.helpers"; import { ipcMain } from "electron"; import { WindowManagerService } from "../window-manager.service"; import { IpcRequest } from "shared/models/ipc"; diff --git a/src/main/services/thrid-party/model-saber/model-saber.service.ts b/src/main/services/thrid-party/model-saber/model-saber.service.ts index 099d85b0..229eb02c 100644 --- a/src/main/services/thrid-party/model-saber/model-saber.service.ts +++ b/src/main/services/thrid-party/model-saber/model-saber.service.ts @@ -1,3 +1,4 @@ +import { Observable } from "rxjs"; import { MSGetQuery, MSGetQueryFilterType, MSModel, MSModelPlatform } from "../../../../shared/models/models/model-saber.model"; import { ModelSaberApiService } from "./model-saber-api.service"; import log from "electron-log"; @@ -81,4 +82,14 @@ export class ModelSaberService { } + public searchModels(query: MSGetQuery): Observable{ + return new Observable(observer => { + (async () => { + const res = await this.modelSaberApi.searchModel(query); + if(res.status !== 200){ observer.error(res.status); } + observer.next(Object.values(res.data)); + })().catch(e => observer.error(e)).then(() => observer.complete()); + }); + } + } \ No newline at end of file diff --git a/src/renderer/components/modal/modal-types/download-maps-modal.component.tsx b/src/renderer/components/modal/modal-types/download-maps-modal.component.tsx index b7c831cf..fb0e194d 100644 --- a/src/renderer/components/modal/modal-types/download-maps-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/download-maps-modal.component.tsx @@ -48,7 +48,7 @@ export const DownloadMapsModal: ModalComponent { + const sortOptions: BsmSelectOption[] = (() => { return BSV_SORT_ORDER.map(sort => ({text: `beat-saver.maps-sorts.${sort}`, value: sort})); })(); @@ -135,10 +135,10 @@ export const DownloadMapsModal: ModalComponent { - setSortOrder(() => newSort as SearchOrder); + const handleSortChange = (newSort: SearchOrder) => { + setSortOrder(() => newSort); setMaps(() => []); - setSearchParams(() => ({...searchParams, sortOrder: (newSort as SearchOrder)})); + setSearchParams(() => ({...searchParams, sortOrder: (newSort)})); } const handleSearch = () => { @@ -167,7 +167,7 @@ export const DownloadMapsModal: ModalComponent setQuery(e.target.value)}/> {e.preventDefault(); handleSearch()}}/> - + handleSortChange(sort)}/>
    {maps.length === 0 ? ( diff --git a/src/renderer/components/modal/modal-types/models/download-models-modal.component.tsx b/src/renderer/components/modal/modal-types/models/download-models-modal.component.tsx new file mode 100644 index 00000000..3e51d4a7 --- /dev/null +++ b/src/renderer/components/modal/modal-types/models/download-models-modal.component.tsx @@ -0,0 +1,133 @@ +import { motion } from "framer-motion"; +import { BsmButton } from "renderer/components/shared/bsm-button.component"; +import { BsmSelect, BsmSelectOption } from "renderer/components/shared/bsm-select.component"; +import { ModalComponent } from "renderer/services/modale.service"; +import { BSVersion } from "shared/bs-version.interface"; +import BeatWaitingImg from "../../../../../../assets/images/apngs/beat-waiting.png"; +import BeatConflictImg from "../../../../../../assets/images/apngs/beat-conflict.png"; +import { BsmLocalModel } from "shared/models/models/bsm-local-model.interface"; +import { MSGetQuery, MSGetSort, MSGetSortDirection, MSModel, MSModelPlatform, MSModelType } from "shared/models/models/model-saber.model"; +import { useCallback, useMemo, useState } from "react"; +import { ModelItem } from "renderer/components/models-management/model-item.component"; +import { useService } from "renderer/hooks/use-service.hook"; +import { ModelDownload, ModelsDownloaderService } from "renderer/services/models-management/models-downloader.service"; +import { ModelSaberService } from "renderer/services/thrird-partys/model-saber.service"; +import { useBehaviorSubject } from "renderer/hooks/use-behavior-subject.hook"; +import { useTranslation } from "renderer/hooks/use-translation.hook"; +import { OsDiagnosticService } from "renderer/services/os-diagnostic.service"; +import { useObservable } from "renderer/hooks/use-observable.hook"; +import { useConstant } from "renderer/hooks/use-constant.hook"; +import { MODEL_TYPES, MS_GET_QUERY_SORTS } from "shared/models/models/constants"; +import { useOnUpdate } from "renderer/hooks/use-on-update.hook"; +import equal from "fast-deep-equal"; + +export const DownloadModelsModal: ModalComponent = ({resolver, data: { version, type }}) => { + + const modelsDownloader = useService(ModelsDownloaderService); + const modelSaber = useService(ModelSaberService); + const os = useService(OsDiagnosticService); + + const modelTypesOptions = useConstant[]>(() => MODEL_TYPES.map(type => ({ + text: type.toString(), // TODO TRANSLATE + value: type + }))); + + const querySortsOptions = useConstant[]>(() => MS_GET_QUERY_SORTS.map(sort => ({ + text: sort.toString(), // TODO TRANSLATE + value: sort + }))); + + const t = useTranslation(); + const currentDownload = useObservable(modelsDownloader.currentDownload$(), null); + const [msModels, msModels$] = useBehaviorSubject([]); + const isOnline = useObservable(os.isOnline$, true); + const [isLoading, isLoading$] = useBehaviorSubject(false); + + console.log(msModels); + + const [currentType, currentType$] = useBehaviorSubject(type); + const [currentSort, currentSort$] = useBehaviorSubject(MSGetSort.Date); + const [searhInput, searhInput$] = useBehaviorSubject(""); + const [getQuery, getQuery$] = useBehaviorSubject({ type: currentType, platform: MSModelPlatform.PC, start: 0, end: 25, sort: currentSort, sortDirection: MSGetSortDirection.Descending }); + + useOnUpdate(() => { + const sub = modelSaber.searchModels(getQuery).subscribe(models => msModels$.next([...msModels, ...models])); + return () => sub.unsubscribe(); + }, [getQuery]) + + useOnUpdate(() => { + msModels$.next([]); + + + + getQuery$.next({ + ...getQuery$.value, + start: 0, + end: 25, + type: currentType, + sort: currentSort + }); + + }, [currentType, currentSort]); + + const loadMore = () => { + console.log("load more"); + const currentQuery = getQuery; + currentQuery.start += 25; + currentQuery.end += 25; + getQuery$.next({...currentQuery}); + } + + const search = () => { + + const filters = searhInput.split(" ").join(","); + + const getQuery: MSGetQuery = { + start: 0, end: 25, + type: currentType, + sort: currentSort, + filter: modelSaber.parseFilter(searhInput) + }; + + msModels$.next([]); + getQuery$.next(getQuery); + + } + + const handleDownloadModel = useCallback((model: MSModel) => { + modelsDownloader.addModelToDownload({...model, version}); + }, []); + + + return ( +
    {e.preventDefault(); search()}}> +
    + currentType$.next(value)}/> + searhInput$.next(e.target.value)}/> + {e.preventDefault(); search()}}/> + currentSort$.next(value)}/> +
    +
      + {msModels.length === 0 ? ( +
      +  + {t(isLoading ? "Chargement des modèles" : isOnline ? "Aucun modèles trouvés" : "Pas d'internet")} {/** TODO TRANSLATE **/} +
      + ) : ( + <> + {msModels.map(model => ( + + ))} + + + )} +
    +
    + ) +} diff --git a/src/renderer/components/models-management/model-item.component.tsx b/src/renderer/components/models-management/model-item.component.tsx index d399157c..450c7697 100644 --- a/src/renderer/components/models-management/model-item.component.tsx +++ b/src/renderer/components/models-management/model-item.component.tsx @@ -12,26 +12,26 @@ 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"; +import { BsmBasicSpinner } from "../shared/bsm-basic-spinner/bsm-basic-spinner.component"; +import { isValidUrl } from "shared/helpers/url.helpers"; -type Props = { +type Props = { selected?: boolean, hash: string, - id: number, - isDownloading?: Observable - onDelete?: () => void, - onDownload?: () => void, - onCancelDownload?: () => void + id?: number, + isDownloading?: boolean, + callbackValue?: T + onDelete?: (value: T) => void, + onDownload?: (value: T) => void, + onCancelDownload?: (value: T) => void } & Partial & Partial & Omit, "id"> -export const ModelItem = memo((props: Props) => { +function modelItem(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(!props.id){ return null; } @@ -41,25 +41,39 @@ export const ModelItem = memo((props: Props) => { })(); const authorPageUrl = (() => { - if(!props.discordid){ return null; } + if(!props.discord){ return null; } const url = new URL("Profile", MODEL_SABER_URL); url.searchParams.set("user", props.discord.toString()); return url.toString(); })(); + const thumbnailUrl = (() => { + if(!props.thumbnail){ return null; } + if(isValidUrl(props.thumbnail)){ return props.thumbnail; } + const [file, ext] = props.thumbnail.split("."); + return props.download.split("/").slice(0, -1).join("/") + `/${file}.${ext.toLowerCase()}`; + })(); + + const modelTags = (() => { + if(!props.tags){ return null; } + return [...new Set(props.tags)]; + })(); + + console.log(thumbnailUrl, "la"); + 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 + buttons.push({ text: "Download", icon: "download", action: () => props.onDownload(props.callbackValue) }); // TODO TRANSLATE } if(props.onDelete){ - buttons.push({ text: "Delete", icon: "trash", action: props.onDelete }); // TODO TRANSLATE + buttons.push({ text: "Delete", icon: "trash", action: () => props.onDelete(props.callbackValue) }); // TODO TRANSLATE } if(props.onCancelDownload){ - buttons.push({ text: "Cancel download", icon: "cross", action: props.onCancelDownload }); // TODO TRANSLATE + buttons.push({ text: "Cancel download", icon: "cross", action: () => props.onCancelDownload(props.callbackValue) }); // TODO TRANSLATE } return buttons; @@ -77,28 +91,22 @@ export const ModelItem = memo((props: Props) => { // TODO TRANSLATE ALL TEXT setHovered(() => true)} onHoverEnd={() => setHovered(() => false)} onClick={props.onClick}> -
    - {!isDownloading ? ( -
    - {actionButtons().map((button, index) => ( - - {e.stopPropagation(); e.preventDefault(); button.action();}}/> - - ))} -
    - ) : ( -
    - -
    - )} -
    -
    - - {e.stopPropagation(); e.preventDefault()}}> +
    + +
    + {!props.isDownloading ? ( + actionButtons().map((button, index) => ( + {e.stopPropagation(); e.preventDefault(); button.action();}} withBar={false}/> + )) + ): ( + + )} +
    + {e.stopPropagation(); e.preventDefault()}}> {props.name} {props.author ?? ''}
      - {props.tags?.map(tag => ( + {modelTags?.map(tag => (
    • {tag}
    • ))}
    @@ -121,4 +129,8 @@ export const ModelItem = memo((props: Props) => {
    ) -}, equal); +}; + +const typedMemo: (c: T, propsAreEqual?: (prevProps: Readonly

    , nextProps: Readonly

    ) => boolean) => T = memo; + +export const ModelItem = typedMemo(modelItem, equal); diff --git a/src/renderer/components/models-management/models-grid.component.tsx b/src/renderer/components/models-management/models-grid.component.tsx index 40afea3f..caed6159 100644 --- a/src/renderer/components/models-management/models-grid.component.tsx +++ b/src/renderer/components/models-management/models-grid.component.tsx @@ -126,6 +126,13 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active} }) } + const handleDelete = (model: BsmLocalModel) => { + modelsManager.deleteModels([model], version).then(deleted => { + if(!deleted){ return; } + setModels(prev => prev.filter(m => m.hash !== model.hash)); + }); + } + const renderContent = () => { if(isLoading){ return ( @@ -151,19 +158,15 @@ export const ModelsGrid = forwardRef(({className, version, type, search, active}

      {filtredModels().map(localModel => ( m.hash === localModel.hash)} - path={localModel.path} onClick={() => handleModelClick(localModel)} + onDelete={() => handleDelete(localModel)} /> ))}
    diff --git a/src/renderer/components/models-management/models-panel.component.tsx b/src/renderer/components/models-management/models-panel.component.tsx index 21c364fb..67cbad2c 100644 --- a/src/renderer/components/models-management/models-panel.component.tsx +++ b/src/renderer/components/models-management/models-panel.component.tsx @@ -8,14 +8,22 @@ 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"; +import { useService } from "renderer/hooks/use-service.hook"; +import { ModelsDownloaderService } from "renderer/services/models-management/models-downloader.service"; export function ModelsPanel({version}: {version?: BSVersion}) { - const modelsManager = useConstant(() => ModelsManagerService.getInstance()); + + const modelsManager = useService(ModelsManagerService); + const modelDownloader = useService(ModelsDownloaderService); + const [avatarsRef, sabersRef, platformsRef, bloqsRef] = [useRef(null), useRef(null), useRef(null), useRef(null)]; - const [modelTypeTab, setModelTypeTab] = useState(ModelTabType.Avatars); + + const [modelTypeTab, setModelTypeTab] = useState(MSModelType.Avatar); + const [currentTabIndex, setCurrentTabIndex] = useState(0); + const [search, setSearch] = useState(""); - const getActiveTabRef = () => [avatarsRef, sabersRef, platformsRef, bloqsRef][modelTypeTab]; + const getActiveTabRef = () => [avatarsRef, sabersRef, platformsRef, bloqsRef][currentTabIndex]; const exportModels = () => { const selectedModels = [avatarsRef, sabersRef, platformsRef, bloqsRef].map(ref =>(ref.current?.getSelectedModels() as BsmLocalModel[])).flat() @@ -27,6 +35,10 @@ export function ModelsPanel({version}: {version?: BSVersion}) { activeTab.current?.deleteSelectedModels(); } + const openDownloadModal = () => { + modelDownloader.openDownloadModelsModal(version, modelTypeTab); + } + const threeDotsItems: DropDownItem[] = [ {text: "TODO TRANSLATE EXPORT", onClick: exportModels, icon: "export"}, {text: "TODO TRANSLATE DELETE", onClick: deleteModels, icon: "trash"}, @@ -35,29 +47,22 @@ export function ModelsPanel({version}: {version?: BSVersion}) { return (
    - + {e.preventDefault(); openDownloadModal()}}/>
    setSearch(e.target.value)} tabIndex={-1}/>
    - + {setModelTypeTab(() => tab.extra), setCurrentTabIndex(() => index)}}/> -
    - - - - +
    + + + +
    ) } - -enum ModelTabType { - Avatars = 0, - Sabers = 1, - Platforms = 2, - Bloqs = 3, -} 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 60d0b232..e19bc2d2 100644 --- a/src/renderer/components/models-management/models-tabs-navbar.component.tsx +++ b/src/renderer/components/models-management/models-tabs-navbar.component.tsx @@ -13,7 +13,7 @@ type Props = { className?: string, version?: BSVersion tabIndex: number, - onTabChange: (index: number) => void + onTabChange: (index: number, tab: BsContentNavBarTab) => void } export function ModelsTabsNavbar({className, version, tabIndex, onTabChange}: Props) { @@ -70,10 +70,10 @@ function ModelTab({version, modelType, active, onClick}: TabProps){ } return ( -
  • -
    +
  • +
    - {modelType} + {modelType}
    {!!version && ( diff --git a/src/renderer/components/shared/bsm-select.component.tsx b/src/renderer/components/shared/bsm-select.component.tsx index 8b9a87ba..0affe993 100644 --- a/src/renderer/components/shared/bsm-select.component.tsx +++ b/src/renderer/components/shared/bsm-select.component.tsx @@ -1,31 +1,30 @@ -import { ChangeEvent, CSSProperties } from "react" +import { ComponentProps } from "react" import { useTranslation } from "renderer/hooks/use-translation.hook" -type Props = { - className?: string, - style?: CSSProperties, - options?: BsmSelectOption[], - onChange?: (value: string) => void +type Props = Omit, "onChange"> & { + options?: BsmSelectOption[], + onChange?: (value: T) => void, } -export function BsmSelect({className, style, options, onChange}: Props) { +export function BsmSelect({className, style, options, onChange}: Props) { const t = useTranslation(); - const handleChange = (event: ChangeEvent) => { - onChange?.(event.target.value); + const handleChange: ComponentProps<"select">["onChange"] = (e) => { + e.preventDefault(); + onChange?.(options?.[parseInt(e.target.value)]?.value); } return ( ) } -export interface BsmSelectOption{ +export interface BsmSelectOption{ text: string, - value: string + value: T } diff --git a/src/renderer/hooks/use-behavior-subject.hook.ts b/src/renderer/hooks/use-behavior-subject.hook.ts index 2a782fd3..028ca36a 100644 --- a/src/renderer/hooks/use-behavior-subject.hook.ts +++ b/src/renderer/hooks/use-behavior-subject.hook.ts @@ -3,8 +3,8 @@ import { useObservable } from "./use-observable.hook"; import { useConstant } from "./use-constant.hook"; export function useBehaviorSubject(value: T): [T, BehaviorSubject]{ - const subject = useConstant(() => new BehaviorSubject(value)); - const subjectValue = useObservable(subject); + const subject$ = useConstant(() => new BehaviorSubject(value)); + const subjectValue = useObservable(subject$, value); - return [subjectValue, subject]; + return [subjectValue, subject$]; } \ No newline at end of file diff --git a/src/renderer/hooks/use-service.hook.ts b/src/renderer/hooks/use-service.hook.ts new file mode 100644 index 00000000..c5e16f91 --- /dev/null +++ b/src/renderer/hooks/use-service.hook.ts @@ -0,0 +1,7 @@ +import { useConstant } from "./use-constant.hook"; +import { Service } from "shared/models/service.interface"; + +export function useService(s: Service): T { + const service = useConstant(() => s.getInstance()); + return service; +} \ No newline at end of file diff --git a/src/renderer/index.css b/src/renderer/index.css index f4930e8e..ff41a982 100644 --- a/src/renderer/index.css +++ b/src/renderer/index.css @@ -52,6 +52,28 @@ animation: glowing 20s linear infinite; } +.dim > * { z-index: 1; } +.dim::before{ + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.25); +} + +.dim-on-hover:hover > * { z-index: 1; } +.dim-on-hover:hover:before{ + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.25); +} + @keyframes glowing { 0% { background-position: 0 0; } 50% { background-position: 400% 0; } diff --git a/src/renderer/services/ipc.service.ts b/src/renderer/services/ipc.service.ts index ad01c43a..a06f43d1 100644 --- a/src/renderer/services/ipc.service.ts +++ b/src/renderer/services/ipc.service.ts @@ -3,7 +3,7 @@ import { Observable } from "rxjs"; import { IpcRequest, IpcResponse } from "shared/models/ipc"; import { identity } from "rxjs"; -export class IpcService { +export class IpcService{ private static instance: IpcService; diff --git a/src/renderer/services/models-management/models-downloader.service.ts b/src/renderer/services/models-management/models-downloader.service.ts index 95b3c6a0..49d4deab 100644 --- a/src/renderer/services/models-management/models-downloader.service.ts +++ b/src/renderer/services/models-management/models-downloader.service.ts @@ -4,7 +4,7 @@ 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"; +import { DownloadModelsModal } from "renderer/components/modal/modal-types/models/download-models-modal.component"; export class ModelsDownloaderService { @@ -20,14 +20,14 @@ export class ModelsDownloaderService { 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 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)); + this._currentDownload$.pipe(filter(v => !!v)).subscribe(model => this.downloadModel(model)); } private downloadModel(model: ModelDownload){ @@ -56,18 +56,22 @@ export class ModelsDownloaderService { } public isDownloading$(model: ModelDownload): Observable{ - return this.currentDownload$.pipe(map(current => { + return this._currentDownload$.pipe(map(current => { if(!current){ return false; } return current.hash === model.hash && current.version === model.version }), distinctUntilChanged()); } + public currentDownload$(): Observable{ + return this._currentDownload$; + } + 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 + return this.modal.openModal(DownloadModelsModal, {version, type}); // TODO MODELS } } diff --git a/src/renderer/services/thrird-partys/model-saber.service.ts b/src/renderer/services/thrird-partys/model-saber.service.ts index 99b35688..ae2226db 100644 --- a/src/renderer/services/thrird-partys/model-saber.service.ts +++ b/src/renderer/services/thrird-partys/model-saber.service.ts @@ -1,5 +1,6 @@ -import { MSModel } from "shared/models/models/model-saber.model"; +import { MSGetQuery, MSGetQueryFilter, MSGetQueryFilterType, MSModel } from "shared/models/models/model-saber.model"; import { IpcService } from "../ipc.service"; +import { Observable } from "rxjs"; export class ModelSaberService { @@ -22,4 +23,26 @@ export class ModelSaberService { return res.data; } + public searchModels(query: MSGetQuery): Observable{ + return this.ipc.sendV2("search-models", {args: query}); + } + + public parseFilter(stringFilters: string): MSGetQueryFilter[]{ + return stringFilters.split(" ").map(value => { + + let trimed = value.trim(); + const isNegative = trimed.at(0) === "-"; + + if(isNegative){ + trimed = trimed.substring(0); + } + + if(!trimed.includes(":")){ + return {type: MSGetQueryFilterType.SearchName, value: trimed, isNegative} + } + + + }); + } + } \ No newline at end of file diff --git a/src/renderer/windows/App.tsx b/src/renderer/windows/App.tsx index 80ff97ff..2142804b 100644 --- a/src/renderer/windows/App.tsx +++ b/src/renderer/windows/App.tsx @@ -22,22 +22,18 @@ import { NotificationService } from "renderer/services/notification.service"; import { timer } from "rxjs"; import { ConfigurationService } from "renderer/services/configuration.service"; import { OsDiagnosticService } from "renderer/services/os-diagnostic.service"; -import { IpcService } from "../services/ipc.service"; -import { MSModelType } from "shared/models/models/model-saber.model"; -import { BsmLocalModelsProgress } from "main/services/additional-content/local-models-manager.service"; -import { Progression } from "main/helpers/fs.helpers"; -import { BsmLocalModel } from "shared/models/models/bsm-local-model.interface"; +import { useService } from "renderer/hooks/use-service.hook"; export default function App() { - const themeService = ThemeService.getInstance(); - const pageState = PageStateService.getInstance(); - const maps = MapsManagerService.getInstance(); - const playlists = PlaylistsManagerService.getInstance(); - const models = ModelsManagerService.getInstance(); - const notification = NotificationService.getInstance(); - const config = ConfigurationService.getInstance(); - const os = OsDiagnosticService.getInstance(); + useService(OsDiagnosticService); + const themeService = useService(ThemeService); + const pageState = useService(PageStateService); + const maps = useService(MapsManagerService); + const playlists = useService(PlaylistsManagerService); + const models = useService(ModelsManagerService); + const notification = useService(NotificationService); + const config = useService(ConfigurationService); const location = useLocation(); const navigate = useNavigate(); diff --git a/src/main/helpers/url.helpers.ts b/src/shared/helpers/url.helpers.ts similarity index 100% rename from src/main/helpers/url.helpers.ts rename to src/shared/helpers/url.helpers.ts diff --git a/src/shared/models/models/constants.ts b/src/shared/models/models/constants.ts index ce161cb7..684e1ad8 100644 --- a/src/shared/models/models/constants.ts +++ b/src/shared/models/models/constants.ts @@ -1,4 +1,4 @@ -import { MSModelType } from "./model-saber.model"; +import { MSGetSort, MSModelType } from "./model-saber.model"; export const MODEL_SABER_URL = "https://modelsaber.com"; @@ -23,4 +23,5 @@ export const MODEL_FILE_EXTENSIONS = { [MSModelType.Saber]: ".saber" } -export const MODEL_TYPES = Array.from(Object.values(MSModelType)); \ No newline at end of file +export const MODEL_TYPES = Array.from(Object.values(MSModelType)); +export const MS_GET_QUERY_SORTS = Array.from(Object.values(MSGetSort)); \ No newline at end of file diff --git a/src/shared/models/models/model-saber.model.ts b/src/shared/models/models/model-saber.model.ts index a4b6c68d..69b167ff 100644 --- a/src/shared/models/models/model-saber.model.ts +++ b/src/shared/models/models/model-saber.model.ts @@ -68,4 +68,5 @@ export enum MSGetQueryFilterType { DiscordID = "discordid", ID = "id", SearchName = "searchName" -} \ No newline at end of file +} + diff --git a/src/shared/models/service.interface.ts b/src/shared/models/service.interface.ts new file mode 100644 index 00000000..8e49e87e --- /dev/null +++ b/src/shared/models/service.interface.ts @@ -0,0 +1,3 @@ +export interface Service { + getInstance: () => T; +} \ No newline at end of file