mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-07-03 06:18:11 +00:00
refactor: use new webwindowed api (#831)
* chore: update webview with new api * chore: update modman to use new webview api * chore: use title handler plugin from webview lib * chore: use favicon plugin from webview lib * chore: use vite-plugin-cpp-header from webview repo * chore: use asset handler from webview lib * chore: make webview utility * chore: rename webview to webwindowed * chore: Rename code usages to webwindowed
This commit is contained in:
@@ -1,223 +0,0 @@
|
||||
import type { Plugin, UserConfig } from "vite";
|
||||
import type { OutputAsset, OutputChunk } from "rolldown";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
type MinimalOutputAsset = Pick<OutputAsset, "type" | "fileName" | "source">;
|
||||
type MinimalOutputChunk = Pick<OutputChunk, "type" | "fileName" | "code">;
|
||||
type MinimalOutputBundle = Record<string, MinimalOutputAsset | MinimalOutputChunk>;
|
||||
|
||||
interface PublicDirFile {
|
||||
fullPath: string;
|
||||
relativePath: string;
|
||||
}
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
function getPublicDirFiles(publicDir?: string): PublicDirFile[] {
|
||||
if (!publicDir) return [];
|
||||
|
||||
const result: PublicDirFile[] = [];
|
||||
const files = fs.readdirSync(publicDir, { recursive: true, withFileTypes: true });
|
||||
for (const file of files) {
|
||||
if (!file.isFile()) continue;
|
||||
const fullPath = path.join(file.parentPath, file.name);
|
||||
let relativePath = path.relative(publicDir, fullPath).replaceAll(/\\/g, "/");
|
||||
if (relativePath.startsWith("./")) {
|
||||
relativePath = relativePath.substring(2);
|
||||
}
|
||||
|
||||
result.push({
|
||||
fullPath,
|
||||
relativePath,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function createVarName(fileName: string) {
|
||||
return fileName.replaceAll(/[\\/]/g, "__").replaceAll(/[\.-]/g, "_").toUpperCase();
|
||||
}
|
||||
|
||||
function transformAsset(asset: MinimalOutputAsset) {
|
||||
const varName = createVarName(asset.fileName);
|
||||
|
||||
let buffer: Uint8Array;
|
||||
if (typeof asset.source === "string") {
|
||||
buffer = textEncoder.encode(asset.source);
|
||||
} else {
|
||||
buffer = asset.source;
|
||||
}
|
||||
|
||||
const bytes = [...buffer].map((v) => String(v)).join(",");
|
||||
|
||||
return `constexpr const unsigned char ${varName}[] {${bytes}};
|
||||
`;
|
||||
}
|
||||
|
||||
function transformChunk(chunk: MinimalOutputChunk) {
|
||||
const varName = createVarName(chunk.fileName);
|
||||
const buffer = textEncoder.encode(chunk.code);
|
||||
const bytes = [...buffer].map((v) => String(v)).join(",");
|
||||
|
||||
return `constexpr const unsigned char ${varName}[] {${bytes}};
|
||||
`;
|
||||
}
|
||||
|
||||
function transformPublicFile(publicFile: PublicDirFile) {
|
||||
const varName = createVarName(publicFile.relativePath);
|
||||
const bytes = [...fs.readFileSync(publicFile.fullPath)].map((v) => String(v)).join(",");
|
||||
|
||||
return `constexpr const unsigned char ${varName}[] {${bytes}};
|
||||
`;
|
||||
}
|
||||
|
||||
function writeHeader(
|
||||
bundle: MinimalOutputBundle,
|
||||
outputDir?: string,
|
||||
options?: HeaderTransformationPluginOptions,
|
||||
publicDir?: string,
|
||||
devServerPort?: number,
|
||||
) {
|
||||
const outputPath = options?.outputPath ?? path.join(outputDir ?? "dist", "ViteAssets.h");
|
||||
const outputPathParentDir = path.dirname(outputPath);
|
||||
|
||||
fs.mkdirSync(outputPathParentDir, { recursive: true });
|
||||
|
||||
const fd = fs.openSync(outputPath, "w");
|
||||
const includeFileEnumeration = options?.includeFileEnumeration ?? true;
|
||||
|
||||
fs.writeSync(
|
||||
fd,
|
||||
`#pragma once
|
||||
|
||||
`,
|
||||
);
|
||||
|
||||
if (includeFileEnumeration) {
|
||||
fs.writeSync(
|
||||
fd,
|
||||
`#include <cstdlib>
|
||||
#include <type_traits>
|
||||
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
fs.writeSync(
|
||||
fd,
|
||||
`constexpr auto VITE_DEV_SERVER = ${devServerPort ? "true" : "false"};
|
||||
constexpr auto VITE_DEV_SERVER_PORT = ${devServerPort ? String(devServerPort) : "-1"};
|
||||
`,
|
||||
);
|
||||
|
||||
const fileNames: string[] = [];
|
||||
for (const curBundle of Object.values(bundle)) {
|
||||
if (curBundle.type === "asset") {
|
||||
fs.writeSync(fd, transformAsset(curBundle));
|
||||
} else {
|
||||
fs.writeSync(fd, transformChunk(curBundle));
|
||||
}
|
||||
fileNames.push(curBundle.fileName);
|
||||
}
|
||||
for (const publicDirFile of getPublicDirFiles(publicDir)) {
|
||||
fs.writeSync(fd, transformPublicFile(publicDirFile));
|
||||
fileNames.push(publicDirFile.relativePath);
|
||||
}
|
||||
|
||||
if (includeFileEnumeration) {
|
||||
fs.writeSync(
|
||||
fd,
|
||||
`
|
||||
struct UiFile
|
||||
{
|
||||
const char* filename;
|
||||
const void* data;
|
||||
const size_t dataSize;
|
||||
};
|
||||
|
||||
static inline const UiFile MOD_MAN_UI_FILES[] {
|
||||
`,
|
||||
);
|
||||
|
||||
let index = 0;
|
||||
for (const fileName of fileNames) {
|
||||
const varName = createVarName(fileName);
|
||||
|
||||
let prefix = " ";
|
||||
if (index > 0) {
|
||||
prefix = `,
|
||||
`;
|
||||
}
|
||||
|
||||
fs.writeSync(
|
||||
fd,
|
||||
`${prefix}{ "${fileName}", ${varName}, std::extent_v<decltype(${varName})> }`,
|
||||
);
|
||||
index++;
|
||||
}
|
||||
|
||||
fs.writeSync(
|
||||
fd,
|
||||
`
|
||||
};
|
||||
`,
|
||||
);
|
||||
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
export interface HeaderTransformationPluginOptions {
|
||||
outputPath?: string;
|
||||
includeFileEnumeration?: boolean;
|
||||
}
|
||||
|
||||
export default function headerTransformationPlugin(
|
||||
options?: HeaderTransformationPluginOptions,
|
||||
): Plugin {
|
||||
let writeServerActive = false;
|
||||
let writeBundleActive = false;
|
||||
let publicDir: string | undefined = undefined;
|
||||
|
||||
return {
|
||||
name: "vite-plugin-header-transformation",
|
||||
enforce: "post",
|
||||
config(userOptions: UserConfig, env) {
|
||||
if (env.command === "serve") {
|
||||
writeServerActive = true;
|
||||
} else {
|
||||
writeBundleActive = true;
|
||||
}
|
||||
|
||||
if (typeof userOptions.publicDir === "string") {
|
||||
publicDir = userOptions.publicDir;
|
||||
}
|
||||
},
|
||||
configureServer(server) {
|
||||
if (!writeServerActive) {
|
||||
return;
|
||||
}
|
||||
server.httpServer?.once("listening", () => {
|
||||
writeHeader(
|
||||
{
|
||||
// We need at least one array entry for MSVC
|
||||
dummyfile: { type: "chunk", fileName: "dummyfile", code: "dummy" },
|
||||
},
|
||||
server.config.build.outDir,
|
||||
options,
|
||||
publicDir,
|
||||
server.config.server.port,
|
||||
);
|
||||
});
|
||||
},
|
||||
writeBundle(outputOptions, bundle) {
|
||||
if (!writeBundleActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeHeader(bundle, outputOptions.dir, options, publicDir);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
"vue-router": "5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@laupetin/vite-plugin-cpp-header": "1.1.0",
|
||||
"@tsconfig/node24": "24.0.4",
|
||||
"@types/jsdom": "28.0.3",
|
||||
"@types/node": "25.9.2",
|
||||
|
||||
@@ -7,20 +7,22 @@ export type NativeMethods = AssetBinds & DialogBinds & UnlinkingBinds & ZoneBind
|
||||
|
||||
type NativeEventMap = UnlinkingEventMap & ZoneEventMap;
|
||||
|
||||
type WebViewExtensions = {
|
||||
webviewBinds: NativeMethods;
|
||||
webviewAddEventListener<K extends keyof NativeEventMap>(
|
||||
type WebWindowedExtensions = {
|
||||
webwindowedBinds: NativeMethods;
|
||||
webwindowedAddEventListener<K extends keyof NativeEventMap>(
|
||||
eventKey: K,
|
||||
callback: (payload: NativeEventMap[K]) => void,
|
||||
): void;
|
||||
webviewRemoveEventListener<K extends keyof NativeEventMap>(
|
||||
webwindowedRemoveEventListener<K extends keyof NativeEventMap>(
|
||||
eventKey: K,
|
||||
callback: (payload: NativeEventMap[K]) => void,
|
||||
): boolean;
|
||||
};
|
||||
|
||||
const windowWithWebViewExtensions = window as typeof window & WebViewExtensions;
|
||||
const windowWithWebWindowedExtensions = window as typeof window & WebWindowedExtensions;
|
||||
|
||||
export const webviewBinds = windowWithWebViewExtensions.webviewBinds;
|
||||
export const webviewAddEventListener = windowWithWebViewExtensions.webviewAddEventListener;
|
||||
export const webviewRemoveEventListener = windowWithWebViewExtensions.webviewRemoveEventListener;
|
||||
export const webwindowedBinds = windowWithWebWindowedExtensions.webwindowedBinds;
|
||||
export const webwindowedAddEventListener =
|
||||
windowWithWebWindowedExtensions.webwindowedAddEventListener;
|
||||
export const webwindowedRemoveEventListener =
|
||||
windowWithWebWindowedExtensions.webwindowedRemoveEventListener;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { computed, ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import type { ZoneAssetsDto } from "@/native/AssetBinds";
|
||||
import { webviewBinds } from "@/native";
|
||||
import { webwindowedBinds } from "@/native";
|
||||
|
||||
export const useAssetStore = defineStore("asset", () => {
|
||||
const zoneName = ref<string | null>(null);
|
||||
@@ -21,7 +21,7 @@ export const useAssetStore = defineStore("asset", () => {
|
||||
|
||||
// Only load assets when there is a new zone name specified
|
||||
if (!newZoneName) return;
|
||||
webviewBinds.getAssetsForZone(newZoneName).then((res) => {
|
||||
webwindowedBinds.getAssetsForZone(newZoneName).then((res) => {
|
||||
if (zoneName.value === newZoneName) {
|
||||
assetsOfZone.value = res;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { webviewAddEventListener, webviewBinds } from "@/native";
|
||||
import { webwindowedAddEventListener, webwindowedBinds } from "@/native";
|
||||
|
||||
export const useUnlinkingStore = defineStore("unlinking", () => {
|
||||
const isUnlinking = ref(false);
|
||||
@@ -11,7 +11,7 @@ export const useUnlinkingStore = defineStore("unlinking", () => {
|
||||
isUnlinking.value = true;
|
||||
lastPercentage.value = 0;
|
||||
failureMessage.value = null;
|
||||
return webviewBinds
|
||||
return webwindowedBinds
|
||||
.unlinkZone(zoneName)
|
||||
.catch((e: string) => {
|
||||
console.error("Failed to unlink fastfile:", e);
|
||||
@@ -23,7 +23,7 @@ export const useUnlinkingStore = defineStore("unlinking", () => {
|
||||
});
|
||||
}
|
||||
|
||||
webviewAddEventListener("zoneUnlinkProgress", (dto) => {
|
||||
webwindowedAddEventListener("zoneUnlinkProgress", (dto) => {
|
||||
lastPercentage.value = dto.percentage;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { computed, ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { webviewAddEventListener, webviewBinds } from "@/native";
|
||||
import { webwindowedAddEventListener, webwindowedBinds } from "@/native";
|
||||
import type { ZoneDto, ZoneLoadedDto } from "@/native/ZoneBinds";
|
||||
|
||||
export const useZoneStore = defineStore("zone", () => {
|
||||
@@ -21,7 +21,7 @@ export const useZoneStore = defineStore("zone", () => {
|
||||
zonesCurrentlyBeingLoaded.value.push(expectedZoneName);
|
||||
lastPercentageByZoneName.value[expectedZoneName] = 0;
|
||||
|
||||
return webviewBinds
|
||||
return webwindowedBinds
|
||||
.loadFastFile(fastFilePath)
|
||||
.catch((e: string) => {
|
||||
console.error("Failed to load fastfile:", e);
|
||||
@@ -44,21 +44,21 @@ export const useZoneStore = defineStore("zone", () => {
|
||||
}
|
||||
|
||||
// Initially get all loaded zones
|
||||
webviewBinds.getZones().then((allZones) => {
|
||||
webwindowedBinds.getZones().then((allZones) => {
|
||||
loadedZones.value = allZones;
|
||||
});
|
||||
|
||||
webviewAddEventListener("zoneLoadProgress", (dto) => {
|
||||
webwindowedAddEventListener("zoneLoadProgress", (dto) => {
|
||||
if (lastPercentageByZoneName.value[dto.zoneName] !== undefined) {
|
||||
lastPercentageByZoneName.value[dto.zoneName] = dto.percentage;
|
||||
}
|
||||
});
|
||||
|
||||
webviewAddEventListener("zoneLoaded", (dto) => {
|
||||
webwindowedAddEventListener("zoneLoaded", (dto) => {
|
||||
loadedZones.value.push(dto.zone);
|
||||
});
|
||||
|
||||
webviewAddEventListener("zoneUnloaded", (dto) => {
|
||||
webwindowedAddEventListener("zoneUnloaded", (dto) => {
|
||||
const index = loadedZones.value.findIndex((zone) => zone.name === dto.zoneName);
|
||||
if (index >= 0) {
|
||||
loadedZones.value.splice(index, 1);
|
||||
|
||||
@@ -4,7 +4,7 @@ import ProgressBar from "primevue/progressbar";
|
||||
import Listbox from "primevue/listbox";
|
||||
import { computed } from "vue";
|
||||
import { useZoneStore } from "@/stores/ZoneStore";
|
||||
import { webviewBinds } from "@/native";
|
||||
import { webwindowedBinds } from "@/native";
|
||||
|
||||
interface SelectableZone {
|
||||
isLoading: boolean;
|
||||
@@ -15,7 +15,9 @@ const zoneStore = useZoneStore();
|
||||
const selectedZone = defineModel<string | null>("selectedZone");
|
||||
|
||||
async function openFastFileSelect() {
|
||||
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
|
||||
return await webwindowedBinds.openFileDialog({
|
||||
filters: [{ name: "Fastfiles", filter: "*.ff" }],
|
||||
});
|
||||
}
|
||||
|
||||
async function onOpenFastFileClick() {
|
||||
@@ -51,7 +53,7 @@ const availableZones = computed<SelectableZone[]>(() => {
|
||||
function onUnloadClicked() {
|
||||
if (!selectedZone.value) return;
|
||||
|
||||
webviewBinds.unloadZone(selectedZone.value).catch((e: string) => {
|
||||
webwindowedBinds.unloadZone(selectedZone.value).catch((e: string) => {
|
||||
console.error("Failed to unload zone:", e);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import { fileURLToPath, URL } from "node:url";
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import vueDevTools from "vite-plugin-vue-devtools";
|
||||
import headerTransformationPlugin from "./build/HeaderTransformationPlugin";
|
||||
import PluginCppHeader from "@laupetin/vite-plugin-cpp-header";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
rolldownOptions: {
|
||||
output: {
|
||||
assetFileNames: "[name][extname]",
|
||||
entryFileNames: "[name].js",
|
||||
@@ -21,7 +21,7 @@ export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
headerTransformationPlugin({
|
||||
PluginCppHeader({
|
||||
outputPath: fileURLToPath(
|
||||
new URL("../../build/src/ModMan/Web/ViteAssets.h", import.meta.url),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user