diff --git a/src/main/services/additional-content/local-maps-manager.service.ts b/src/main/services/additional-content/local-maps-manager.service.ts
index 0fdf040c..b30349aa 100644
--- a/src/main/services/additional-content/local-maps-manager.service.ts
+++ b/src/main/services/additional-content/local-maps-manager.service.ts
@@ -7,7 +7,7 @@ import { InstallationLocationService } from "../installation-location.service";
import { UtilsService } from "../utils.service";
import crypto from "crypto";
import { lstatSync } from "fs";
-import { copy, createReadStream, ensureDir, realpath, unlink } from "fs-extra";
+import { copy, createReadStream, ensureDir, pathExists, realpath, unlink } from "fs-extra";
import StreamZip from "node-stream-zip";
import { RequestService } from "../request.service";
import sanitize from "sanitize-filename";
@@ -255,10 +255,12 @@ export class LocalMapsManagerService {
const mapFolderName = sanitize(`${map.id}-${map.name}`);
const mapsFolder = await this.getMapsFolderPath(version);
- const installedMap = await this.loadMapInfoFromPath(path.join(mapsFolder, mapFolderName)).catch(e => {
- log.error("loadMapInfoFromPath", e);
- return null;
- });
+ const mapPath = path.join(mapsFolder, mapFolderName);
+
+ const installedMap = await pathExists(mapPath).then(exists => {
+ if(!exists){ return null; }
+ return this.loadMapInfoFromPath(mapPath);
+ }).catch(() => null);
if(map.versions.every(version => version.hash === installedMap?.hash)) {
return installedMap;
@@ -270,8 +272,6 @@ export class LocalMapsManagerService {
throw `Cannot download ${zipUrl}`;
}
- const mapPath = path.join(mapsFolder, mapFolderName);
-
await ensureFolderExist(mapPath);
await zip.extract(null, mapPath);
diff --git a/src/renderer/components/available-versions/available-versions-slider.component.tsx b/src/renderer/components/available-versions/available-versions-slider.component.tsx
index 89255959..02f45b9f 100644
--- a/src/renderer/components/available-versions/available-versions-slider.component.tsx
+++ b/src/renderer/components/available-versions/available-versions-slider.component.tsx
@@ -8,7 +8,7 @@ import { useObservable } from "renderer/hooks/use-observable.hook";
export function AvailableVersionsSlider() {
const versionManagerService = useService(BSVersionManagerService);
- const availableVersions = useObservable(versionManagerService.availableVersions$);
+ const availableVersions = useObservable(() => versionManagerService.availableVersions$);
const [yearIndex, setYearIndex] = useState(0);
const availableYears = (() => {
diff --git a/src/renderer/components/maps-mangement-components/link-button.component.tsx b/src/renderer/components/maps-mangement-components/link-button.component.tsx
index bc8d1d6c..9526fceb 100644
--- a/src/renderer/components/maps-mangement-components/link-button.component.tsx
+++ b/src/renderer/components/maps-mangement-components/link-button.component.tsx
@@ -1,48 +1,56 @@
import { motion } from "framer-motion";
-import { forwardRef } from "react";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { BsmIcon } from "../svgs/bsm-icon.component";
import { useTranslation } from "renderer/hooks/use-translation.hook";
+import { FolderLinkState } from "renderer/services/version-folder-linker.service";
-type Props = {
+export type LinkBtnProps = {
className?: string;
title?: string;
- linked?: boolean;
- disabled?: boolean;
- onClick?: () => void;
+ state: FolderLinkState;
+ onClick?: () => unknown;
};
-export const LinkButton = motion(
- forwardRef((props: Props, ref) => {
- const t = useTranslation();
- const color = useThemeColor("first-color");
+export const LinkButton = ({className, title, state, onClick}: LinkBtnProps) => {
+ const t = useTranslation();
+
+ const color = useThemeColor("first-color");
+ const disabled = state === FolderLinkState.Processing || state === FolderLinkState.Pending;
- const linkedColor = (() => {
- if (props.disabled) {
- return "orange";
- }
- if (props.linked) {
+ const btnColor = () => {
+ switch (state) {
+ case FolderLinkState.Linked:
return color;
- }
- return "red";
- })();
+ case FolderLinkState.Pending:
+ case FolderLinkState.Processing:
+ return "orange";
+ case FolderLinkState.Unlinked:
+ return "red";
+ default:
+ return "red";
+ }
+ }
- return (
-
{
- e.preventDefault();
- e.stopPropagation();
- !props.disabled && props.onClick?.();
- }}
- style={{ pointerEvents: props.disabled ? "none" : "auto" }}
- tabIndex={-1}
- >
-
-
-
- );
- })
-);
+ const handleClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ !disabled && onClick?.();
+ }
+
+ return (
+
+
+
+
+ );
+}
diff --git a/src/renderer/components/maps-mangement-components/local-maps-list-panel.component.tsx b/src/renderer/components/maps-mangement-components/local-maps-list-panel.component.tsx
index e7af8ece..a6f66cf9 100644
--- a/src/renderer/components/maps-mangement-components/local-maps-list-panel.component.tsx
+++ b/src/renderer/components/maps-mangement-components/local-maps-list-panel.component.tsx
@@ -429,7 +429,7 @@ export const LocalMapsListPanel = forwardRef(({ version, className, filter, sear
return (
- 108} itemCount={preppedMaps.length} itemData={preppedMaps} layout="vertical" style={{ scrollbarGutter: "stable both-edges" }} itemKey={(i, data) => data[i].map(map => map.hash).join()}>
+ 108} itemCount={preppedMaps.length} itemData={preppedMaps} layout="vertical" style={{ scrollbarGutter: "stable both-edges" }} itemKey={(i, data) => data[i].map(map => map.hash).join()}>
{props => }
diff --git a/src/renderer/components/maps-mangement-components/map-item.component.tsx b/src/renderer/components/maps-mangement-components/map-item.component.tsx
index b71ccd33..49242a6f 100644
--- a/src/renderer/components/maps-mangement-components/map-item.component.tsx
+++ b/src/renderer/components/maps-mangement-components/map-item.component.tsx
@@ -70,7 +70,7 @@ export const MapItem = memo(({ hash, title, autor, songAutor, coverUrl, songUrl,
onDoubleClick: () => onDoubleClick?.(callBackParam),
});
- const songPlaying = useObservable(audioPlayer.playing$.pipe(map(playing => playing && audioPlayer.src === songUrl)));
+ const songPlaying = useObservable(() => audioPlayer.playing$.pipe(map(playing => playing && audioPlayer.src === songUrl)));
const previewUrl = mapId ? `https://allpoland.github.io/ArcViewer/?id=${mapId}` : null;
const mapUrl = mapId ? `https://beatsaver.com/maps/${mapId}` : null;
@@ -191,8 +191,8 @@ export const MapItem = memo(({ hash, title, autor, songAutor, coverUrl, songUrl,
-
-
+
+
{title}
@@ -240,7 +240,7 @@ export const MapItem = memo(({ hash, title, autor, songAutor, coverUrl, songUrl,
-
+
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 db26ff29..3705f055 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
@@ -1,24 +1,22 @@
-import { DetailedHTMLProps, useEffect, useRef, useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { BSVersion } from "shared/bs-version.interface";
-import { TabNavBar } from "../shared/tab-nav-bar.component";
import { LocalMapsListPanel } from "./local-maps-list-panel.component";
import { BsmDropdownButton, DropDownItem } from "../shared/bsm-dropdown-button.component";
import { FilterPanel } from "./filter-panel.component";
import { MapFilter } from "shared/models/maps/beat-saver.model";
-import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { MapsManagerService } from "renderer/services/maps-manager.service";
-import { motion, Variants } from "framer-motion";
import { MapsDownloaderService } from "renderer/services/maps-downloader.service";
import { BsmImage } from "../shared/bsm-image.component";
import wipGif from "../../../../assets/images/gifs/wip.gif";
-import { OsDiagnosticService } from "renderer/services/os-diagnostic.service";
-import { useObservable } from "renderer/hooks/use-observable.hook";
-import { BsmIcon } from "../svgs/bsm-icon.component";
import { useTranslation } from "renderer/hooks/use-translation.hook";
-import { LinkButton } from "./link-button.component";
-import { debounceTime } from "rxjs/operators";
-import { VersionFolderLinkerService, VersionLinkerActionListener } from "renderer/services/version-folder-linker.service";
+import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionListener } from "renderer/services/version-folder-linker.service";
import { useService } from "renderer/hooks/use-service.hook";
+import { BsContentTabPanel } from "../shared/bs-content-tab-panel/bs-content-tab-panel.component";
+import { BsmButton } from "../shared/bsm-button.component";
+import { MapIcon } from "../svgs/icons/map-icon.component";
+import { PlaylistIcon } from "../svgs/icons/playlist-icon.component";
+import { useObservable } from "renderer/hooks/use-observable.hook";
+import { of } from "rxjs";
type Props = {
version?: BSVersion;
@@ -29,7 +27,6 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
const mapsService = useService(MapsManagerService);
const mapsDownloader = useService(MapsDownloaderService);
- const osDiagnostic = useService(OsDiagnosticService);
const linker = useService(VersionFolderLinkerService);
const [tabIndex, setTabIndex] = useState(0);
@@ -37,9 +34,7 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
const [mapSearch, setMapSearch] = useState("");
const [playlistSearch, setPlaylistSearch] = useState("");
const [mapsLinked, setMapsLinked] = useState(false);
- const [linkingPending, setLinkingPending] = useState(false);
- const isOnline = useObservable(osDiagnostic.isOnline$);
- const color = useThemeColor("first-color");
+ const mapsLinkedState = useObservable(() => version ? mapsService.$mapsFolderLinkState(version) : of(null), FolderLinkState.Unlinked, [version]);
const t = useTranslation();
const mapsRef = useRef
();
@@ -50,8 +45,6 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
loadMapIsLinked();
- const sub = mapsService.$mapsLinkingPending(version).pipe(debounceTime(50)).subscribe(setLinkingPending);
-
const onMapsLinked: VersionLinkerActionListener = action => {
if (!action.relativeFolder.includes(MapsManagerService.RELATIVE_MAPS_FOLDER)) {
return;
@@ -63,7 +56,6 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
linker.onVersionFolderUnlinked(onMapsLinked);
return () => {
- sub.unsubscribe();
linker.removeVersionFolderLinkedListener(onMapsLinked);
linker.removeVersionFolderUnlinkedListener(onMapsLinked);
};
@@ -80,6 +72,10 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
return setPlaylistSearch(() => value);
};
+ const handleMapsAddClick = () => {
+ mapsDownloader.openDownloadMapModal(version, mapsRef.current.getMaps?.());
+ };
+
const handleMapsLinkClick = () => {
if (!mapsLinked) {
return mapsService.linkVersion(version);
@@ -87,56 +83,6 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
return mapsService.unlinkVersion(version);
};
- const handleMapsAddClick = () => {
- mapsDownloader.openDownloadMapModal(version, mapsRef.current.getMaps?.());
- };
-
- const renderTab = (props: DetailedHTMLProps, HTMLLIElement>, text: string, index: number): JSX.Element => {
- const onClickLink = (index: number) => {
- if (index === 0) {
- handleMapsLinkClick();
- }
- };
-
- const onClickAdd = (index: number) => {
- if (index === 0) {
- handleMapsAddClick();
- }
- };
-
- const variants: Variants = { hover: { rotate: 22.5 }, tap: { rotate: 45 } };
-
- return (
-
- {text}
- {index === 0 && (
-
- {isOnline && (
- {
- e.stopPropagation();
- onClickAdd(index);
- }}
- tabIndex={-1}
- >
-
-
-
-
- {t("misc.add")}
-
- )}
- {!!version && onClickLink(index)} />}
-
- )}
-
- );
- };
-
const dropDownItems = ((): DropDownItem[] => {
if (tabIndex === 1) {
return [];
@@ -150,6 +96,15 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
return (
-
-
-
+
setTabIndex(index)}
+ tabs={[
+ {
+ text: "misc.maps",
+ icon: MapIcon,
+ onClick: () => setTabIndex(0),
+ linkProps: version ? {
+ state: mapsLinkedState,
+ onClick: handleMapsLinkClick,
+ } : null,
+ },
+ {
+ text: "misc.playlists",
+ icon: PlaylistIcon,
+ onClick: () => setTabIndex(1),
+ },
+ ]}
+ >
+ <>
Coming soon
-
-
+ >
+
);
}
diff --git a/src/renderer/components/maps-mangement-components/maps-row.component.tsx b/src/renderer/components/maps-mangement-components/maps-row.component.tsx
index e92d4ac2..0e99fd27 100644
--- a/src/renderer/components/maps-mangement-components/maps-row.component.tsx
+++ b/src/renderer/components/maps-mangement-components/maps-row.component.tsx
@@ -16,13 +16,10 @@ type Props = {
};
export const MapsRow = memo(({ maps, style, selectedMaps$, onMapSelect, onMapDelete }: Props) => {
- const selectedMaps = useObservable(
- selectedMaps$.pipe(
+ const selectedMaps = useObservable(() => selectedMaps$.pipe(
map(selectedMaps => selectedMaps.filter(selected => maps.some(map => map.hash === selected.hash))),
- distinctUntilChanged(equal)
- ),
- []
- );
+ distinctUntilChanged(equal),
+ ), []);
const extractMapDiffs = (map: BsmLocalMap): Map => {
const res = new Map();
@@ -48,11 +45,30 @@ export const MapsRow = memo(({ maps, style, selectedMaps$, onMapSelect, onMapDel
};
const renderMapItem = (map: BsmLocalMap) => {
- return selected.hash === map.hash)} diffs={extractMapDiffs(map)} mapId={map.bsaverInfo?.id} ranked={map.bsaverInfo?.ranked} autorId={map.bsaverInfo?.uploader?.id} likes={map.bsaverInfo?.stats?.upvotes} createdAt={map.bsaverInfo?.createdAt} onDelete={onMapDelete} onSelected={onMapSelect} callBackParam={map} />;
+ return selected.hash === map.hash)}
+ diffs={extractMapDiffs(map)} mapId={map.bsaverInfo?.id}
+ ranked={map.bsaverInfo?.ranked}
+ autorId={map.bsaverInfo?.uploader?.id}
+ likes={map.bsaverInfo?.stats?.upvotes}
+ createdAt={map.bsaverInfo?.createdAt}
+ onDelete={onMapDelete}
+ onSelected={onMapSelect}
+ callBackParam={map}
+ />;
};
return (
-
+
{maps?.map(renderMapItem)}
);
diff --git a/src/renderer/components/modal/modal-types/bs-downgrade/login-to-steam-modal.component.tsx b/src/renderer/components/modal/modal-types/bs-downgrade/login-to-steam-modal.component.tsx
index ef511bf6..5d4169d2 100644
--- a/src/renderer/components/modal/modal-types/bs-downgrade/login-to-steam-modal.component.tsx
+++ b/src/renderer/components/modal/modal-types/bs-downgrade/login-to-steam-modal.component.tsx
@@ -21,7 +21,7 @@ export const LoginToSteamModal: ModalComponent<
const [password, setPassword] = useState("");
const [stay, setStay] = useState(false);
const [showPassword, setShowPassword] = useState(false);
- const qrCodeUrl = useObservable(data.qrCode$);
+ const qrCodeUrl = useObservable(() => data.qrCode$);
const t = useTranslation();
useEffect(() => {
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 a42e710f..04fa513b 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
@@ -27,8 +27,8 @@ export const DownloadMapsModal: ModalComponent mapsDownloader.currentMapDownload$);
+ const mapsInQueue = useObservable(() => mapsDownloader.mapsInQueue$);
const t = useTranslation();
const filterContainerRef = useRef(null);
const [filter, setFilter] = useState({});
@@ -37,7 +37,7 @@ export const DownloadMapsModal: ModalComponent(BSV_SORT_ORDER.at(0));
const [ownedMapHashs, setOwnedMapHashs] = useState(ownedMaps?.map(map => map.hash) ?? []);
const [loading, setLoading] = useState(false);
- const isOnline = useObservable(os.isOnline$);
+ const isOnline = useObservable(() => os.isOnline$);
const [searchParams, setSearchParams] = useState({
sortOrder,
filter,
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
index 0d0b1a76..d6982bd3 100644
--- 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
@@ -44,10 +44,10 @@ export const DownloadModelsModal: ModalComponent modelsDownloader.currentDownload$(), null);
+ const downloadQueue = useObservable(() => modelsDownloader.getQueue$(), []);
const [msModels, msModels$] = useBehaviorSubject([]);
- const isOnline = useObservable(os.isOnline$, true);
+ const isOnline = useObservable(() => os.isOnline$, true);
const [error, error$] = useBehaviorSubject(false);
const [isLoading, isLoading$] = useBehaviorSubject(false);
const [ownedModels, setOwnedModels] = useState(owned ?? []);
diff --git a/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx b/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx
index 75907e77..c04865d7 100644
--- a/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx
+++ b/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx
@@ -1,5 +1,4 @@
import Tippy from "@tippyjs/react";
-import { Variants } from "framer-motion";
import { useEffect, useState } from "react";
import { LinkButton } from "renderer/components/maps-mangement-components/link-button.component";
import { BsmBasicSpinner } from "renderer/components/shared/bsm-basic-spinner/bsm-basic-spinner.component";
@@ -12,7 +11,7 @@ import { BSVersionManagerService } from "renderer/services/bs-version-manager.se
import { ConfigurationService } from "renderer/services/configuration.service";
import { IpcService } from "renderer/services/ipc.service";
import { ModalComponent } from "renderer/services/modale.service";
-import { VersionFolderLinkerService, VersionLinkerActionType } from "renderer/services/version-folder-linker.service";
+import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionType } from "renderer/services/version-folder-linker.service";
import { BSVersion } from "shared/bs-version.interface";
export const ShareFoldersModal: ModalComponent = ({ data }) => {
@@ -107,33 +106,12 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
const t = useTranslation();
- const name = relativeFolder.split(window.electron.path.sep).at(-1);
const color = useThemeColor("first-color");
-
- const variants: Variants = {
- hover: { rotate: 22.5 },
- tap: { rotate: 45 },
- };
-
- const pending = useObservable(linker.isPending(version, relativeFolder));
- const processing = useObservable(linker.isProcessing(version, relativeFolder));
- const linkDisabled = pending || processing;
-
- const [linked, setLinked] = useState(false);
-
- useEffect(() => {
- if (pending) {
- return;
- }
- loadFolderIsLinked();
- }, [version, relativeFolder, pending]);
-
- const loadFolderIsLinked = () => {
- linker.isVersionFolderLinked(version, relativeFolder).toPromise().then(setLinked);
- };
+ const state = useObservable(() => linker.$folderLinkedState(version, relativeFolder), FolderLinkState.Unlinked, [version, relativeFolder]);
+ const name = relativeFolder.split(window.electron.path.sep).at(-1);
const onClickLink = () => {
- if (linked) {
+ if (state === FolderLinkState.Linked) {
return linker.unlinkVersionFolder({
version,
relativeFolder,
@@ -150,7 +128,6 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
const cancelLink = () => {
linker.cancelAction(version, relativeFolder);
- loadFolderIsLinked();
};
return (
@@ -159,14 +136,18 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
{name}
-
-
+
+
{(() => {
- if (processing) {
+ if (state === FolderLinkState.Processing) {
return ;
}
- if (pending) {
+ if (state === FolderLinkState.Pending) {
return (
modalSevice.getModalToShow());
const modalData = modalSevice.getModalData();
const resolver = modalSevice.getResolver();
diff --git a/src/renderer/components/models-management/models-panel.component.tsx b/src/renderer/components/models-management/models-panel.component.tsx
index 1bf18a2f..1b1f96fe 100644
--- a/src/renderer/components/models-management/models-panel.component.tsx
+++ b/src/renderer/components/models-management/models-panel.component.tsx
@@ -1,6 +1,5 @@
import { BSVersion } from "shared/bs-version.interface";
import { useRef, useState } from "react";
-import { ModelsTabsNavbar } from "./models-tabs-navbar.component";
import { ModelsGrid } from "./models-grid.component";
import { MSModelType } from "shared/models/models/model-saber.model";
import { BsmDropdownButton, DropDownItem } from "../shared/bsm-dropdown-button.component";
@@ -14,6 +13,16 @@ import { NotificationService } from "renderer/services/notification.service";
import { ConfigurationService } from "renderer/services/configuration.service";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { lt } from "semver";
+import { lastValueFrom, of, take } from "rxjs";
+import { FolderLinkState } from "renderer/services/version-folder-linker.service";
+import { BsContentTabItemProps } from "../shared/bs-content-tab-panel/bs-content-tab-item.component";
+import { BsContentTabPanel } from "../shared/bs-content-tab-panel/bs-content-tab-panel.component";
+import { ModelTypeAvatarIcon } from "../svgs/icons/model-type-avatar-icon.component";
+import { SvgIcon } from "../svgs/svg-icon.type";
+import { ModelTypeSaberIcon } from "../svgs/icons/model-type-saber-icon.component";
+import { ModelTypePlatformIcon } from "../svgs/icons/model-type-platform-icon.component";
+import { ModelTypeBloqIcon } from "../svgs/icons/model-type-bloq-icon.component";
+import { useObservable } from "renderer/hooks/use-observable.hook";
export function ModelsPanel({ version, isActive, goToMods }: { version?: BSVersion; isActive: boolean; goToMods?: () => void }) {
const modelsManager = useService(ModelsManagerService);
@@ -29,9 +38,15 @@ export function ModelsPanel({ version, isActive, goToMods }: { version?: BSVersi
const [modelTypeTab, setModelTypeTab] = useState(MSModelType.Avatar);
const [currentTabIndex, setCurrentTabIndex] = useState(0);
-
const [search, setSearch] = useState("");
+ const modelsLinkStats = {
+ [MSModelType.Avatar]: useObservable(() => version ? modelsManager.$modelsLinkingState(version, MSModelType.Avatar) : of(null), FolderLinkState.Unlinked, [version]),
+ [MSModelType.Saber]: useObservable(() => version ? modelsManager.$modelsLinkingState(version, MSModelType.Saber) : of(null), FolderLinkState.Unlinked, [version]),
+ [MSModelType.Platfrom]: useObservable(() => version ? modelsManager.$modelsLinkingState(version, MSModelType.Platfrom) : of(null), FolderLinkState.Unlinked, [version]),
+ [MSModelType.Bloq]: useObservable(() => version ? modelsManager.$modelsLinkingState(version, MSModelType.Bloq) : of(null), FolderLinkState.Unlinked, [version]),
+ }
+
useOnUpdate(() => {
if (!isActive || !goToMods) {
return;
@@ -93,6 +108,44 @@ export function ModelsPanel({ version, isActive, goToMods }: { version?: BSVersi
{ text: "models.panel.actions.drop-down.delete", onClick: deleteModels, icon: "trash" },
];
+ const getModelTabProps = (model: MSModelType): BsContentTabItemProps => {
+
+ const onClick = async () => {
+ const state = await lastValueFrom(modelsManager.$modelsLinkingState(version, model).pipe(take(1)));
+ if (state === FolderLinkState.Pending || state === FolderLinkState.Processing) { return; }
+ if (state === FolderLinkState.Linked) {
+ modelsManager.unlinkModels(model, version);
+ } else {
+ modelsManager.linkModels(model, version);
+ }
+ }
+
+ const getModelIcon = (model: MSModelType): SvgIcon => {
+ switch (model) {
+ case MSModelType.Avatar:
+ return ModelTypeAvatarIcon;
+ case MSModelType.Saber:
+ return ModelTypeSaberIcon;
+ case MSModelType.Platfrom:
+ return ModelTypePlatformIcon;
+ case MSModelType.Bloq:
+ return ModelTypeBloqIcon;
+ default:
+ return ModelTypeAvatarIcon;
+ }
+ }
+
+ return {
+ text: `models.types.plural.${model}`,
+ icon: getModelIcon(model),
+ onClick: () => setModelTypeTab(model),
+ linkProps: version ? {
+ onClick,
+ state: modelsLinkStats[model],
+ } : null
+ }
+ };
+
return (
@@ -112,24 +165,23 @@ export function ModelsPanel({ version, isActive, goToMods }: { version?: BSVersi
-
-
{
- setModelTypeTab(() => tab.extra);
- setCurrentTabIndex(() => index);
- }}
- />
-
-
+ setCurrentTabIndex(index)}
+ tabs={[
+ getModelTabProps(MSModelType.Avatar),
+ getModelTabProps(MSModelType.Saber),
+ getModelTabProps(MSModelType.Platfrom),
+ getModelTabProps(MSModelType.Bloq),
+ ]}
+ >
+ <>
-
-
+ >
+
);
}
diff --git a/src/renderer/components/models-management/models-tabs-navbar.component.tsx b/src/renderer/components/models-management/models-tabs-navbar.component.tsx
deleted file mode 100644
index 71d59464..00000000
--- a/src/renderer/components/models-management/models-tabs-navbar.component.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { BSVersion } from "shared/bs-version.interface";
-import { MSModelType } from "../../../shared/models/models/model-saber.model";
-import { LinkButton } from "../maps-mangement-components/link-button.component";
-import { useState } from "react";
-import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
-import { ModelsManagerService } from "renderer/services/models-management/models-manager.service";
-import { BsContentNavBar, BsContentNavBarTab } from "../shared/bs-content-nav-bar.component";
-import { useConstant } from "renderer/hooks/use-constant.hook";
-import { BsmIcon } from "../svgs/bsm-icon.component";
-import { MODEL_TYPES } from "../../../shared/models/models/constants";
-import { useService } from "renderer/hooks/use-service.hook";
-import { useTranslation } from "renderer/hooks/use-translation.hook";
-
-type Props = {
- className?: string;
- version?: BSVersion;
- tabIndex: number;
- onTabChange: (index: number, tab: BsContentNavBarTab) => void;
-};
-
-export function ModelsTabsNavbar({ className, version, tabIndex, onTabChange }: Props) {
- const tabs = useConstant[]>(() => {
- return MODEL_TYPES.map(type => ({
- text: type,
- extra: type,
- }));
- });
-
- return } />;
-}
-
-type TabProps = {
- version?: BSVersion;
- modelType: MSModelType;
- active?: boolean;
-} & React.ComponentProps<"li">;
-
-function ModelTab({ version, modelType, active, onClick }: TabProps) {
- const modelsManager = useService(ModelsManagerService);
-
- const t = useTranslation();
-
- const [modelsAreLinked, setModelsAreLinked] = useState(false);
- const [linkBtnDisabled, setLinkBtnDisabled] = useState(false);
-
- useOnUpdate(() => {
- if (!version) {
- return;
- }
-
- const sub = modelsManager.$modelsLinkingPending(version, modelType).subscribe(async pending => {
- if (pending) {
- return setLinkBtnDisabled(() => pending);
- }
- const modelsLinked = await modelsManager.isModelsLinked(version, modelType);
- setModelsAreLinked(() => modelsLinked);
- setLinkBtnDisabled(() => pending);
- });
-
- return () => {
- sub.unsubscribe();
- };
- }, [version]);
-
- const linkModels = () => modelsManager.linkModels(modelType, version);
- const unlinkModels = () => modelsManager.unlinkModels(modelType, version);
-
- const onClickLink = () => {
- if (!version) {
- return Promise.resolve();
- }
- if (modelsAreLinked) {
- return unlinkModels();
- }
- return linkModels();
- };
-
- return (
- -
-
-
- {t(`models.types.plural.${modelType}`)}
-
- {!!version && }
-
- );
-}
diff --git a/src/renderer/components/nav-bar/bsmanager-icon.component.tsx b/src/renderer/components/nav-bar/bsmanager-icon.component.tsx
index 0a0ddcda..b4e1355d 100644
--- a/src/renderer/components/nav-bar/bsmanager-icon.component.tsx
+++ b/src/renderer/components/nav-bar/bsmanager-icon.component.tsx
@@ -12,7 +12,7 @@ export const BsManagerIcon = memo(({ className }: { className?: string }) => {
const audioPlayer = useService(AudioPlayerService);
const { firstColor, secondColor } = useThemeColor();
- const playing = useObservable(audioPlayer.playing$);
+ const playing = useObservable(() => audioPlayer.playing$);
const { bpm } = audioPlayer;
diff --git a/src/renderer/components/nav-bar/nav-bar-items/shared-nav-bar-item.component.tsx b/src/renderer/components/nav-bar/nav-bar-items/shared-nav-bar-item.component.tsx
index 2b24dfb5..a820cdfc 100644
--- a/src/renderer/components/nav-bar/nav-bar-items/shared-nav-bar-item.component.tsx
+++ b/src/renderer/components/nav-bar/nav-bar-items/shared-nav-bar-item.component.tsx
@@ -12,7 +12,7 @@ export function SharedNavBarItem() {
const pageState = useService(PageStateService);
const t = useTranslation();
- const route = useObservable(pageState.route$);
+ const route = useObservable(() => pageState.route$);
const color = useThemeColor("first-color");
return (
diff --git a/src/renderer/components/nav-bar/nav-bar.component.tsx b/src/renderer/components/nav-bar/nav-bar.component.tsx
index adb87168..f3fcc58b 100644
--- a/src/renderer/components/nav-bar/nav-bar.component.tsx
+++ b/src/renderer/components/nav-bar/nav-bar.component.tsx
@@ -19,8 +19,8 @@ export function NavBar() {
const versionManager = useService(BSVersionManagerService);
const versionDownloader = useService(BsDownloaderService);
- const downloadingVersion = useObservable(versionDownloader.downloadingVersion$.pipe(distinctUntilChanged(equal)));
- const installedVersions = useObservable(versionManager.installedVersions$);
+ const downloadingVersion = useObservable(() => versionDownloader.downloadingVersion$.pipe(distinctUntilChanged(equal)));
+ const installedVersions = useObservable(() => versionManager.installedVersions$);
const color = useThemeColor("first-color");
const t = useTranslation();
diff --git a/src/renderer/components/notification/notification-overlay.component.tsx b/src/renderer/components/notification/notification-overlay.component.tsx
index 4709235a..eea04f13 100644
--- a/src/renderer/components/notification/notification-overlay.component.tsx
+++ b/src/renderer/components/notification/notification-overlay.component.tsx
@@ -7,7 +7,7 @@ import { useService } from "renderer/hooks/use-service.hook";
export function NotificationOverlay() {
const notificationService = useService(NotificationService);
- const notifications = useObservable(notificationService.notifications$);
+ const notifications = useObservable(() => notificationService.notifications$);
return (
diff --git a/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx b/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx
index 13eea1d6..1b005c79 100644
--- a/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx
+++ b/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx
@@ -9,9 +9,9 @@ import { useService } from "renderer/hooks/use-service.hook";
export function BsmProgressBar() {
const progressBarService = useService(ProgressBarService);
- const progressData = useObservable(progressBarService.progressData$);
- const visible = useObservable(progressBarService.visible$);
- const style = useObservable(progressBarService.style$);
+ const progressData = useObservable(() => progressBarService.progressData$);
+ const visible = useObservable(() => progressBarService.visible$);
+ const style = useObservable(() => progressBarService.style$);
const progressLabel = (() => {
if (!progressData) {
diff --git a/src/renderer/components/progress-bar/text-progress-bar.component.tsx b/src/renderer/components/progress-bar/text-progress-bar.component.tsx
index 3179f2a2..81964e87 100644
--- a/src/renderer/components/progress-bar/text-progress-bar.component.tsx
+++ b/src/renderer/components/progress-bar/text-progress-bar.component.tsx
@@ -9,7 +9,7 @@ type Props = {
};
export default function TextProgressBar({ value$, className, style }: Props) {
- const value = useObservable(value$);
+ const value = useObservable(() => value$);
const prefix = typeof value === "number" ? "%" : "";
diff --git a/src/renderer/components/settings/setting-radio-array.component.tsx b/src/renderer/components/settings/setting-radio-array.component.tsx
index 60eb44a1..1a7f2e60 100644
--- a/src/renderer/components/settings/setting-radio-array.component.tsx
+++ b/src/renderer/components/settings/setting-radio-array.component.tsx
@@ -21,7 +21,7 @@ export function SettingRadioArray({ id, items, selectedItemId, selectedItemVa
return (
{items.map(i => (
-
onItemSelected(i)} key={i.id} className={`py-3 w-full flex cursor-pointer justify-between items-center rounded-md px-2 transition-colors duration-200 ${isSelected(i) ? "bg-light-main-color-3 dark:bg-main-color-3" : "bg-light-main-color-1 dark:bg-main-color-1"} ${i.className}`}>
+
onItemSelected(i)} key={i.id} className={`py-3 w-full flex cursor-pointer justify-between items-center rounded-md px-2 transition-colors duration-300 ${isSelected(i) ? "bg-light-main-color-3 dark:bg-main-color-3" : "bg-light-main-color-1 dark:bg-main-color-1"} ${i.className}`}>
diff --git a/src/renderer/components/shared/bs-content-nav-bar.component.tsx b/src/renderer/components/shared/bs-content-nav-bar.component.tsx
deleted file mode 100644
index e747df83..00000000
--- a/src/renderer/components/shared/bs-content-nav-bar.component.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { DetailedHTMLProps, Fragment } from "react";
-import { BsmIconType } from "../svgs/bsm-icon.component";
-
-type Props
= {
- className?: string;
- tabIndex: number;
- tabs: BsContentNavBarTab[];
- renderTab: (props: DetailedHTMLProps, any>, tab?: BsContentNavBarTab, activeTab?: BsContentNavBarTab) => JSX.Element;
- onTabChange?: (index: number, tab?: BsContentNavBarTab) => void;
-};
-
-export type BsContentNavBarTab = {
- text: string;
- icon?: BsmIconType;
- extra?: T;
-};
-
-export function BsContentNavBar({ className, tabIndex, tabs, renderTab, onTabChange }: Props) {
- const handleTabClick = (index: number) => {
- onTabChange?.(index, tabs[index]);
- };
-
- return (
-
- );
-}
diff --git a/src/renderer/components/shared/bs-content-tab-panel/bs-content-tab-item.component.tsx b/src/renderer/components/shared/bs-content-tab-panel/bs-content-tab-item.component.tsx
new file mode 100644
index 00000000..336dc3df
--- /dev/null
+++ b/src/renderer/components/shared/bs-content-tab-panel/bs-content-tab-item.component.tsx
@@ -0,0 +1,44 @@
+import { LinkBtnProps, LinkButton } from "renderer/components/maps-mangement-components/link-button.component";
+import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
+import { useTranslation } from "renderer/hooks/use-translation.hook";
+
+export type BsContentTabItemProps = {
+ text: string;
+ icon: SvgIcon;
+ active?: boolean;
+ value?: T;
+ linkProps?: LinkBtnProps;
+ onClick: (value?: T) => void;
+};
+
+type BsContentTabItemComponent = ({text, icon, active, value, linkProps, onClick}: BsContentTabItemProps) => JSX.Element;
+
+export const BsContentTabItem: BsContentTabItemComponent = ({ text, icon: Icon, active, value, linkProps, onClick }) => {
+
+ const t = useTranslation();
+
+ const handleClick = (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ onClick(value);
+ }
+
+ return (
+ -
+
+
+ {t(text)}
+
+ {linkProps && (
+
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/src/renderer/components/shared/bs-content-tab-panel/bs-content-tab-panel.component.tsx b/src/renderer/components/shared/bs-content-tab-panel/bs-content-tab-panel.component.tsx
new file mode 100644
index 00000000..6cc1257e
--- /dev/null
+++ b/src/renderer/components/shared/bs-content-tab-panel/bs-content-tab-panel.component.tsx
@@ -0,0 +1,35 @@
+import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
+import { LaserSlider } from "../laser-slider.component";
+import { BsContentTabItem, BsContentTabItemProps } from "./bs-content-tab-item.component";
+
+type Props = Readonly<{
+ className?: string;
+ tabs: BsContentTabItemProps[];
+ tabIndex: number;
+ onTabChange: (index: number, tab: BsContentTabItemProps) => void;
+ children: JSX.Element;
+}>;
+
+export function BsContentTabPanel({className, tabIndex, tabs, onTabChange, children}: Props) {
+
+ const sliderColor = useThemeColor("second-color");
+
+ return (
+
+
+
+
+ {children}
+
+
+ )
+}
diff --git a/src/renderer/components/shared/iframe-view.component.tsx b/src/renderer/components/shared/iframe-view.component.tsx
index 2ca5ebd6..c378d846 100644
--- a/src/renderer/components/shared/iframe-view.component.tsx
+++ b/src/renderer/components/shared/iframe-view.component.tsx
@@ -8,7 +8,7 @@ import { LinkOpenerService } from "renderer/services/link-opener.service";
export function BsmIframeView() {
const linkService = useService(LinkOpenerService);
- const iframeUrl = useObservable(linkService.iframeLink$);
+ const iframeUrl = useObservable(() => linkService.iframeLink$);
const ref = useRef(null);
useClickOutside(ref, () => linkService.closeIframe());
diff --git a/src/renderer/components/shared/laser-slider.component.tsx b/src/renderer/components/shared/laser-slider.component.tsx
new file mode 100644
index 00000000..ce35107a
--- /dev/null
+++ b/src/renderer/components/shared/laser-slider.component.tsx
@@ -0,0 +1,35 @@
+import { CSSProperties } from "react";
+
+type Props = Readonly<{
+ mode: "horizontal" | "vertical";
+ color: string;
+ className?: string;
+ nbSteps: number;
+ step: number;
+}>
+
+export function LaserSlider({mode, color, className, nbSteps, step}: Props) {
+
+ const sliderStyle = ((): CSSProperties => {
+ if(mode === "vertical"){
+ return {
+ transform: `translate(0, ${step * 100}%)`,
+ height: `calc(100% / ${nbSteps})`,
+ width: "100%"
+ }
+ }
+
+ return {
+ transform: `translate(${step * 100}%, 0)`,
+ width: `calc(100% / ${nbSteps})`,
+ height: "100%"
+ }
+ })();
+
+ return (
+
+
+
+
+ )
+}
diff --git a/src/renderer/components/shared/tab-nav-bar.component.tsx b/src/renderer/components/shared/tab-nav-bar.component.tsx
index 0484ec58..960ab97e 100644
--- a/src/renderer/components/shared/tab-nav-bar.component.tsx
+++ b/src/renderer/components/shared/tab-nav-bar.component.tsx
@@ -1,6 +1,7 @@
import { DetailedHTMLProps, Fragment } from "react";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { useTranslation } from "renderer/hooks/use-translation.hook";
+import { LaserSlider } from "./laser-slider.component";
type Props = {
tabIndex: number;
@@ -22,10 +23,7 @@ export function TabNavBar(props: Props) {
return (
-
+
diff --git a/src/renderer/services/maps-manager.service.ts b/src/renderer/services/maps-manager.service.ts
index aacf9cb6..4c227074 100644
--- a/src/renderer/services/maps-manager.service.ts
+++ b/src/renderer/services/maps-manager.service.ts
@@ -13,7 +13,7 @@ import { ConfigurationService } from "./configuration.service";
import { ArchiveProgress } from "shared/models/archive.interface";
import { map, last, catchError } from "rxjs/operators";
import { ProgressionInterface } from "shared/models/progress-bar";
-import { VersionFolderLinkerService, VersionLinkerActionType } from "./version-folder-linker.service";
+import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionType } from "./version-folder-linker.service";
export class MapsManagerService {
private static instance: MapsManagerService;
@@ -176,6 +176,10 @@ export class MapsManagerService {
}
public $mapsLinkingPending(version: BSVersion): Observable
{
- return this.linker.$isVersionFolderPending(version, MapsManagerService.RELATIVE_MAPS_FOLDER);
+ return this.linker.$isPending(version, MapsManagerService.RELATIVE_MAPS_FOLDER);
+ }
+
+ public $mapsFolderLinkState(version: BSVersion): Observable {
+ return this.linker.$folderLinkedState(version, MapsManagerService.RELATIVE_MAPS_FOLDER);
}
}
diff --git a/src/renderer/services/models-management/models-manager.service.ts b/src/renderer/services/models-management/models-manager.service.ts
index 3dd5f111..0c1c230e 100644
--- a/src/renderer/services/models-management/models-manager.service.ts
+++ b/src/renderer/services/models-management/models-manager.service.ts
@@ -1,6 +1,6 @@
import { MSModelType } from "shared/models/models/model-saber.model";
import { IpcService } from "../ipc.service";
-import { VersionFolderLinkerService, VersionLinkerActionListener, VersionLinkerActionType } from "../version-folder-linker.service";
+import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionListener, VersionLinkerActionType } from "../version-folder-linker.service";
import { MODEL_TYPE_FOLDERS } from "shared/models/models/constants";
import { Observable, lastValueFrom, map } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
@@ -49,7 +49,11 @@ export class ModelsManagerService {
}
public $modelsLinkingPending(version: BSVersion, type: MSModelType): Observable {
- return this.versionFolderLinked.$isVersionFolderPending(version, MODEL_TYPE_FOLDERS[type]);
+ return this.versionFolderLinked.$isPending(version, MODEL_TYPE_FOLDERS[type]);
+ }
+
+ public $modelsLinkingState(version: BSVersion, type: MSModelType): Observable {
+ return this.versionFolderLinked.$folderLinkedState(version, MODEL_TYPE_FOLDERS[type]);
}
public onModelsFolderLinked(callback: VersionLinkerActionListener): void {
diff --git a/src/renderer/services/version-folder-linker.service.ts b/src/renderer/services/version-folder-linker.service.ts
index 5b7c6b33..936de9d6 100644
--- a/src/renderer/services/version-folder-linker.service.ts
+++ b/src/renderer/services/version-folder-linker.service.ts
@@ -1,6 +1,6 @@
import { LinkOptions, UnlinkOptions } from "main/services/folder-linker.service";
import { map, distinctUntilChanged, filter, mergeMap, shareReplay } from "rxjs/operators";
-import { BehaviorSubject, Observable } from "rxjs";
+import { BehaviorSubject, Observable, of } from "rxjs";
import { BSVersion } from "shared/bs-version.interface";
import { IpcService } from "./ipc.service";
import { ProgressBarService } from "./progress-bar.service";
@@ -62,6 +62,13 @@ export class VersionFolderLinkerService {
return this.ipcService.sendV2("link-version-folder-action", { args: action });
}
+ private get currentAction$(): Observable {
+ return this._queue$.pipe(
+ map(actions => actions.at(0)),
+ distinctUntilChanged()
+ );
+ }
+
public linkVersionFolder(action: VersionLinkFolderAction): Promise {
const promise = new Promise(resolve => {
const callBack: VersionLinkerActionListener = (performedAction, linked) => {
@@ -122,12 +129,33 @@ export class VersionFolderLinkerService {
return this.ipcService.sendV2("is-version-folder-linked", { args: { version, relativeFolder } });
}
- public isPending(version: BSVersion, relativeFolder: string): Observable {
- return this.queue$.pipe(map(queue => queue.length > 0 && queue.some(action => action.version === version && action.relativeFolder === relativeFolder), distinctUntilChanged()));
+ public $folderLinkedState(version: BSVersion, relativeFolder: string): Observable {
+ return this._queue$.pipe(
+ mergeMap(queue => {
+ const currentAction = queue.at(0);
+ if(currentAction && equal(currentAction.version, version) && currentAction.relativeFolder === relativeFolder) {
+ return of(FolderLinkState.Processing)
+ }
+
+ if(queue.some(action => equal(action.version, version) && action.relativeFolder === relativeFolder)) {
+ return of(FolderLinkState.Pending);
+ }
+
+ return this.isVersionFolderLinked(version, relativeFolder).pipe(
+ map(linked => linked ? FolderLinkState.Linked : FolderLinkState.Unlinked)
+ );
+ }),
+ distinctUntilChanged(),
+ shareReplay(1)
+ );
}
- public isProcessing(version: BSVersion, relativeFolder: string): Observable {
- return this.currentAction$.pipe(map(action => action && action.version === version && action.relativeFolder === relativeFolder));
+ public $isPending(version: BSVersion, relativeFolder: string): Observable {
+ return this.$folderLinkedState(version, relativeFolder).pipe(map(state => state === FolderLinkState.Pending));
+ }
+
+ public $isProcessing(version: BSVersion, relativeFolder: string): Observable {
+ return this.$folderLinkedState(version, relativeFolder).pipe(map(state => state === FolderLinkState.Processing));
}
public getLinkedFolders(version: BSVersion, options?: { relative?: boolean }): Observable {
@@ -137,27 +165,6 @@ export class VersionFolderLinkerService {
public relinkAllVersionsFolders(): Observable {
return this.ipcService.sendV2("relink-all-versions-folders");
}
-
- public $isVersionFolderPending(version: BSVersion, relativeFolder: string): Observable {
- return this.queue$.pipe(
- mergeMap(async queue => {
- return queue.some(q => q.relativeFolder.includes(relativeFolder) && equal(q.version, version));
- }),
- distinctUntilChanged(),
- shareReplay(1)
- );
- }
-
- public get currentAction$(): Observable {
- return this._queue$.pipe(
- map(actions => actions.at(0)),
- distinctUntilChanged()
- );
- }
-
- public get queue$(): Observable {
- return this._queue$.asObservable();
- }
}
export const enum VersionLinkerActionType {
@@ -182,3 +189,10 @@ export interface VersionUnlinkFolderAction extends VersionLinkerAction {
}
export type VersionLinkerActionListener = (action: VersionLinkerAction, linked: boolean) => void;
+
+export enum FolderLinkState {
+ Linked,
+ Unlinked,
+ Pending,
+ Processing
+}
diff --git a/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx b/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx
index 8dab7d1b..a21bf590 100644
--- a/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx
+++ b/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx
@@ -24,8 +24,8 @@ export default function OneClickDownloadPlaylist() {
const mapsContainer = useRef(null);
const playlistUrl = useConstant(() => new URLSearchParams(window.location.search).get("playlistUrl"));
const download$ = useConstant(() => playlistDownloader.oneClickInstallPlaylist(playlistUrl));
- const playlistInfos = useObservable(download$.pipe(filter(progress => !!progress.data?.playlistInfos), map(progress => progress.data.playlistInfos), take(1)));
- const downloadedMaps = useObservable(download$.pipe(filter(progress => !!progress.data?.downloadedMaps), map(progress => progress.data.downloadedMaps)));
+ const playlistInfos = useObservable(() => download$.pipe(filter(progress => !!progress.data?.playlistInfos), map(progress => progress.data.playlistInfos), take(1)));
+ const downloadedMaps = useObservable(() => download$.pipe(filter(progress => !!progress.data?.downloadedMaps), map(progress => progress.data.downloadedMaps)));
useEffect(() => {
diff --git a/src/renderer/windows/ShortcutLaunch.tsx b/src/renderer/windows/ShortcutLaunch.tsx
index ca2f2a87..1049676a 100644
--- a/src/renderer/windows/ShortcutLaunch.tsx
+++ b/src/renderer/windows/ShortcutLaunch.tsx
@@ -26,7 +26,7 @@ export default function ShortcutLaunch() {
const t = useTranslation();
const color = useThemeColor("second-color");
- const launchOptions = useObservable(ipc.sendV2("shortcut-launch-options").pipe(take(1)), null)
+ const launchOptions = useObservable(() => ipc.sendV2("shortcut-launch-options").pipe(take(1)), null)
const [rotation, setRotation] = useState(0);
const [status, setStatus] = useState();