mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-11-17 18:52:06 +00:00
feat: combine loading bar and zone list
This commit is contained in:
@@ -10,7 +10,7 @@ namespace fs = std::filesystem;
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
constexpr double MIN_PROGRESS_TO_REPORT = 0.005;
|
constexpr double MIN_PROGRESS_TO_REPORT = 0.5;
|
||||||
|
|
||||||
class LoadingEventProgressReporter : public ProgressCallback
|
class LoadingEventProgressReporter : public ProgressCallback
|
||||||
{
|
{
|
||||||
@@ -23,7 +23,7 @@ namespace
|
|||||||
|
|
||||||
void OnProgress(const size_t current, const size_t total) override
|
void OnProgress(const size_t current, const size_t total) override
|
||||||
{
|
{
|
||||||
const double percentage = static_cast<double>(current) / static_cast<double>(total);
|
const double percentage = static_cast<double>(current) / static_cast<double>(total) * 100.0;
|
||||||
|
|
||||||
if (percentage - m_last_progress >= MIN_PROGRESS_TO_REPORT)
|
if (percentage - m_last_progress >= MIN_PROGRESS_TO_REPORT)
|
||||||
{
|
{
|
||||||
@@ -38,31 +38,45 @@ namespace
|
|||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
LoadedZone::LoadedZone(std::unique_ptr<Zone> zone, std::string filePath)
|
||||||
|
: m_zone(std::move(zone)),
|
||||||
|
m_file_path(std::move(filePath))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void FastFileContext::Destroy()
|
void FastFileContext::Destroy()
|
||||||
{
|
{
|
||||||
// Unload all zones
|
// Unload all zones
|
||||||
m_loaded_zones.clear();
|
m_loaded_zones.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
result::Expected<Zone*, std::string> FastFileContext::LoadFastFile(const std::string& path)
|
result::Expected<LoadedZone*, std::string> FastFileContext::LoadFastFile(const std::string& path)
|
||||||
{
|
{
|
||||||
auto zone = ZoneLoading::LoadZone(path, std::make_unique<LoadingEventProgressReporter>(fs::path(path).filename().replace_extension().string()));
|
auto zone = ZoneLoading::LoadZone(path, std::make_unique<LoadingEventProgressReporter>(fs::path(path).filename().replace_extension().string()));
|
||||||
if (!zone)
|
if (!zone)
|
||||||
return result::Unexpected(std::move(zone.error()));
|
return result::Unexpected(std::move(zone.error()));
|
||||||
|
|
||||||
auto* result = m_loaded_zones.emplace_back(std::move(*zone)).get();
|
auto loadedZone = std::make_unique<LoadedZone>(std::move(*zone), path);
|
||||||
|
|
||||||
ui::NotifyZoneLoaded(result->m_name, path);
|
LoadedZone* result;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_zone_lock);
|
||||||
|
result = m_loaded_zones.emplace_back(std::move(loadedZone)).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui::NotifyZoneLoaded(*result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result::Expected<NoResult, std::string> FastFileContext::UnloadZone(const std::string& zoneName)
|
result::Expected<NoResult, std::string> FastFileContext::UnloadZone(const std::string& zoneName)
|
||||||
{
|
{
|
||||||
const auto existingZone = std::ranges::find_if(m_loaded_zones,
|
|
||||||
[&zoneName](const std::unique_ptr<Zone>& zone)
|
|
||||||
{
|
{
|
||||||
return zone->m_name == zoneName;
|
std::lock_guard lock(m_zone_lock);
|
||||||
|
const auto existingZone = std::ranges::find_if(m_loaded_zones,
|
||||||
|
[&zoneName](const std::unique_ptr<LoadedZone>& loadedZone)
|
||||||
|
{
|
||||||
|
return loadedZone->m_zone->m_name == zoneName;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingZone != m_loaded_zones.end())
|
if (existingZone != m_loaded_zones.end())
|
||||||
@@ -71,6 +85,7 @@ result::Expected<NoResult, std::string> FastFileContext::UnloadZone(const std::s
|
|||||||
ui::NotifyZoneUnloaded(zoneName);
|
ui::NotifyZoneUnloaded(zoneName);
|
||||||
return NoResult();
|
return NoResult();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
|
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,26 @@
|
|||||||
#include "Zone/Zone.h"
|
#include "Zone/Zone.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
class LoadedZone
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::unique_ptr<Zone> m_zone;
|
||||||
|
std::string m_file_path;
|
||||||
|
|
||||||
|
LoadedZone(std::unique_ptr<Zone> zone, std::string filePath);
|
||||||
|
};
|
||||||
|
|
||||||
class FastFileContext
|
class FastFileContext
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void Destroy();
|
void Destroy();
|
||||||
|
|
||||||
result::Expected<Zone*, std::string> LoadFastFile(const std::string& path);
|
result::Expected<LoadedZone*, std::string> LoadFastFile(const std::string& path);
|
||||||
result::Expected<NoResult, std::string> UnloadZone(const std::string& zoneName);
|
result::Expected<NoResult, std::string> UnloadZone(const std::string& zoneName);
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Zone>> m_loaded_zones;
|
std::vector<std::unique_ptr<LoadedZone>> m_loaded_zones;
|
||||||
|
std::shared_mutex m_zone_lock;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace
|
|||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnlinkProgressDto, zoneName, percentage);
|
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnlinkProgressDto, zoneName, percentage);
|
||||||
|
|
||||||
constexpr double MIN_PROGRESS_TO_REPORT = 0.005;
|
constexpr double MIN_PROGRESS_TO_REPORT = 0.5;
|
||||||
|
|
||||||
class UnlinkingEventProgressReporter : public ProgressCallback
|
class UnlinkingEventProgressReporter : public ProgressCallback
|
||||||
{
|
{
|
||||||
@@ -36,7 +36,7 @@ namespace
|
|||||||
|
|
||||||
void OnProgress(const size_t current, const size_t total) override
|
void OnProgress(const size_t current, const size_t total) override
|
||||||
{
|
{
|
||||||
const double percentage = static_cast<double>(current) / static_cast<double>(total);
|
const double percentage = static_cast<double>(current) / static_cast<double>(total) * 100.0;
|
||||||
|
|
||||||
if (percentage - m_last_progress >= MIN_PROGRESS_TO_REPORT)
|
if (percentage - m_last_progress >= MIN_PROGRESS_TO_REPORT)
|
||||||
{
|
{
|
||||||
@@ -54,17 +54,17 @@ namespace
|
|||||||
{
|
{
|
||||||
const auto& context = ModManContext::Get().m_fast_file;
|
const auto& context = ModManContext::Get().m_fast_file;
|
||||||
const auto existingZone = std::ranges::find_if(context.m_loaded_zones,
|
const auto existingZone = std::ranges::find_if(context.m_loaded_zones,
|
||||||
[&zoneName](const std::unique_ptr<Zone>& zone)
|
[&zoneName](const std::unique_ptr<LoadedZone>& loadedZone)
|
||||||
{
|
{
|
||||||
return zone->m_name == zoneName;
|
return loadedZone->m_zone->m_name == zoneName;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingZone == context.m_loaded_zones.end())
|
if (existingZone == context.m_loaded_zones.end())
|
||||||
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
|
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
|
||||||
|
|
||||||
const auto& zone = *existingZone->get();
|
const auto& loadedZone = *existingZone->get();
|
||||||
|
|
||||||
const auto* objWriter = IObjWriter::GetObjWriterForGame(zone.m_game_id);
|
const auto* objWriter = IObjWriter::GetObjWriterForGame(loadedZone.m_zone->m_game_id);
|
||||||
|
|
||||||
const auto outputFolderPath = fs::path(utils::GetExecutablePath()).parent_path() / "zone_dump" / zoneName;
|
const auto outputFolderPath = fs::path(utils::GetExecutablePath()).parent_path() / "zone_dump" / zoneName;
|
||||||
const auto outputFolderPathStr = outputFolderPath.string();
|
const auto outputFolderPathStr = outputFolderPath.string();
|
||||||
@@ -72,7 +72,7 @@ namespace
|
|||||||
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
|
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
|
||||||
SearchPaths searchPaths;
|
SearchPaths searchPaths;
|
||||||
AssetDumpingContext dumpingContext(
|
AssetDumpingContext dumpingContext(
|
||||||
zone, outputFolderPathStr, outputFolderOutputPath, searchPaths, std::make_unique<UnlinkingEventProgressReporter>(zoneName));
|
*loadedZone.m_zone, outputFolderPathStr, outputFolderOutputPath, searchPaths, std::make_unique<UnlinkingEventProgressReporter>(zoneName));
|
||||||
objWriter->DumpZone(dumpingContext);
|
objWriter->DumpZone(dumpingContext);
|
||||||
|
|
||||||
return NoResult();
|
return NoResult();
|
||||||
|
|||||||
@@ -7,6 +7,15 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
class ZoneDto
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string name;
|
||||||
|
std::string filePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneDto, name, filePath);
|
||||||
|
|
||||||
class ZoneLoadProgressDto
|
class ZoneLoadProgressDto
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -19,11 +28,10 @@ namespace
|
|||||||
class ZoneLoadedDto
|
class ZoneLoadedDto
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::string zoneName;
|
ZoneDto zone;
|
||||||
std::string filePath;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zoneName, filePath);
|
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zone);
|
||||||
|
|
||||||
class ZoneUnloadedDto
|
class ZoneUnloadedDto
|
||||||
{
|
{
|
||||||
@@ -33,6 +41,33 @@ namespace
|
|||||||
|
|
||||||
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName);
|
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName);
|
||||||
|
|
||||||
|
ZoneDto CreateZoneDto(const LoadedZone& loadedZone)
|
||||||
|
{
|
||||||
|
return ZoneDto{
|
||||||
|
.name = loadedZone.m_zone->m_name,
|
||||||
|
.filePath = loadedZone.m_file_path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ZoneDto> GetLoadedZones()
|
||||||
|
{
|
||||||
|
auto& context = ModManContext::Get().m_fast_file;
|
||||||
|
|
||||||
|
std::vector<ZoneDto> result;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(context.m_zone_lock);
|
||||||
|
result.reserve(context.m_loaded_zones.size());
|
||||||
|
|
||||||
|
for (const auto& loadedZone : context.m_loaded_zones)
|
||||||
|
{
|
||||||
|
result.emplace_back(CreateZoneDto(*loadedZone));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void LoadFastFile(webview::webview& wv, std::string id, std::string path) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
|
void LoadFastFile(webview::webview& wv, std::string id, std::string path) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
|
||||||
{
|
{
|
||||||
ModManContext::Get().m_db_thread.Dispatch(
|
ModManContext::Get().m_db_thread.Dispatch(
|
||||||
@@ -45,10 +80,9 @@ namespace
|
|||||||
ui::PromiseResolve(wv,
|
ui::PromiseResolve(wv,
|
||||||
id,
|
id,
|
||||||
ZoneLoadedDto{
|
ZoneLoadedDto{
|
||||||
.zoneName = maybeZone.value()->m_name,
|
.zone = CreateZoneDto(*maybeZone.value()),
|
||||||
.filePath = path,
|
|
||||||
});
|
});
|
||||||
con::debug("Loaded zone \"{}\"", maybeZone.value()->m_name);
|
con::debug("Loaded zone \"{}\"", maybeZone.value()->m_zone->m_name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -89,11 +123,10 @@ namespace ui
|
|||||||
Notify(*ModManContext::Get().m_main_webview, "zoneLoadProgress", dto);
|
Notify(*ModManContext::Get().m_main_webview, "zoneLoadProgress", dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath)
|
void NotifyZoneLoaded(const LoadedZone& loadedZone)
|
||||||
{
|
{
|
||||||
const ZoneLoadedDto dto{
|
const ZoneLoadedDto dto{
|
||||||
.zoneName = std::move(zoneName),
|
.zone = CreateZoneDto(loadedZone),
|
||||||
.filePath = std::move(fastFilePath),
|
|
||||||
};
|
};
|
||||||
Notify(*ModManContext::Get().m_main_webview, "zoneLoaded", dto);
|
Notify(*ModManContext::Get().m_main_webview, "zoneLoaded", dto);
|
||||||
}
|
}
|
||||||
@@ -108,6 +141,13 @@ namespace ui
|
|||||||
|
|
||||||
void RegisterZoneBinds(webview::webview& wv)
|
void RegisterZoneBinds(webview::webview& wv)
|
||||||
{
|
{
|
||||||
|
BindRetOnly<std::vector<ZoneDto>>(wv,
|
||||||
|
"getZones",
|
||||||
|
[]
|
||||||
|
{
|
||||||
|
return GetLoadedZones();
|
||||||
|
});
|
||||||
|
|
||||||
BindAsync<std::string>(wv,
|
BindAsync<std::string>(wv,
|
||||||
"loadFastFile",
|
"loadFastFile",
|
||||||
[&wv](const std::string& id, std::string path)
|
[&wv](const std::string& id, std::string path)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Context/FastFileContext.h"
|
||||||
#include "Web/WebViewLib.h"
|
#include "Web/WebViewLib.h"
|
||||||
|
|
||||||
namespace ui
|
namespace ui
|
||||||
{
|
{
|
||||||
void NotifyZoneLoadProgress(std::string zoneName, double percentage);
|
void NotifyZoneLoadProgress(std::string zoneName, double percentage);
|
||||||
void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath);
|
void NotifyZoneLoaded(const LoadedZone& loadedZone);
|
||||||
void NotifyZoneUnloaded(std::string zoneName);
|
void NotifyZoneUnloaded(std::string zoneName);
|
||||||
|
|
||||||
void RegisterZoneBinds(webview::webview& wv);
|
void RegisterZoneBinds(webview::webview& wv);
|
||||||
|
|||||||
@@ -1,80 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Button from "primevue/button";
|
import Button from "primevue/button";
|
||||||
import { computed, ref } from "vue";
|
import { webviewBinds } from "@/native";
|
||||||
import { webviewAddEventListener, webviewBinds } from "@/native";
|
|
||||||
import ProgressBar from "primevue/progressbar";
|
|
||||||
import ZoneSelector from "./components/ZoneSelector.vue";
|
import ZoneSelector from "./components/ZoneSelector.vue";
|
||||||
|
import { useZoneStore } from "./stores/ZoneStore";
|
||||||
|
|
||||||
const loadingFastFile = ref(false);
|
const zoneStore = useZoneStore();
|
||||||
const unlinkingFastFile = ref(false);
|
|
||||||
const lastPercentage = ref<number>(0);
|
|
||||||
|
|
||||||
const performingAction = computed<boolean>(() => loadingFastFile.value || unlinkingFastFile.value);
|
|
||||||
|
|
||||||
async function openFastFileSelect() {
|
async function openFastFileSelect() {
|
||||||
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
|
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onOpenFastFileClick() {
|
async function onOpenFastFileClick() {
|
||||||
if (performingAction.value) return;
|
|
||||||
|
|
||||||
const fastFilePath = await openFastFileSelect();
|
const fastFilePath = await openFastFileSelect();
|
||||||
if (!fastFilePath) return;
|
if (!fastFilePath) return;
|
||||||
|
|
||||||
loadingFastFile.value = true;
|
zoneStore.loadFastFile(fastFilePath);
|
||||||
lastPercentage.value = 0;
|
|
||||||
|
|
||||||
webviewBinds
|
|
||||||
.loadFastFile(fastFilePath)
|
|
||||||
.catch((e: string) => {
|
|
||||||
console.error("Failed to load fastfile:", e);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loadingFastFile.value = false;
|
|
||||||
lastPercentage.value = 100;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onUnlinkFastFileClick() {
|
|
||||||
if (performingAction.value) return;
|
|
||||||
|
|
||||||
const fastFilePath = await openFastFileSelect();
|
|
||||||
if (!fastFilePath) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
unlinkingFastFile.value = true;
|
|
||||||
|
|
||||||
let loadedZoneName: string;
|
|
||||||
try {
|
|
||||||
lastPercentage.value = 0;
|
|
||||||
loadedZoneName = (await webviewBinds.loadFastFile(fastFilePath)).zoneName;
|
|
||||||
} catch (e: unknown) {
|
|
||||||
console.error("Failed to load fastfile:", e as string);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
lastPercentage.value = 0;
|
|
||||||
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;
|
|
||||||
lastPercentage.value = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
webviewAddEventListener("zoneLoadProgress", (dto) => {
|
|
||||||
lastPercentage.value = Math.floor(dto.percentage * 1000) / 10;
|
|
||||||
});
|
|
||||||
|
|
||||||
webviewAddEventListener("zoneUnlinkProgress", (dto) => {
|
|
||||||
lastPercentage.value = Math.floor(dto.percentage * 1000) / 10;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -83,28 +24,10 @@ webviewAddEventListener("zoneUnlinkProgress", (dto) => {
|
|||||||
<small>Nothing to see here yet, this is mainly for testing</small>
|
<small>Nothing to see here yet, this is mainly for testing</small>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<Button
|
<Button label="Load fastfile" @click="onOpenFastFileClick" />
|
||||||
label="Load fastfile"
|
|
||||||
:disabled="performingAction"
|
|
||||||
:loading="loadingFastFile"
|
|
||||||
@click="onOpenFastFileClick"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
label="Unlink fastfile"
|
|
||||||
:disabled="performingAction"
|
|
||||||
:loading="unlinkingFastFile"
|
|
||||||
@click="onUnlinkFastFileClick"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ZoneSelector />
|
<ZoneSelector />
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
v-if="performingAction"
|
|
||||||
class="progressbar"
|
|
||||||
:show-value="false"
|
|
||||||
:value="lastPercentage"
|
|
||||||
/>
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -113,6 +36,7 @@ webviewAddEventListener("zoneUnlinkProgress", (dto) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
column-gap: 0.5em;
|
column-gap: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zone-list {
|
.zone-list {
|
||||||
|
|||||||
@@ -1,17 +1,46 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Button from "primevue/button";
|
import Button from "primevue/button";
|
||||||
|
import ProgressBar from "primevue/progressbar";
|
||||||
import Listbox from "primevue/listbox";
|
import Listbox from "primevue/listbox";
|
||||||
import { ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
import { useZoneStore } from "@/stores/ZoneStore";
|
import { useZoneStore } from "@/stores/ZoneStore";
|
||||||
import { webviewBinds } from "@/native";
|
import { webviewBinds } from "@/native";
|
||||||
|
|
||||||
|
interface SelectableZone {
|
||||||
|
isLoading: boolean;
|
||||||
|
zoneName: string;
|
||||||
|
}
|
||||||
|
|
||||||
const zoneStore = useZoneStore();
|
const zoneStore = useZoneStore();
|
||||||
const selectedZone = ref<string | null>(null);
|
const selectedZone = ref<SelectableZone | null>(null);
|
||||||
|
|
||||||
|
const availableZones = computed<SelectableZone[]>(() => {
|
||||||
|
const result = [
|
||||||
|
...zoneStore.zonesCurrentlyBeingLoaded.map(
|
||||||
|
(zoneBeingLoaded) =>
|
||||||
|
({
|
||||||
|
isLoading: true,
|
||||||
|
zoneName: zoneBeingLoaded,
|
||||||
|
}) satisfies SelectableZone,
|
||||||
|
),
|
||||||
|
...zoneStore.loadedZones.map(
|
||||||
|
(loadedZone) =>
|
||||||
|
({
|
||||||
|
isLoading: false,
|
||||||
|
zoneName: loadedZone.name,
|
||||||
|
}) satisfies SelectableZone,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
result.sort((a, b) => a.zoneName.localeCompare(b.zoneName));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
function onUnloadClicked() {
|
function onUnloadClicked() {
|
||||||
if (!selectedZone.value) return;
|
if (!selectedZone.value) return;
|
||||||
|
|
||||||
webviewBinds.unloadZone(selectedZone.value).catch((e: string) => {
|
webviewBinds.unloadZone(selectedZone.value.zoneName).catch((e: string) => {
|
||||||
console.error("Failed to unload zone:", e);
|
console.error("Failed to unload zone:", e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -21,7 +50,8 @@ watch(
|
|||||||
(newValue) => {
|
(newValue) => {
|
||||||
// Reset selection if unloaded
|
// Reset selection if unloaded
|
||||||
if (!selectedZone.value) return;
|
if (!selectedZone.value) return;
|
||||||
if (newValue.indexOf(selectedZone.value) >= 0) return;
|
if (newValue.findIndex((loadedZone) => loadedZone.name === selectedZone.value?.zoneName) >= 0)
|
||||||
|
return;
|
||||||
selectedZone.value = null;
|
selectedZone.value = null;
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
@@ -31,7 +61,25 @@ watch(
|
|||||||
<template>
|
<template>
|
||||||
<div class="zone-selector">
|
<div class="zone-selector">
|
||||||
<div class="zone-list">
|
<div class="zone-list">
|
||||||
<Listbox v-model="selectedZone" :options="zoneStore.loadedZones" class="zone" />
|
<Listbox
|
||||||
|
v-model="selectedZone"
|
||||||
|
:options="availableZones"
|
||||||
|
data-key="zoneName"
|
||||||
|
emptyMessage="No zones loaded"
|
||||||
|
class="zone"
|
||||||
|
>
|
||||||
|
<template #option="{ option }: { option: SelectableZone }">
|
||||||
|
<div class="selectable-zone">
|
||||||
|
<span>{{ option.zoneName }}</span>
|
||||||
|
<ProgressBar
|
||||||
|
v-if="option.isLoading"
|
||||||
|
class="zone-progressbar"
|
||||||
|
:value="zoneStore.getPercentageForZoneBeingLoaded(option.zoneName)"
|
||||||
|
:show-value="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Listbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="zone-actions">
|
<div class="zone-actions">
|
||||||
@@ -51,4 +99,18 @@ watch(
|
|||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selectable-zone {
|
||||||
|
position: relative;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-progressbar {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 0.2em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
export interface ZoneUnlinkProgressDto {
|
export interface ZoneUnlinkProgressDto {
|
||||||
zoneName: string;
|
zoneName: string;
|
||||||
|
/**
|
||||||
|
* Between 0-100
|
||||||
|
*/
|
||||||
percentage: number;
|
percentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
|
export interface ZoneDto {
|
||||||
|
name: string;
|
||||||
|
filePath: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ZoneLoadProgressDto {
|
export interface ZoneLoadProgressDto {
|
||||||
zoneName: string;
|
zoneName: string;
|
||||||
|
/**
|
||||||
|
* Between 0-100
|
||||||
|
*/
|
||||||
percentage: number;
|
percentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZoneLoadedDto {
|
export interface ZoneLoadedDto {
|
||||||
zoneName: string;
|
zone: ZoneDto;
|
||||||
filePath: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZoneUnloadedDto {
|
export interface ZoneUnloadedDto {
|
||||||
@@ -13,6 +20,7 @@ export interface ZoneUnloadedDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ZoneBinds {
|
export interface ZoneBinds {
|
||||||
|
getZones(): Promise<ZoneDto[]>;
|
||||||
loadFastFile(path: string): Promise<ZoneLoadedDto>;
|
loadFastFile(path: string): Promise<ZoneLoadedDto>;
|
||||||
unloadZone(zoneName: string): Promise<void>;
|
unloadZone(zoneName: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/ModManUi/src/stores/UnlinkingStore.ts
Normal file
31
src/ModManUi/src/stores/UnlinkingStore.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { webviewAddEventListener, webviewBinds } from "@/native";
|
||||||
|
|
||||||
|
export const useUnlinkingStore = defineStore("unlinking", () => {
|
||||||
|
const isUnlinking = ref(false);
|
||||||
|
const lastPercentage = ref<number>(0);
|
||||||
|
const failureMessage = ref<string | null>(null);
|
||||||
|
|
||||||
|
function unlinkZone(zoneName: string) {
|
||||||
|
isUnlinking.value = true;
|
||||||
|
lastPercentage.value = 0;
|
||||||
|
failureMessage.value = null;
|
||||||
|
return webviewBinds
|
||||||
|
.unlinkZone(zoneName)
|
||||||
|
.catch((e: string) => {
|
||||||
|
console.error("Failed to unlink fastfile:", e);
|
||||||
|
failureMessage.value = e;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isUnlinking.value = false;
|
||||||
|
lastPercentage.value = 100;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
webviewAddEventListener("zoneUnlinkProgress", (dto) => {
|
||||||
|
lastPercentage.value = dto.percentage;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isUnlinking, lastPercentage, unlinkZone };
|
||||||
|
});
|
||||||
@@ -1,20 +1,72 @@
|
|||||||
import { ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { webviewAddEventListener } from "@/native";
|
import { webviewAddEventListener, webviewBinds } from "@/native";
|
||||||
|
import type { ZoneDto, ZoneLoadedDto } from "@/native/ZoneBinds";
|
||||||
|
|
||||||
export const useZoneStore = defineStore("zone", () => {
|
export const useZoneStore = defineStore("zone", () => {
|
||||||
const loadedZones = ref<string[]>([]);
|
const loadedZones = ref<ZoneDto[]>([]);
|
||||||
|
const zonesCurrentlyBeingLoaded = ref<string[]>([]);
|
||||||
|
const lastPercentageByZoneName = ref<Record<string, number>>({});
|
||||||
|
|
||||||
|
const isLoadingZone = computed(() => zonesCurrentlyBeingLoaded.value.length > 0);
|
||||||
|
|
||||||
|
function loadFastFile(fastFilePath: string): Promise<ZoneLoadedDto> {
|
||||||
|
const lastDirectorySeparator = fastFilePath.replace(/\\/g, "/").lastIndexOf("/");
|
||||||
|
const lastDot = fastFilePath.lastIndexOf(".");
|
||||||
|
const expectedZoneName = fastFilePath.substring(
|
||||||
|
lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0,
|
||||||
|
lastDot > lastDirectorySeparator ? lastDot : fastFilePath.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
zonesCurrentlyBeingLoaded.value.push(expectedZoneName);
|
||||||
|
lastPercentageByZoneName.value[expectedZoneName] = 0;
|
||||||
|
|
||||||
|
return webviewBinds
|
||||||
|
.loadFastFile(fastFilePath)
|
||||||
|
.catch((e: string) => {
|
||||||
|
console.error("Failed to load fastfile:", e);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
zonesCurrentlyBeingLoaded.value.splice(
|
||||||
|
zonesCurrentlyBeingLoaded.value.indexOf(expectedZoneName),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
delete lastPercentageByZoneName.value[expectedZoneName];
|
||||||
|
}) as Promise<ZoneLoadedDto>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPercentageForZoneBeingLoaded(zoneName: string) {
|
||||||
|
return lastPercentageByZoneName.value[zoneName] ?? 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially get all loaded zones
|
||||||
|
webviewBinds.getZones().then((allZones) => {
|
||||||
|
loadedZones.value = allZones;
|
||||||
|
});
|
||||||
|
|
||||||
|
webviewAddEventListener("zoneLoadProgress", (dto) => {
|
||||||
|
if (lastPercentageByZoneName.value[dto.zoneName] !== undefined) {
|
||||||
|
lastPercentageByZoneName.value[dto.zoneName] = dto.percentage;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
webviewAddEventListener("zoneLoaded", (dto) => {
|
webviewAddEventListener("zoneLoaded", (dto) => {
|
||||||
loadedZones.value.push(dto.zoneName);
|
loadedZones.value.push(dto.zone);
|
||||||
});
|
});
|
||||||
|
|
||||||
webviewAddEventListener("zoneUnloaded", (dto) => {
|
webviewAddEventListener("zoneUnloaded", (dto) => {
|
||||||
const index = loadedZones.value.indexOf(dto.zoneName);
|
const index = loadedZones.value.findIndex((zone) => zone.name === dto.zoneName);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
loadedZones.value.splice(index, 1);
|
loadedZones.value.splice(index, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { loadedZones };
|
return {
|
||||||
|
loadedZones,
|
||||||
|
zonesCurrentlyBeingLoaded,
|
||||||
|
isLoadingZone,
|
||||||
|
lastPercentageByZoneName,
|
||||||
|
loadFastFile,
|
||||||
|
getPercentageForZoneBeingLoaded,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user