diff --git a/package.json b/package.json index d3ca5a6..cd3743b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "wh3mm", "productName": "wh3mm", - "version": "1.37.4", + "version": "1.38.0", "description": "WH3 Mod Manager", "main": ".webpack/main", "scripts": { diff --git a/src/app.tsx b/src/app.tsx index 483a917..7a0eee2 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -35,7 +35,10 @@ function render() { > {(window.location.pathname.includes("/main_window") && ( -
+
diff --git a/src/appSlice.ts b/src/appSlice.ts index 3671176..8fa7c7d 100644 --- a/src/appSlice.ts +++ b/src/appSlice.ts @@ -8,6 +8,8 @@ import { withoutDataAndContentDuplicates, } from "./modsHelpers"; import { SortingType } from "./utility/modRowSorting"; +import { compareModNames, sortAsInPreset, sortByNameAndLoadOrder } from "./modSortingHelpers"; +import { index } from "handsontable/helpers/dom"; const addCategoryByPayload = (state: AppState, payload: AddCategoryPayload) => { const { mods, category } = payload; @@ -87,8 +89,9 @@ const appSlice = createSlice({ // before we make symbolic links in data queue those mods to be re-enabled dataModsToEnableByName: [], // if a enabled mod was removed it's possible it was updated, re-enabled it then - removedEnabledModPaths: [], + removedModsData: [], modRowsSortingType: SortingType.Ordered, + importedMods: [], } as AppState, reducers: { // when mutating mods make sure you get the same mod from state.currentPreset.mods and don't change the mod that's from the payload @@ -126,6 +129,38 @@ const appSlice = createSlice({ } }); }, + orderImportedMods: (state: AppState) => { + console.log("ordering imported mods"); + for (let i = 0; i < state.importedMods.length; i++) { + const importedMod = state.importedMods[i]; + if (importedMod.loadOrder != undefined) { + const currentMod = state.currentPreset.mods.find( + (mod) => mod.workshopId == state.importedMods[i].workshopId + ); + if (!currentMod) continue; + + const currentModIndex = currentMod && state.currentPreset.mods.indexOf(currentMod); + if (currentModIndex == -1) continue; + + if (i == 0) { + // no previous siblings so put it at start of all mods + state.currentPreset.mods.splice(currentModIndex, 1); + state.currentPreset.mods.splice(0, 0, currentMod); + } else { + const previousSiblingModIndex = state.currentPreset.mods.findIndex( + (mod) => mod.workshopId == state.importedMods[i - 1].workshopId + ); + if (previousSiblingModIndex != -1) { + // put the mod with the load order after the previous sibling + state.currentPreset.mods.splice(currentModIndex, 1); + state.currentPreset.mods.splice(0, 0, currentMod); + } + } + } + } + + state.importedMods = []; + }, enableAll: (state: AppState) => { state.currentPreset.mods.forEach((mod) => (mod.isEnabled = true)); @@ -151,6 +186,9 @@ const appSlice = createSlice({ ); toEnable.forEach((mod) => (mod.isEnabled = true)); }, + setImportedMods: (state: AppState, action: PayloadAction) => { + state.importedMods = action.payload; + }, setMods: (state: AppState, action: PayloadAction) => { console.log("appSlice/setMods: SETTING CURRENT PRESET"); const mods = action.payload; @@ -163,6 +201,15 @@ const appSlice = createSlice({ (!mod.isInData && !mods.find((modOther) => modOther.name == mod.name && modOther.isInData)) ); + if (state.dataFromConfig && state.dataFromConfig.currentPreset.version != undefined) { + state.currentPreset.version = state.dataFromConfig.currentPreset.version; + console.log("sorting as in preset from config in setMods"); + state.currentPreset.mods = sortAsInPreset( + state.currentPreset.mods, + state.dataFromConfig.currentPreset.mods + ); + } + if (state.dataFromConfig) { state.currentPreset.mods .filter((iterMod) => @@ -184,7 +231,11 @@ const appSlice = createSlice({ } const appStartIndex = state.presets.findIndex((preset) => preset.name === "On App Start"); - const newPreset = { name: "On App Start", mods: [...state.currentPreset.mods] }; + const newPreset = { + name: "On App Start", + mods: [...state.currentPreset.mods], + version: state.currentPreset.version, + }; if (appStartIndex != -1) { state.presets.splice(appStartIndex, 1, newPreset); } else { @@ -201,19 +252,21 @@ const appSlice = createSlice({ if (!alreadyExistsByName) { state.currentPreset.mods.push(mod); } else if (mod.isInData) { - state.currentPreset.mods.splice(state.currentPreset.mods.indexOf(alreadyExistsByName), 1); - state.currentPreset.mods.push(mod); + const previousIndex = state.currentPreset.mods.indexOf(alreadyExistsByName); + state.currentPreset.mods.splice(previousIndex, 1, mod); + mod.isEnabled = alreadyExistsByName.isEnabled; } if (!state.allMods.find((iterMod) => iterMod.path == mod.path)) { state.allMods.push(mod); } - if (state.removedEnabledModPaths.find((path) => path === mod.path)) { - mod.isEnabled = true; - state.removedEnabledModPaths = state.removedEnabledModPaths.filter( - (pathOfRemoved) => pathOfRemoved != mod.path - ); + const removedModData = state.removedModsData.find(({ modPath }) => modPath === mod.path); + if (removedModData) { + mod.isEnabled = removedModData.isEnabled; + state.currentPreset.mods.splice(state.currentPreset.mods.indexOf(mod), 1); + state.currentPreset.mods.splice(removedModData.indexInMods, 0, mod); + state.removedModsData = state.removedModsData.filter(({ modPath }) => modPath != mod.path); } if (state.removedModsCategories[mod.path]) { @@ -278,9 +331,11 @@ const appSlice = createSlice({ } } - if (removedMod.isEnabled) { - state.removedEnabledModPaths.push(removedMod.path); - } + state.removedModsData.push({ + modPath: removedMod.path, + isEnabled: removedMod.isEnabled, + indexInMods: state.currentPreset.mods.indexOf(removedMod), + }); state.removedModsCategories[removedMod.path] = removedMod.categories ?? []; state.currentPreset.mods = state.currentPreset.mods.filter((iterMod) => iterMod.path !== modPath); @@ -372,6 +427,16 @@ const appSlice = createSlice({ if (mod.loadOrder != null) existingMod.loadOrder = mod.loadOrder; } }); + + if (fromConfigAppState.currentPreset.version != undefined) { + state.currentPreset.version = fromConfigAppState.currentPreset.version; + console.log("sorting as in preset from config"); + state.currentPreset.mods = sortAsInPreset( + state.currentPreset.mods, + fromConfigAppState.currentPreset.mods + ); + } + fromConfigAppState.presets.forEach((preset) => { if (!state.presets.find((existingPreset) => existingPreset.name === preset.name)) { state.presets.push(preset); @@ -413,6 +478,12 @@ const appSlice = createSlice({ addPreset: (state: AppState, action: PayloadAction) => { const newPreset = action.payload; if (state.presets.find((preset) => preset.name === newPreset.name)) return; + + console.log("current preset version is ", state.currentPreset.version); + newPreset.mods = + (state.currentPreset.version != undefined && newPreset.mods) || + sortByNameAndLoadOrder(newPreset.mods); + newPreset.version = 1; state.presets.push(newPreset); state.lastSelectedPreset = newPreset; }, @@ -421,6 +492,7 @@ const appSlice = createSlice({ const newPreset = { name: "On Last Game Launch", mods: [...state.currentPreset.mods], + version: state.currentPreset.version, }; if (appStartIndex != -1) { state.presets.splice(appStartIndex, 1, newPreset); @@ -442,6 +514,7 @@ const appSlice = createSlice({ }); const newPresetMods = withoutDataAndContentDuplicates(newPreset.mods); + if (newPreset.version == undefined) newPreset.mods = sortByNameAndLoadOrder(newPreset.mods); state.currentPreset.mods.forEach((mod) => { const modToChange = findMod(newPresetMods, mod); @@ -450,6 +523,9 @@ const appSlice = createSlice({ mod.loadOrder = modToChange.loadOrder; } }); + + state.currentPreset.mods = sortAsInPreset(state.currentPreset.mods, newPresetMods); + state.currentPreset.version = 1; } else if (presetSelection === "addition" || presetSelection === "subtraction") { newPreset.mods.forEach((mod) => { if (mod.isEnabled) { @@ -471,7 +547,12 @@ const appSlice = createSlice({ replacePreset: (state: AppState, action: PayloadAction) => { const name = action.payload; const preset = state.presets.find((preset) => preset.name === name); - if (preset) preset.mods = state.currentPreset.mods; + if (!preset) return; + + preset.mods = + (state.currentPreset.version != undefined && state.currentPreset.mods) || + sortByNameAndLoadOrder(state.currentPreset.mods); + preset.version = 1; }, setFilter: (state: AppState, action: PayloadAction) => { const filter = action.payload; @@ -512,12 +593,51 @@ const appSlice = createSlice({ // printLoadOrders(state.currentPreset.mods); }, + + setModLoadOrderRelativeTo: (state: AppState, action: PayloadAction) => { + const payload = action.payload; + const { modNameToChange, modNameRelativeTo } = payload; + const modToChange = state.currentPreset.mods.find((mod) => mod.name === modNameToChange); + const modRelativeTo = state.currentPreset.mods.find((mod) => mod.name === modNameRelativeTo); + + if (!modToChange || !modRelativeTo) return; + + state.currentPreset.mods.splice(state.currentPreset.mods.indexOf(modToChange), 1); + const newIndex = state.currentPreset.mods.indexOf(modRelativeTo); + state.currentPreset.mods.splice(newIndex, 0, modToChange); + + modToChange.loadOrder = newIndex; + }, + resetModLoadOrderAll: (state: AppState) => { + state.currentPreset.mods.forEach((mod) => { + mod.loadOrder = undefined; + }); + state.currentPreset.mods = sortByNameAndLoadOrder(state.currentPreset.mods); + }, resetModLoadOrder: (state: AppState, action: PayloadAction) => { const mods = action.payload; mods.forEach((mod) => { const stateMod = state.currentPreset.mods.find((stateMod) => stateMod.name === mod.name); if (stateMod) stateMod.loadOrder = undefined; }); + + if (state.currentPreset.version != undefined) { + for (const mod of mods) { + const modToChange = state.currentPreset.mods.find((iterMod) => mod.name === iterMod.name); + if (!modToChange) continue; + + const siblingMod = state.currentPreset.mods.find( + (iterMod) => + iterMod.path != mod.path && + iterMod.loadOrder == undefined && + compareModNames(iterMod.name, mod.name) >= 0 + ); + if (!siblingMod) continue; + + state.currentPreset.mods.splice(state.currentPreset.mods.indexOf(modToChange), 1); + state.currentPreset.mods.splice(state.currentPreset.mods.indexOf(siblingMod), 0, modToChange); + } + } }, toggleAlwaysEnabledMods: (state: AppState, action: PayloadAction) => { const mods = action.payload; @@ -685,6 +805,7 @@ export const { setModData, setFromConfig, enableAll, + setImportedMods, disableAllMods, addPreset, selectPreset, @@ -693,7 +814,9 @@ export const { deletePreset, setFilter, setModLoadOrder, + setModLoadOrderRelativeTo, resetModLoadOrder, + resetModLoadOrderAll, toggleAlwaysEnabledMods, toggleAlwaysHiddenMods, setSaves, @@ -712,6 +835,7 @@ export const { toggleIsSkipIntroMoviesEnabled, toggleIsAutoStartCustomBattleEnabled, setSharedMod, + orderImportedMods, addMod, removeMod, createdMergedPack, diff --git a/src/components/ModRow.tsx b/src/components/ModRow.tsx index 72f0f4b..28c3f6c 100644 --- a/src/components/ModRow.tsx +++ b/src/components/ModRow.tsx @@ -1,6 +1,6 @@ import { faCamera, faEraser, faFileArchive, faGrip } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import React, { memo } from "react"; +import React, { memo, useCallback } from "react"; import { useAppSelector } from "../hooks"; import { Tooltip } from "flowbite-react"; import classNames from "classnames"; @@ -67,6 +67,14 @@ const ModRow = memo( const areThumbnailsEnabled = useAppSelector((state) => state.app.areThumbnailsEnabled); const isDev = useAppSelector((state) => state.app.isDev); const isAuthorEnabled = useAppSelector((state) => state.app.isAuthorEnabled); + const areModsInOrder = useAppSelector((state) => state.app.currentPreset.version) != undefined; + + const getGhostClass = useCallback(() => { + if (isAuthorEnabled && areThumbnailsEnabled) return "grid-column-7"; + if (isAuthorEnabled) return "grid-column-6"; + if (areThumbnailsEnabled) return "grid-column-6"; + return "grid-column-5"; + }, [isAuthorEnabled, areThumbnailsEnabled]); const timeColumnValue = (isSubbedTimeSort(sortingType) && @@ -91,7 +99,7 @@ const ModRow = memo( id={mod.name} data-load-order={mod.loadOrder} > - {index != 0 &&
} +
onRemoveModOrder(mod)}> {loadOrder}
diff --git a/src/components/ModRows.tsx b/src/components/ModRows.tsx index 3ec976a..2c4f63b 100644 --- a/src/components/ModRows.tsx +++ b/src/components/ModRows.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useRef, useState } from "react"; +import React, { memo, useCallback, useRef, useState } from "react"; import "../index.css"; import { useAppDispatch, useAppSelector } from "../hooks"; import { @@ -8,6 +8,8 @@ import { setModLoadOrder, resetModLoadOrder, setModRowsSortingType, + setModLoadOrderRelativeTo, + resetModLoadOrderAll, } from "../appSlice"; import { Alert, Tooltip } from "flowbite-react"; import { getFilteredMods, sortByNameAndLoadOrder } from "../modSortingHelpers"; @@ -85,7 +87,7 @@ const onDragEnd = (e?: React.DragEvent) => { console.log("first child rect AFTER", originalElement?.children[1].getBoundingClientRect()); const newTop = originalElement.children[1].getBoundingClientRect().top; - window.scrollBy(0, newTop - oldTop); + document.getElementById("mod-rows-scroll")?.scrollBy(0, newTop - oldTop); } // console.log("on drag end SCR 2", document.scrollingElement?.scrollTop); @@ -99,8 +101,9 @@ const onDragEnd = (e?: React.DragEvent) => { }, 100); }; -export default function ModRows() { +const ModRows = memo(() => { const dispatch = useAppDispatch(); + const areModsInOrder = useAppSelector((state) => state.app.currentPreset.version) != undefined; const filter = useAppSelector((state) => state.app.filter); const hiddenMods = useAppSelector((state) => state.app.hiddenMods); const alwaysEnabledMods = useAppSelector((state) => state.app.alwaysEnabledMods); @@ -129,7 +132,7 @@ export default function ModRows() { const isAlwaysEnabled = alwaysEnabledMods.find((mod) => iterMod.name === mod.name); return !isHidden || (isHidden && isAlwaysEnabled); }); - const orderedMods = sortByNameAndLoadOrder(modsToOrder); + const orderedMods = (areModsInOrder && modsToOrder) || sortByNameAndLoadOrder(modsToOrder); let mods: Mod[] = modRowSorting.getSortedMods(presetMods, orderedMods, sortingType); @@ -152,7 +155,8 @@ export default function ModRows() { const mod = mods.find((mod) => mod.workshopId == name); if (!mod) return; - const lastScrollTop = document.scrollingElement?.scrollTop; + const modRowsScroll = document.getElementById("mod-rows-scroll"); + const lastScrollTop = modRowsScroll?.scrollTop; // if always enabled don't allow unchecking if (isModAlwaysEnabled(mod, alwaysEnabledMods)) { @@ -162,7 +166,7 @@ export default function ModRows() { dispatch(toggleMod(mod)); setTimeout(() => { - if (lastScrollTop && document.scrollingElement) document.scrollingElement.scrollTop = lastScrollTop; + if (lastScrollTop && modRowsScroll) modRowsScroll.scrollTop = lastScrollTop; }, 1); }, [mods] @@ -184,8 +188,8 @@ export default function ModRows() { }, [mods]); const onOrderRightClick = useCallback(() => { - dispatch(resetModLoadOrder(mods.filter((mod) => mod.loadOrder !== undefined))); - }, [mods, resetModLoadOrder]); + dispatch(resetModLoadOrderAll()); + }, [resetModLoadOrderAll]); const afterDrop = useCallback((originalId: string, droppedOnId: string) => { // console.log(`----DROPPED----`); @@ -299,7 +303,7 @@ export default function ModRows() { } setTimeout(() => { const newTop = t.parentElement?.getBoundingClientRect().top; - if (oldTop && newTop) window.scrollBy(0, newTop - oldTop); + if (oldTop && newTop) document.getElementById("mod-rows-scroll")?.scrollBy(0, newTop - oldTop); // t.parentElement?.scrollIntoView({ block: "center" }); }, 50); @@ -307,7 +311,6 @@ export default function ModRows() { }, []); const getGhostClass = useCallback(() => { - console.log(isAuthorEnabled, areThumbnailsEnabled); if (isAuthorEnabled && areThumbnailsEnabled) return "grid-column-7"; if (isAuthorEnabled) return "grid-column-6"; if (areThumbnailsEnabled) return "grid-column-6"; @@ -377,40 +380,54 @@ export default function ModRows() { // return false; }, []); - const onDrop = useCallback((e: React.DragEvent) => { - console.log("onDrop"); - // console.log(`dragged id with ${e.dataTransfer.getData("text/plain")}`); - const droppedId = e.dataTransfer.getData("text/plain"); - if (droppedId === "") return; - idOfDragged = droppedId; + const onDrop = useCallback( + (e: React.DragEvent) => { + console.log("onDrop"); + // console.log(`dragged id with ${e.dataTransfer.getData("text/plain")}`); + const droppedId = e.dataTransfer.getData("text/plain"); + if (droppedId === "") return; + idOfDragged = droppedId; - console.log("in ondrop droppedID", droppedId); + // if(areModsInOrder) return dispatch(setModLoadOrderRelativeTo()) - const t = e.currentTarget as HTMLDivElement; - // console.log(`DROPPED ONTO ${t.id}`); + console.log("in ondrop droppedID", droppedId); - if (droppedId === t.id) return; + const t = e.currentTarget as HTMLDivElement; + console.log(`DROPPED ONTO ${t.id}`); - // console.log("isBottomDrop: " + isBottomDrop); - if (!t.nextElementSibling) return; - const rowId = (isBottomDrop ? (t.nextElementSibling.nextElementSibling as HTMLElement) : t).id; + if (droppedId === t.id) return; - afterDrop(droppedId, rowId); - // onDragEnd(); - // e.stopPropagation(); - }, []); + // console.log("isBottomDrop: " + isBottomDrop); + if (!t.nextElementSibling) return; + const rowId = (isBottomDrop ? (t.nextElementSibling.nextElementSibling as HTMLElement) : t).id; + + if (areModsInOrder) { + dispatch( + setModLoadOrderRelativeTo({ + modNameToChange: droppedId, + modNameRelativeTo: t.id, + } as ModLoadOrderRelativeTo) + ); + return; + } + + afterDrop(droppedId, rowId); + // onDragEnd(); + // e.stopPropagation(); + }, + [areModsInOrder] + ); const onDrag = useCallback( (e: React.DragEvent) => { - if (e.clientY < 200) { - const yRatio = e.clientY / 200; - console.log(-(20 * yRatio + 75 * (1 - yRatio))); - scrollBy(0, -(20 * yRatio + 75 * (1 - yRatio))); + if (e.clientY < 150) { + const yRatio = e.clientY / 150; + document.getElementById("mod-rows-scroll")?.scrollBy(0, -(20 * yRatio + 60 * (1 - yRatio))); } if (e.clientY > innerHeight - 75) { const yRatio = (e.clientY - (innerHeight - 75)) / 75; - scrollBy(0, 75 * yRatio + 20 * (1 - yRatio)); + document.getElementById("mod-rows-scroll")?.scrollBy(0, 60 * yRatio + 20 * (1 - yRatio)); } }, [currentDragTarget, dropOutlineElement] @@ -462,12 +479,13 @@ export default function ModRows() { ); const onDropdownOverlayClick = useCallback(() => { - if (!document.scrollingElement) return; - const lastScrollTop = document.scrollingElement.scrollTop; + const modRowsScroll = document.getElementById("mod-rows-scroll"); + if (!modRowsScroll) return; + const lastScrollTop = modRowsScroll.scrollTop; setIsDropdownOpen(false); setTimeout(() => { - if (document.scrollingElement) document.scrollingElement.scrollTop = lastScrollTop; + if (modRowsScroll) modRowsScroll.scrollTop = lastScrollTop; }, 1); }, []); @@ -662,4 +680,6 @@ export default function ModRows() {
); -} +}); + +export default ModRows; diff --git a/src/components/SaveGames.tsx b/src/components/SaveGames.tsx index 08ea46a..a215cf4 100644 --- a/src/components/SaveGames.tsx +++ b/src/components/SaveGames.tsx @@ -20,6 +20,7 @@ const SaveGame = memo((props: SaveGameProps) => { const isAutoStartCustomBattleEnabled = useAppSelector((state) => state.app.isAutoStartCustomBattleEnabled); const isClosedOnPlay = useAppSelector((state) => state.app.isClosedOnPlay); const saves = [...useAppSelector((state) => state.app.saves)]; + const areModsInOrder = useAppSelector((state) => state.app.currentPreset.version) != undefined; saves.sort((first, second) => second.lastChanged - first.lastChanged); const onClose = useCallback(() => { @@ -30,6 +31,7 @@ const SaveGame = memo((props: SaveGameProps) => { (name: string) => { window.api?.startGame( mods, + areModsInOrder, { isMakeUnitsGeneralsEnabled, isSkipIntroMoviesEnabled, diff --git a/src/components/ShareMods.tsx b/src/components/ShareMods.tsx index 6e37415..7f93ea5 100644 --- a/src/components/ShareMods.tsx +++ b/src/components/ShareMods.tsx @@ -1,7 +1,13 @@ import { Modal } from "../flowbite/components/Modal/index"; import React, { memo, useCallback, useEffect, useState } from "react"; import { useAppDispatch, useAppSelector } from "../hooks"; -import { disableAllMods, setAreModsEnabled, setSharedMod } from "../appSlice"; +import { + disableAllMods, + orderImportedMods, + setAreModsEnabled, + setImportedMods, + setSharedMod, +} from "../appSlice"; import { Spinner } from "flowbite-react"; const subbedModIdsToWaitFor: ModIdAndLoadOrder[] = []; @@ -15,6 +21,7 @@ const ShareMods = memo((props: ShareModsProps) => { const [importModsText, setImportModsText] = useState(""); const [isSpinnerOpen, setIsSpinnerOpen] = useState(false); const mods = useAppSelector((state) => state.app.currentPreset.mods); + const importedMods = useAppSelector((state) => state.app.importedMods); const saves = [...useAppSelector((state) => state.app.saves)]; saves.sort((first, second) => second.lastChanged - first.lastChanged); @@ -39,6 +46,8 @@ const ShareMods = memo((props: ShareModsProps) => { return { workshopId: idAndOrder, loadOrder: undefined }; }); + dispatch(setImportedMods(imported)); + const onlyIds = imported.map((idAndOrder) => idAndOrder.workshopId); subbedModIdsToWaitFor.splice(0, subbedModIdsToWaitFor.length, ...imported); console.log("waiting for: ", subbedModIdsToWaitFor); @@ -59,6 +68,13 @@ const ShareMods = memo((props: ShareModsProps) => { useEffect(() => { const interval = setInterval(() => { + if ( + importedMods.length > 0 && + importedMods.every((importedMod) => mods.some((mod) => mod.workshopId == importedMod.workshopId)) + ) { + dispatch(orderImportedMods()); + } + const newMods = subbedModIdsToWaitFor.filter((subbedMod) => mods.find((iterMod) => iterMod.workshopId === subbedMod.workshopId) ); @@ -77,84 +93,88 @@ const ShareMods = memo((props: ShareModsProps) => { return ( <> - setIsSpinnerOpen(false)} - // show={true} - show={ - isSpinnerOpen && - !subbedModIdsToWaitFor.every((subbedMod) => - mods.find((iterMod) => iterMod.workshopId === subbedMod.workshopId) - ) - } - size="2xl" - position="center" - > - Waiting For Mods To Download... - -

- We're now subscribed to the mods, but there is a chance Steam won't download new mods while the - mod manager is running. Close the manager, wait for Steam to download the mods and import mods - agains if this takes more than 1 min. -

-
- -
-
-
- - Share Mod List - -
-
Export
-
-

- Share the enabled mods and their load order. Pressing the button will copy some text into your - clipboard, share that text with the other person. + {props.isOpen && ( + <> + setIsSpinnerOpen(false)} + // show={true} + show={ + isSpinnerOpen && + !subbedModIdsToWaitFor.every((subbedMod) => + mods.find((iterMod) => iterMod.workshopId === subbedMod.workshopId) + ) + } + size="2xl" + position="center" + > + Waiting For Mods To Download... + +

+ We're now subscribed to the mods, but there is a chance Steam won't download new mods while + the mod manager is running. Close the manager, wait for Steam to download the mods and import + mods agains if this takes more than 1 min. +

+
+ +
+ + + + Share Mod List + +
+
Export
+
+

+ Share the enabled mods and their load order. Pressing the button will copy some text into + your clipboard, share that text with the other person. +

+ +
+ +
+
+
+ +
Import
+

+ Import shared mods. Paste the exported text the other person has shared with you. This will + subscribe to, download and enable those mods.

+
-
-
- -
Import
-

- Import shared mods. Paste the exported text the other person has shared with you. This will - subscribe to, download and enable those mods. -

- - -
- -
-
-
+ + + + )} ); }); diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 6123b47..cefb9ed 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -47,10 +47,11 @@ const Sidebar = React.memo(() => { const allMods = useAppSelector((state) => state.app.allMods); const alwaysEnabledMods = useAppSelector((state) => state.app.alwaysEnabledMods); const lastSelectedPreset: Preset | null = useAppSelector((state) => state.app.lastSelectedPreset); + const areModsInOrder = useAppSelector((state) => state.app.currentPreset.version) != undefined; const playGameClicked = () => { dispatch(createOnGameStartPreset()); - window.api?.startGame(mods, { + window.api?.startGame(mods, areModsInOrder, { isMakeUnitsGeneralsEnabled, isSkipIntroMoviesEnabled, isScriptLoggingEnabled, @@ -117,6 +118,7 @@ const Sidebar = React.memo(() => { const onContinueGameClicked = () => { window.api?.startGame( mods, + areModsInOrder, { isMakeUnitsGeneralsEnabled, isSkipIntroMoviesEnabled, diff --git a/src/index.d.ts b/src/index.d.ts index 9c543f5..4de5021 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -64,6 +64,7 @@ declare global { interface Preset { mods: Mod[]; name: string; + version?: number; } interface NewMergedPack { path: string; @@ -73,6 +74,7 @@ declare global { interface AppState { categories: string[]; currentPreset: Preset; + importedMods: ModIdAndLoadOrder[]; presets: Preset[]; lastSelectedPreset: Preset | null; filter: string; @@ -109,7 +111,7 @@ declare global { toasts: Toast[]; removedModsCategories: Record; dataModsToEnableByName: string[]; - removedEnabledModPaths: string[]; + removedModsData: RemovedModData[]; modRowsSortingType: SortingType; } @@ -147,6 +149,11 @@ declare global { originalOrder?: number; } + interface ModLoadOrderRelativeTo { + modNameToChange: string; + modNameRelativeTo: string; + } + interface ModUpdateExists { updateExists: boolean; downloadURL?: string; @@ -236,6 +243,12 @@ declare global { isEnabled: boolean; } + interface RemovedModData { + isEnabled: boolean; + modPath: string; + indexInMods: number; + } + type ToastType = "success" | "warning" | "info"; type MainWindowTab = "mods" | "enabledMods" | "categories"; diff --git a/src/index.ts b/src/index.ts index 9a01e22..238b24e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1413,7 +1413,13 @@ if (!gotTheLock) { ipcMain.on( "startGame", - async (event, mods: Mod[], startGameOptions: StartGameOptions, saveName?: string) => { + async ( + event, + mods: Mod[], + areModsPresorted: boolean, + startGameOptions: StartGameOptions, + saveName?: string + ) => { console.log("before start:"); for (const pack of appData.packsData) { console.log(pack.name, pack.readTables); @@ -1425,7 +1431,7 @@ if (!gotTheLock) { const myModsPath = nodePath.join(appData.gamePath, "my_mods.txt"); const usedModsPath = nodePath.join(appData.gamePath, "used_mods.txt"); - const sortedMods = sortByNameAndLoadOrder(mods); + const sortedMods = (areModsPresorted && mods) || sortByNameAndLoadOrder(mods); const enabledMods = sortedMods.filter((mod) => mod.isEnabled); const linuxBit = process.platform === "linux" ? "Z:" : ""; diff --git a/src/modSortingHelpers.ts b/src/modSortingHelpers.ts index 36c6a51..9866250 100644 --- a/src/modSortingHelpers.ts +++ b/src/modSortingHelpers.ts @@ -15,6 +15,18 @@ export function sortByNameAndLoadOrder(mods: Mod[]) { return newMods; } +export function sortAsInPreset(mods: Mod[], modsInPreset: Mod[]) { + const newMods = [...mods].sort((modF, modS) => { + const modInPresetIndexF = modsInPreset.findIndex((iterMod) => iterMod.name == modF.name); + const modInPresetIndexS = modsInPreset.findIndex((iterMod) => iterMod.name == modS.name); + + if (modInPresetIndexF != -1 && modInPresetIndexS != -1) return modInPresetIndexF - modInPresetIndexS; + + return compareModNames(modF.name, modS.name); + }); + return newMods; +} + export function getModsSortedByOrder(mods: Mod[], orderedMods: Mod[]) { return [...mods].sort( (firstMod, secondMod) => orderedMods.indexOf(firstMod) - orderedMods.indexOf(secondMod) diff --git a/src/preload.ts b/src/preload.ts index f37ddd3..b35e042 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -6,8 +6,8 @@ import { AppFolderPaths } from "./appData"; console.log("IN PRELOAD"); const api = { - startGame: (mods: Mod[], startGameOptions: StartGameOptions, name?: string) => - ipcRenderer.send("startGame", mods, startGameOptions, name), + startGame: (mods: Mod[], areModsInOrder: boolean, startGameOptions: StartGameOptions, name?: string) => + ipcRenderer.send("startGame", mods, areModsInOrder, startGameOptions, name), exportModsToClipboard: (mods: Mod[]) => ipcRenderer.send("exportModsToClipboard", mods), exportModNamesToClipboard: (mods: Mod[]) => ipcRenderer.send("exportModNamesToClipboard", mods), createSteamCollection: (mods: Mod[]) => ipcRenderer.send("createSteamCollection", mods), diff --git a/src/stories/Categories.stories.tsx b/src/stories/Categories.stories.tsx index 6a4a0be..376fc0c 100644 --- a/src/stories/Categories.stories.tsx +++ b/src/stories/Categories.stories.tsx @@ -46,8 +46,9 @@ export const MockedState: AppState = { toasts: [], removedModsCategories: {}, dataModsToEnableByName: [], - removedEnabledModPaths: [], + removedModsData: [], modRowsSortingType: SortingType.Ordered, + importedMods: [], }; // A super-simple mock of a redux store diff --git a/src/stories/ModRows.stories.tsx b/src/stories/ModRows.stories.tsx index 903cd1f..aa8c231 100644 --- a/src/stories/ModRows.stories.tsx +++ b/src/stories/ModRows.stories.tsx @@ -47,8 +47,9 @@ export const MockedState: AppState = { toasts: [], removedModsCategories: {}, dataModsToEnableByName: [], - removedEnabledModPaths: [], + removedModsData: [], modRowsSortingType: SortingType.Ordered, + importedMods: [], }; // A super-simple mock of a redux store