mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-12-10 13:17:48 +00:00
feat: unlink fastfiles via ModMan
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
#include "Binds.h"
|
#include "Binds.h"
|
||||||
|
|
||||||
|
#include "UnlinkingBinds.h"
|
||||||
#include "Web/Binds/DialogBinds.h"
|
#include "Web/Binds/DialogBinds.h"
|
||||||
#include "ZoneBinds.h"
|
#include "ZoneBinds.h"
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ namespace ui
|
|||||||
void RegisterAllBinds(webview::webview& wv)
|
void RegisterAllBinds(webview::webview& wv)
|
||||||
{
|
{
|
||||||
RegisterDialogHandlerBinds(wv);
|
RegisterDialogHandlerBinds(wv);
|
||||||
|
RegisterUnlinkingBinds(wv);
|
||||||
RegisterZoneBinds(wv);
|
RegisterZoneBinds(wv);
|
||||||
}
|
}
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|||||||
93
src/ModMan/Web/Binds/UnlinkingBinds.cpp
Normal file
93
src/ModMan/Web/Binds/UnlinkingBinds.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#include "UnlinkingBinds.h"
|
||||||
|
|
||||||
|
#include "Context/ModManContext.h"
|
||||||
|
#include "IObjWriter.h"
|
||||||
|
#include "SearchPath/OutputPathFilesystem.h"
|
||||||
|
#include "SearchPath/SearchPaths.h"
|
||||||
|
#include "Utils/PathUtils.h"
|
||||||
|
#include "Web/UiCommunication.h"
|
||||||
|
|
||||||
|
#include "Json/JsonExtension.h"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
class ZoneLoadedDto
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string zoneName;
|
||||||
|
std::string filePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zoneName, filePath);
|
||||||
|
|
||||||
|
class ZoneUnloadedDto
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string zoneName;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName);
|
||||||
|
|
||||||
|
result::Expected<NoResult, std::string> UnlinkZoneInDbThread(const std::string& zoneName)
|
||||||
|
{
|
||||||
|
const auto& context = ModManContext::Get().m_fast_file;
|
||||||
|
const auto existingZone = std::ranges::find_if(context.m_loaded_zones,
|
||||||
|
[&zoneName](const std::unique_ptr<Zone>& zone)
|
||||||
|
{
|
||||||
|
return zone->m_name == zoneName;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingZone == context.m_loaded_zones.end())
|
||||||
|
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
|
||||||
|
|
||||||
|
const auto& zone = *existingZone->get();
|
||||||
|
|
||||||
|
const auto* objWriter = IObjWriter::GetObjWriterForGame(zone.m_game_id);
|
||||||
|
|
||||||
|
const auto outputFolderPath = fs::path(utils::GetExecutablePath()).parent_path() / "zone_dump" / zoneName;
|
||||||
|
const auto outputFolderPathStr = outputFolderPath.string();
|
||||||
|
|
||||||
|
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
|
||||||
|
SearchPaths searchPaths;
|
||||||
|
AssetDumpingContext dumpingContext(zone, outputFolderPathStr, outputFolderOutputPath, searchPaths);
|
||||||
|
objWriter->DumpZone(dumpingContext);
|
||||||
|
|
||||||
|
return NoResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnlinkZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
|
||||||
|
{
|
||||||
|
ModManContext::Get().m_db_thread.Dispatch(
|
||||||
|
[&wv, id, zoneName]
|
||||||
|
{
|
||||||
|
auto result = UnlinkZoneInDbThread(zoneName);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
con::debug("Unlinked zone \"{}\"", zoneName);
|
||||||
|
ui::PromiseResolve(wv, id, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
con::warn("Failed to unlink zone \"{}\": {}", zoneName, result.error());
|
||||||
|
ui::PromiseReject(wv, id, std::move(result).error());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace ui
|
||||||
|
{
|
||||||
|
void RegisterUnlinkingBinds(webview::webview& wv)
|
||||||
|
{
|
||||||
|
BindAsync<std::string>(wv,
|
||||||
|
"unlinkZone",
|
||||||
|
[&wv](const std::string& id, std::string zoneName)
|
||||||
|
{
|
||||||
|
UnlinkZone(wv, id, std::move(zoneName));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} // namespace ui
|
||||||
8
src/ModMan/Web/Binds/UnlinkingBinds.h
Normal file
8
src/ModMan/Web/Binds/UnlinkingBinds.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Web/WebViewLib.h"
|
||||||
|
|
||||||
|
namespace ui
|
||||||
|
{
|
||||||
|
void RegisterUnlinkingBinds(webview::webview& wv);
|
||||||
|
} // namespace ui
|
||||||
@@ -33,7 +33,12 @@ namespace
|
|||||||
|
|
||||||
if (maybeZone)
|
if (maybeZone)
|
||||||
{
|
{
|
||||||
ui::PromiseResolve(wv, id, true);
|
ui::PromiseResolve(wv,
|
||||||
|
id,
|
||||||
|
ZoneLoadedDto{
|
||||||
|
.zoneName = maybeZone.value()->m_name,
|
||||||
|
.filePath = path,
|
||||||
|
});
|
||||||
con::debug("Loaded zone \"{}\"", maybeZone.value()->m_name);
|
con::debug("Loaded zone \"{}\"", maybeZone.value()->m_name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { webviewBinds } from "@/native";
|
import { webviewBinds } from "@/native";
|
||||||
import { useZoneStore } from "@/stores/ZoneStore";
|
import { useZoneStore } from "@/stores/ZoneStore";
|
||||||
import SpinningLoader from "@/components/SpinningLoader.vue";
|
import SpinningLoader from "@/components/SpinningLoader.vue";
|
||||||
|
|
||||||
const zoneStore = useZoneStore();
|
const zoneStore = useZoneStore();
|
||||||
const lastPath = ref("");
|
|
||||||
const loadingFastFile = ref(false);
|
const loadingFastFile = ref(false);
|
||||||
|
const unlinkingFastFile = ref(false);
|
||||||
|
|
||||||
async function onOpenFastfileClick() {
|
const performingAction = computed<boolean>(() => loadingFastFile.value || unlinkingFastFile.value);
|
||||||
lastPath.value =
|
|
||||||
(await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] })) ?? "";
|
async function openFastFileSelect() {
|
||||||
|
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onOpenFastFileClick() {
|
||||||
|
if (performingAction.value) return;
|
||||||
|
|
||||||
|
const fastFilePath = await openFastFileSelect();
|
||||||
|
if (!fastFilePath) return;
|
||||||
|
|
||||||
loadingFastFile.value = true;
|
loadingFastFile.value = true;
|
||||||
|
|
||||||
webviewBinds
|
webviewBinds
|
||||||
.loadFastFile(lastPath.value)
|
.loadFastFile(fastFilePath)
|
||||||
.catch((e: string) => {
|
.catch((e: string) => {
|
||||||
console.error("Failed to load fastfile:", e);
|
console.error("Failed to load fastfile:", e);
|
||||||
})
|
})
|
||||||
@@ -24,6 +32,36 @@ async function onOpenFastfileClick() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onUnlinkFastFileClick() {
|
||||||
|
if (performingAction.value) return;
|
||||||
|
|
||||||
|
const fastFilePath = await openFastFileSelect();
|
||||||
|
if (!fastFilePath) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
unlinkingFastFile.value = true;
|
||||||
|
|
||||||
|
let loadedZoneName: string;
|
||||||
|
try {
|
||||||
|
loadedZoneName = (await webviewBinds.loadFastFile(fastFilePath)).zoneName;
|
||||||
|
} catch (e: unknown) {
|
||||||
|
console.error("Failed to load fastfile:", e as string);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await webviewBinds.unlinkZone(loadedZoneName);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
console.error("Failed to unlink fastfile:", e as string);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
webviewBinds.unloadZone(loadedZoneName);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
unlinkingFastFile.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onUnloadClicked(zoneName: string) {
|
function onUnloadClicked(zoneName: string) {
|
||||||
webviewBinds.unloadZone(zoneName).catch((e: string) => {
|
webviewBinds.unloadZone(zoneName).catch((e: string) => {
|
||||||
console.error("Failed to unload zone:", e);
|
console.error("Failed to unload zone:", e);
|
||||||
@@ -36,18 +74,23 @@ function onUnloadClicked(zoneName: string) {
|
|||||||
<h1>Welcome to ModMan</h1>
|
<h1>Welcome to ModMan</h1>
|
||||||
<small>Nothing to see here yet, this is mainly for testing</small>
|
<small>Nothing to see here yet, this is mainly for testing</small>
|
||||||
|
|
||||||
<p>
|
<div class="actions">
|
||||||
<button :disabled="loadingFastFile" @click="onOpenFastfileClick">
|
<button :disabled="performingAction" @click="onOpenFastFileClick">
|
||||||
<SpinningLoader v-if="loadingFastFile" class="loading" />
|
<SpinningLoader v-if="loadingFastFile" class="loading" />
|
||||||
<span>Load fastfile</span>
|
<span>Load fastfile</span>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
<button :disabled="performingAction" @click="onUnlinkFastFileClick">
|
||||||
|
<SpinningLoader v-if="unlinkingFastFile" class="loading" />
|
||||||
|
<span>Unlink fastfile</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3>Loaded zones:</h3>
|
<h3>Loaded zones:</h3>
|
||||||
<div class="zone-list">
|
<div class="zone-list">
|
||||||
<div v-for="zone in zoneStore.loadedZones" :key="zone" class="zone">
|
<div v-for="zone in zoneStore.loadedZones" :key="zone" class="zone">
|
||||||
<span>{{ zone }}</span>
|
<span>{{ zone }}</span>
|
||||||
<button @click="onUnloadClicked(zone)">Unload</button>
|
<button :disabled="performingAction" @click="onUnloadClicked(zone)">Unload</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,6 +98,12 @@ function onUnloadClicked(zoneName: string) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
column-gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
margin-right: 0.2em;
|
margin-right: 0.2em;
|
||||||
}
|
}
|
||||||
|
|||||||
3
src/ModManUi/src/native/UnlinkingBinds.ts
Normal file
3
src/ModManUi/src/native/UnlinkingBinds.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface UnlinkingBinds {
|
||||||
|
unlinkZone(zoneName: string): Promise<void>;
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ export interface ZoneUnloadedDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ZoneBinds {
|
export interface ZoneBinds {
|
||||||
loadFastFile(path: string): Promise<void>;
|
loadFastFile(path: string): Promise<ZoneLoadedDto>;
|
||||||
unloadZone(zoneName: string): Promise<void>;
|
unloadZone(zoneName: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { DialogBinds } from "./DialogBinds";
|
import type { DialogBinds } from "./DialogBinds";
|
||||||
|
import type { UnlinkingBinds } from "./UnlinkingBinds";
|
||||||
import type { ZoneBinds, ZoneEventMap } from "./ZoneBinds";
|
import type { ZoneBinds, ZoneEventMap } from "./ZoneBinds";
|
||||||
|
|
||||||
export type NativeMethods = DialogBinds & ZoneBinds;
|
export type NativeMethods = DialogBinds & UnlinkingBinds & ZoneBinds;
|
||||||
|
|
||||||
type NativeEventMap = ZoneEventMap;
|
type NativeEventMap = ZoneEventMap;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user